mirror of
https://github.com/esphome/esphome.git
synced 2025-01-10 06:33:19 +01:00
Updates
This commit is contained in:
parent
0a0713f0e2
commit
766f6c045d
44 changed files with 1202 additions and 592 deletions
|
@ -1,7 +1,7 @@
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_AUTOMATION_ID, CONF_CONDITION, CONF_ELSE, CONF_ID, CONF_THEN, \
|
from esphome.const import CONF_AUTOMATION_ID, CONF_CONDITION, CONF_ELSE, CONF_ID, CONF_THEN, \
|
||||||
CONF_TRIGGER_ID, CONF_TYPE_ID
|
CONF_TRIGGER_ID, CONF_TYPE_ID, CONF_TIME
|
||||||
from esphome.core import coroutine
|
from esphome.core import coroutine
|
||||||
from esphome.util import Registry
|
from esphome.util import Registry
|
||||||
|
|
||||||
|
@ -55,6 +55,7 @@ UpdateComponentAction = cg.esphome_ns.class_('UpdateComponentAction', Action)
|
||||||
Automation = cg.esphome_ns.class_('Automation')
|
Automation = cg.esphome_ns.class_('Automation')
|
||||||
|
|
||||||
LambdaCondition = cg.esphome_ns.class_('LambdaCondition', Condition)
|
LambdaCondition = cg.esphome_ns.class_('LambdaCondition', Condition)
|
||||||
|
ForCondition = cg.esphome_ns.class_('ForCondition', Condition)
|
||||||
|
|
||||||
|
|
||||||
def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
||||||
|
@ -78,6 +79,8 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
||||||
try:
|
try:
|
||||||
return cv.Schema([schema])(value)
|
return cv.Schema([schema])(value)
|
||||||
except cv.Invalid as err2:
|
except cv.Invalid as err2:
|
||||||
|
if u'extra keys not allowed' in str(err2) and len(err2.path) == 2:
|
||||||
|
raise err
|
||||||
if u'Unable to find action' in str(err):
|
if u'Unable to find action' in str(err):
|
||||||
raise err2
|
raise err2
|
||||||
raise cv.MultipleInvalid([err, err2])
|
raise cv.MultipleInvalid([err, err2])
|
||||||
|
@ -137,6 +140,19 @@ def lambda_condition_to_code(config, condition_id, template_arg, args):
|
||||||
yield cg.new_Pvariable(condition_id, template_arg, lambda_)
|
yield cg.new_Pvariable(condition_id, template_arg, lambda_)
|
||||||
|
|
||||||
|
|
||||||
|
@register_condition('for', ForCondition, cv.Schema({
|
||||||
|
cv.Required(CONF_TIME): cv.templatable(cv.positive_time_period_milliseconds),
|
||||||
|
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||||
|
}).extend(cv.COMPONENT_SCHEMA))
|
||||||
|
def for_condition_to_code(config, condition_id, template_arg, args):
|
||||||
|
condition = yield build_condition(config[CONF_CONDITION], cg.TemplateArguments(), [])
|
||||||
|
var = cg.new_Pvariable(condition_id, template_arg, condition)
|
||||||
|
yield cg.register_component(var, config)
|
||||||
|
templ = yield cg.templatable(config[CONF_TIME], args, cg.uint32)
|
||||||
|
cg.add(var.set_time(templ))
|
||||||
|
yield var
|
||||||
|
|
||||||
|
|
||||||
@register_action('delay', DelayAction, cv.templatable(cv.positive_time_period_milliseconds))
|
@register_action('delay', DelayAction, cv.templatable(cv.positive_time_period_milliseconds))
|
||||||
def delay_action_to_code(config, action_id, template_arg, args):
|
def delay_action_to_code(config, action_id, template_arg, args):
|
||||||
var = cg.new_Pvariable(action_id, template_arg)
|
var = cg.new_Pvariable(action_id, template_arg)
|
||||||
|
|
|
@ -13,7 +13,7 @@ from esphome.cpp_generator import ( # noqa
|
||||||
StructInitializer, ArrayInitializer, safe_exp, Statement,
|
StructInitializer, ArrayInitializer, safe_exp, Statement,
|
||||||
progmem_array, statement, variable, Pvariable, new_Pvariable,
|
progmem_array, statement, variable, Pvariable, new_Pvariable,
|
||||||
add, add_global, add_library, add_build_flag, add_define,
|
add, add_global, add_library, add_build_flag, add_define,
|
||||||
get_variable, process_lambda, is_template, templatable, MockObj,
|
get_variable, get_variable_with_full_id, process_lambda, is_template, templatable, MockObj,
|
||||||
MockObjClass)
|
MockObjClass)
|
||||||
from esphome.cpp_helpers import ( # noqa
|
from esphome.cpp_helpers import ( # noqa
|
||||||
gpio_pin_expression, register_component, build_registry_entry,
|
gpio_pin_expression, register_component, build_registry_entry,
|
||||||
|
|
|
@ -8,7 +8,7 @@ from esphome.const import CONF_DEVICE_CLASS, CONF_FILTERS, \
|
||||||
CONF_MAX_LENGTH, CONF_MIN_LENGTH, CONF_ON_CLICK, \
|
CONF_MAX_LENGTH, CONF_MIN_LENGTH, CONF_ON_CLICK, \
|
||||||
CONF_ON_DOUBLE_CLICK, CONF_ON_MULTI_CLICK, CONF_ON_PRESS, CONF_ON_RELEASE, CONF_ON_STATE, \
|
CONF_ON_DOUBLE_CLICK, CONF_ON_MULTI_CLICK, CONF_ON_PRESS, CONF_ON_RELEASE, CONF_ON_STATE, \
|
||||||
CONF_STATE, CONF_TIMING, CONF_TRIGGER_ID, CONF_FOR, CONF_NAME, CONF_MQTT_ID
|
CONF_STATE, CONF_TIMING, CONF_TRIGGER_ID, CONF_FOR, CONF_NAME, CONF_MQTT_ID
|
||||||
from esphome.core import CORE, coroutine
|
from esphome.core import CORE, coroutine, coroutine_with_priority
|
||||||
from esphome.py_compat import string_types
|
from esphome.py_compat import string_types
|
||||||
from esphome.util import Registry
|
from esphome.util import Registry
|
||||||
|
|
||||||
|
@ -282,7 +282,8 @@ def new_binary_sensor(config):
|
||||||
|
|
||||||
BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id({
|
BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id({
|
||||||
cv.Required(CONF_ID): cv.use_id(BinarySensor),
|
cv.Required(CONF_ID): cv.use_id(BinarySensor),
|
||||||
cv.Optional(CONF_FOR): cv.positive_time_period_milliseconds,
|
cv.Optional(CONF_FOR): cv.invalid("This option has been removed in 1.13, please use the "
|
||||||
|
"'for' condition instead."),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ -290,16 +291,17 @@ BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id({
|
||||||
BINARY_SENSOR_CONDITION_SCHEMA)
|
BINARY_SENSOR_CONDITION_SCHEMA)
|
||||||
def binary_sensor_is_on_to_code(config, condition_id, template_arg, args):
|
def binary_sensor_is_on_to_code(config, condition_id, template_arg, args):
|
||||||
paren = yield cg.get_variable(config[CONF_ID])
|
paren = yield cg.get_variable(config[CONF_ID])
|
||||||
yield cg.new_Pvariable(condition_id, template_arg, paren, True, config.get(CONF_FOR))
|
yield cg.new_Pvariable(condition_id, template_arg, paren, True)
|
||||||
|
|
||||||
|
|
||||||
@automation.register_condition('binary_sensor.is_off', BinarySensorCondition,
|
@automation.register_condition('binary_sensor.is_off', BinarySensorCondition,
|
||||||
BINARY_SENSOR_CONDITION_SCHEMA)
|
BINARY_SENSOR_CONDITION_SCHEMA)
|
||||||
def binary_sensor_is_off_to_code(config, condition_id, template_arg, args):
|
def binary_sensor_is_off_to_code(config, condition_id, template_arg, args):
|
||||||
paren = yield cg.get_variable(config[CONF_ID])
|
paren = yield cg.get_variable(config[CONF_ID])
|
||||||
yield cg.new_Pvariable(condition_id, template_arg, paren, False, config.get(CONF_FOR))
|
yield cg.new_Pvariable(condition_id, template_arg, paren, False)
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine_with_priority(100.0)
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
cg.add_define('USE_BINARY_SENSOR')
|
cg.add_define('USE_BINARY_SENSOR')
|
||||||
cg.add_global(binary_sensor_ns.using)
|
cg.add_global(binary_sensor_ns.using)
|
||||||
|
|
|
@ -125,22 +125,12 @@ class StateTrigger : public Trigger<bool> {
|
||||||
|
|
||||||
template<typename... Ts> class BinarySensorCondition : public Condition<Ts...> {
|
template<typename... Ts> class BinarySensorCondition : public Condition<Ts...> {
|
||||||
public:
|
public:
|
||||||
BinarySensorCondition(BinarySensor *parent, bool state, uint32_t for_time = 0)
|
BinarySensorCondition(BinarySensor *parent, bool state) : parent_(parent), state_(state) {}
|
||||||
: parent_(parent), state_(state), for_time_(for_time) {
|
bool check(Ts... x) override { return this->parent_->state == this->state_; }
|
||||||
parent->add_on_state_callback([this](bool state) { this->last_state_time_ = millis(); });
|
|
||||||
}
|
|
||||||
bool check(Ts... x) override {
|
|
||||||
if (this->parent_->state != this->state_)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return millis() - this->last_state_time_ >= this->for_time_;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
BinarySensor *parent_;
|
BinarySensor *parent_;
|
||||||
bool state_;
|
bool state_;
|
||||||
uint32_t last_state_time_{0};
|
|
||||||
uint32_t for_time_{0};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class BinarySensorPublishAction : public Action<Ts...> {
|
template<typename... Ts> class BinarySensorPublishAction : public Action<Ts...> {
|
||||||
|
|
|
@ -6,7 +6,7 @@ from esphome.const import CONF_AWAY, CONF_ID, CONF_INTERNAL, CONF_MAX_TEMPERATUR
|
||||||
CONF_MIN_TEMPERATURE, CONF_MODE, CONF_TARGET_TEMPERATURE, \
|
CONF_MIN_TEMPERATURE, CONF_MODE, CONF_TARGET_TEMPERATURE, \
|
||||||
CONF_TARGET_TEMPERATURE_HIGH, CONF_TARGET_TEMPERATURE_LOW, CONF_TEMPERATURE_STEP, CONF_VISUAL, \
|
CONF_TARGET_TEMPERATURE_HIGH, CONF_TARGET_TEMPERATURE_LOW, CONF_TEMPERATURE_STEP, CONF_VISUAL, \
|
||||||
CONF_MQTT_ID, CONF_NAME
|
CONF_MQTT_ID, CONF_NAME
|
||||||
from esphome.core import CORE, coroutine
|
from esphome.core import CORE, coroutine, coroutine_with_priority
|
||||||
|
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@ climate_ns = cg.esphome_ns.namespace('climate')
|
||||||
ClimateDevice = climate_ns.class_('Climate', cg.Nameable)
|
ClimateDevice = climate_ns.class_('Climate', cg.Nameable)
|
||||||
ClimateCall = climate_ns.class_('ClimateCall')
|
ClimateCall = climate_ns.class_('ClimateCall')
|
||||||
ClimateTraits = climate_ns.class_('ClimateTraits')
|
ClimateTraits = climate_ns.class_('ClimateTraits')
|
||||||
# MQTTClimateComponent = climate_ns.class_('MQTTClimateComponent', mqtt.MQTTComponent)
|
|
||||||
|
|
||||||
ClimateMode = climate_ns.enum('ClimateMode')
|
ClimateMode = climate_ns.enum('ClimateMode')
|
||||||
CLIMATE_MODES = {
|
CLIMATE_MODES = {
|
||||||
|
@ -100,6 +99,7 @@ def climate_control_to_code(config, action_id, template_arg, args):
|
||||||
yield var
|
yield var
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine_with_priority(100.0)
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
cg.add_define('USE_CLIMATE')
|
cg.add_define('USE_CLIMATE')
|
||||||
cg.add_global(climate_ns.using)
|
cg.add_global(climate_ns.using)
|
||||||
|
|
|
@ -18,7 +18,8 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(Ts... x) override {
|
||||||
auto call = this->climate_->make_call();
|
auto call = this->climate_->make_call();
|
||||||
call.set_target_temperature(this->mode_.optional_value(x...));
|
call.set_mode(this->mode_.optional_value(x...));
|
||||||
|
call.set_target_temperature(this->target_temperature_.optional_value(x...));
|
||||||
call.set_target_temperature_low(this->target_temperature_low_.optional_value(x...));
|
call.set_target_temperature_low(this->target_temperature_low_.optional_value(x...));
|
||||||
call.set_target_temperature_high(this->target_temperature_high_.optional_value(x...));
|
call.set_target_temperature_high(this->target_temperature_high_.optional_value(x...));
|
||||||
call.set_away(this->away_.optional_value(x...));
|
call.set_away(this->away_.optional_value(x...));
|
||||||
|
|
|
@ -5,7 +5,7 @@ from esphome.automation import maybe_simple_id, Condition
|
||||||
from esphome.components import mqtt
|
from esphome.components import mqtt
|
||||||
from esphome.const import CONF_ID, CONF_INTERNAL, CONF_DEVICE_CLASS, CONF_STATE, \
|
from esphome.const import CONF_ID, CONF_INTERNAL, CONF_DEVICE_CLASS, CONF_STATE, \
|
||||||
CONF_POSITION, CONF_TILT, CONF_STOP, CONF_MQTT_ID, CONF_NAME
|
CONF_POSITION, CONF_TILT, CONF_STOP, CONF_MQTT_ID, CONF_NAME
|
||||||
from esphome.core import CORE, coroutine
|
from esphome.core import CORE, coroutine, coroutine_with_priority
|
||||||
|
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
|
|
||||||
|
@ -124,6 +124,7 @@ def cover_control_to_code(config, action_id, template_arg, args):
|
||||||
yield var
|
yield var
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine_with_priority(100.0)
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
cg.add_define('USE_COVER')
|
cg.add_define('USE_COVER')
|
||||||
cg.add_global(cover_ns.using)
|
cg.add_global(cover_ns.using)
|
||||||
|
|
|
@ -4,7 +4,7 @@ import esphome.config_validation as cv
|
||||||
from esphome import core, automation
|
from esphome import core, automation
|
||||||
from esphome.automation import maybe_simple_id
|
from esphome.automation import maybe_simple_id
|
||||||
from esphome.const import CONF_ID, CONF_LAMBDA, CONF_PAGES, CONF_ROTATION, CONF_UPDATE_INTERVAL
|
from esphome.const import CONF_ID, CONF_LAMBDA, CONF_PAGES, CONF_ROTATION, CONF_UPDATE_INTERVAL
|
||||||
from esphome.core import coroutine
|
from esphome.core import coroutine, coroutine_with_priority
|
||||||
|
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
|
|
||||||
|
@ -98,5 +98,6 @@ def display_page_show_previous_to_code(config, action_id, template_arg, args):
|
||||||
yield cg.new_Pvariable(action_id, template_arg, paren)
|
yield cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine_with_priority(100.0)
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
cg.add_global(display_ns.using)
|
cg.add_global(display_ns.using)
|
||||||
|
|
|
@ -6,7 +6,7 @@ from esphome.components import mqtt
|
||||||
from esphome.const import CONF_ID, CONF_INTERNAL, CONF_MQTT_ID, CONF_OSCILLATING, \
|
from esphome.const import CONF_ID, CONF_INTERNAL, CONF_MQTT_ID, CONF_OSCILLATING, \
|
||||||
CONF_OSCILLATION_COMMAND_TOPIC, CONF_OSCILLATION_STATE_TOPIC, CONF_SPEED, \
|
CONF_OSCILLATION_COMMAND_TOPIC, CONF_OSCILLATION_STATE_TOPIC, CONF_SPEED, \
|
||||||
CONF_SPEED_COMMAND_TOPIC, CONF_SPEED_STATE_TOPIC, CONF_NAME
|
CONF_SPEED_COMMAND_TOPIC, CONF_SPEED_STATE_TOPIC, CONF_NAME
|
||||||
from esphome.core import CORE, coroutine
|
from esphome.core import CORE, coroutine, coroutine_with_priority
|
||||||
|
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
|
|
||||||
|
@ -107,6 +107,7 @@ def fan_turn_on_to_code(config, action_id, template_arg, args):
|
||||||
yield var
|
yield var
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine_with_priority(100.0)
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
cg.add_define('USE_FAN')
|
cg.add_define('USE_FAN')
|
||||||
cg.add_global(fan_ns.using)
|
cg.add_global(fan_ns.using)
|
||||||
|
|
|
@ -1,23 +1,26 @@
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
from esphome import config_validation as cv
|
from esphome import config_validation as cv, automation
|
||||||
from esphome import codegen as cg
|
from esphome import codegen as cg
|
||||||
from esphome.const import CONF_ID, CONF_INITIAL_VALUE, CONF_RESTORE_VALUE, CONF_TYPE
|
from esphome.const import CONF_ID, CONF_INITIAL_VALUE, CONF_RESTORE_VALUE, CONF_TYPE, CONF_VALUE
|
||||||
|
from esphome.core import coroutine_with_priority
|
||||||
from esphome.py_compat import IS_PY3
|
from esphome.py_compat import IS_PY3
|
||||||
|
|
||||||
globals_ns = cg.esphome_ns.namespace('globals')
|
globals_ns = cg.esphome_ns.namespace('globals')
|
||||||
GlobalsComponent = globals_ns.class_('GlobalsComponent', cg.Component)
|
GlobalsComponent = globals_ns.class_('GlobalsComponent', cg.Component)
|
||||||
|
GlobalVarSetAction = globals_ns.class_('GlobalVarSetAction', automation.Action)
|
||||||
|
|
||||||
MULTI_CONF = True
|
MULTI_CONF = True
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema({
|
CONFIG_SCHEMA = cv.Schema({
|
||||||
cv.Required(CONF_ID): cv.declare_id(GlobalsComponent),
|
cv.Required(CONF_ID): cv.declare_id(GlobalsComponent),
|
||||||
cv.Required(CONF_TYPE): cv.string_strict,
|
cv.Required(CONF_TYPE): cv.string_strict,
|
||||||
cv.Optional(CONF_INITIAL_VALUE): cv.string_strict,
|
cv.Optional(CONF_INITIAL_VALUE): cv.string_strict,
|
||||||
cv.Optional(CONF_RESTORE_VALUE): cv.boolean,
|
cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean,
|
||||||
}).extend(cv.COMPONENT_SCHEMA)
|
}).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
|
# Run with low priority so that namespaces are registered first
|
||||||
|
@coroutine_with_priority(-100.0)
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
type_ = cg.RawExpression(config[CONF_TYPE])
|
type_ = cg.RawExpression(config[CONF_TYPE])
|
||||||
template_args = cg.TemplateArguments(type_)
|
template_args = cg.TemplateArguments(type_)
|
||||||
|
@ -31,9 +34,22 @@ def to_code(config):
|
||||||
glob = cg.Pvariable(config[CONF_ID], rhs, type=res_type)
|
glob = cg.Pvariable(config[CONF_ID], rhs, type=res_type)
|
||||||
yield cg.register_component(glob, config)
|
yield cg.register_component(glob, config)
|
||||||
|
|
||||||
if config.get(CONF_RESTORE_VALUE, False):
|
if config[CONF_RESTORE_VALUE]:
|
||||||
value = config[CONF_ID].id
|
value = config[CONF_ID].id
|
||||||
if IS_PY3 and isinstance(value, str):
|
if IS_PY3 and isinstance(value, str):
|
||||||
value = value.encode()
|
value = value.encode()
|
||||||
hash_ = int(hashlib.md5(value).hexdigest()[:8], 16)
|
hash_ = int(hashlib.md5(value).hexdigest()[:8], 16)
|
||||||
cg.add(glob.set_restore_value(hash_))
|
cg.add(glob.set_restore_value(hash_))
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action('globals.set', GlobalVarSetAction, cv.Schema({
|
||||||
|
cv.Required(CONF_ID): cv.use_id(GlobalsComponent),
|
||||||
|
cv.Required(CONF_VALUE): cv.templatable(cv.string_strict),
|
||||||
|
}))
|
||||||
|
def globals_set_to_code(config, action_id, template_arg, args):
|
||||||
|
full_id, paren = yield cg.get_variable_with_full_id(config[CONF_ID])
|
||||||
|
template_arg = cg.TemplateArguments(full_id.type, *template_arg)
|
||||||
|
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
|
templ = yield cg.templatable(config[CONF_VALUE], args, None)
|
||||||
|
cg.add(var.set_value(templ))
|
||||||
|
yield var
|
||||||
|
|
|
@ -8,6 +8,7 @@ namespace globals {
|
||||||
|
|
||||||
template<typename T> class GlobalsComponent : public Component {
|
template<typename T> class GlobalsComponent : public Component {
|
||||||
public:
|
public:
|
||||||
|
using value_type = T;
|
||||||
explicit GlobalsComponent() = default;
|
explicit GlobalsComponent() = default;
|
||||||
explicit GlobalsComponent(T initial_value) : value_(initial_value) {}
|
explicit GlobalsComponent(T initial_value) : value_(initial_value) {}
|
||||||
explicit GlobalsComponent(std::array<typename std::remove_extent<T>::type, std::extent<T>::value> initial_value) {
|
explicit GlobalsComponent(std::array<typename std::remove_extent<T>::type, std::extent<T>::value> initial_value) {
|
||||||
|
@ -49,5 +50,19 @@ template<typename T> class GlobalsComponent : public Component {
|
||||||
ESPPreferenceObject rtc_;
|
ESPPreferenceObject rtc_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<class C, typename... Ts> class GlobalVarSetAction : public Action<Ts...> {
|
||||||
|
public:
|
||||||
|
explicit GlobalVarSetAction(C *parent) : parent_(parent) {}
|
||||||
|
|
||||||
|
using T = typename C::value_type;
|
||||||
|
|
||||||
|
TEMPLATABLE_VALUE(T, value);
|
||||||
|
|
||||||
|
void play(Ts... x) override { this->parent_->value() = this->value_.value(x...); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
C *parent_;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace globals
|
} // namespace globals
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -3,7 +3,7 @@ import esphome.config_validation as cv
|
||||||
from esphome import pins
|
from esphome import pins
|
||||||
from esphome.const import CONF_FREQUENCY, CONF_ID, CONF_SCAN, CONF_SCL, CONF_SDA, CONF_ADDRESS, \
|
from esphome.const import CONF_FREQUENCY, CONF_ID, CONF_SCAN, CONF_SCL, CONF_SDA, CONF_ADDRESS, \
|
||||||
CONF_I2C_ID
|
CONF_I2C_ID
|
||||||
from esphome.core import coroutine
|
from esphome.core import coroutine, coroutine_with_priority
|
||||||
|
|
||||||
i2c_ns = cg.esphome_ns.namespace('i2c')
|
i2c_ns = cg.esphome_ns.namespace('i2c')
|
||||||
I2CComponent = i2c_ns.class_('I2CComponent', cg.Component)
|
I2CComponent = i2c_ns.class_('I2CComponent', cg.Component)
|
||||||
|
@ -20,7 +20,9 @@ CONFIG_SCHEMA = cv.Schema({
|
||||||
}).extend(cv.COMPONENT_SCHEMA)
|
}).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine_with_priority(1.0)
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
|
cg.add_global(i2c_ns.using)
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
yield cg.register_component(var, config)
|
yield cg.register_component(var, config)
|
||||||
|
|
||||||
|
@ -29,7 +31,6 @@ def to_code(config):
|
||||||
cg.add(var.set_frequency(int(config[CONF_FREQUENCY])))
|
cg.add(var.set_frequency(int(config[CONF_FREQUENCY])))
|
||||||
cg.add(var.set_scan(config[CONF_SCAN]))
|
cg.add(var.set_scan(config[CONF_SCAN]))
|
||||||
cg.add_library('Wire', None)
|
cg.add_library('Wire', None)
|
||||||
cg.add_global(i2c_ns.using)
|
|
||||||
|
|
||||||
|
|
||||||
def i2c_device_schema(default_address):
|
def i2c_device_schema(default_address):
|
||||||
|
|
0
esphome/components/integration/__init__.py
Normal file
0
esphome/components/integration/__init__.py
Normal file
68
esphome/components/integration/integration_sensor.cpp
Normal file
68
esphome/components/integration/integration_sensor.cpp
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
#include "integration_sensor.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace integration {
|
||||||
|
|
||||||
|
static const char *TAG = "integration";
|
||||||
|
|
||||||
|
void IntegrationSensor::setup() {
|
||||||
|
if (this->restore_) {
|
||||||
|
this->rtc_ = global_preferences.make_preference<float>(this->get_object_id_hash());
|
||||||
|
this->rtc_.load(&this->result_);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->last_update_ = millis();
|
||||||
|
this->publish_and_save_(this->result_);
|
||||||
|
this->sensor_->add_on_state_callback([this](float state) { this->process_sensor_value_(state); });
|
||||||
|
}
|
||||||
|
void IntegrationSensor::dump_config() { LOG_SENSOR("", "Integration Sensor", this); }
|
||||||
|
std::string IntegrationSensor::unit_of_measurement() {
|
||||||
|
std::string suffix;
|
||||||
|
switch (this->time_) {
|
||||||
|
case INTEGRATION_SENSOR_TIME_MILLISECOND:
|
||||||
|
suffix = "ms";
|
||||||
|
break;
|
||||||
|
case INTEGRATION_SENSOR_TIME_SECOND:
|
||||||
|
suffix = "s";
|
||||||
|
break;
|
||||||
|
case INTEGRATION_SENSOR_TIME_MINUTE:
|
||||||
|
suffix = "min";
|
||||||
|
break;
|
||||||
|
case INTEGRATION_SENSOR_TIME_HOUR:
|
||||||
|
suffix = "h";
|
||||||
|
break;
|
||||||
|
case INTEGRATION_SENSOR_TIME_DAY:
|
||||||
|
suffix = "d";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
std::string base = this->sensor_->get_unit_of_measurement();
|
||||||
|
if (str_endswith(base, "/" + suffix)) {
|
||||||
|
return base.substr(0, base.size() - suffix.size() - 1);
|
||||||
|
}
|
||||||
|
return base + suffix;
|
||||||
|
}
|
||||||
|
void IntegrationSensor::process_sensor_value_(float value) {
|
||||||
|
const uint32_t now = millis();
|
||||||
|
const float old_value = this->last_value_;
|
||||||
|
const float new_value = value;
|
||||||
|
const uint32_t dt_ms = now - this->last_update_;
|
||||||
|
const float dt = dt_ms * this->get_time_factor_();
|
||||||
|
float area = 0.0f;
|
||||||
|
switch (this->method_) {
|
||||||
|
case INTEGRATION_METHOD_TRAPEZOID:
|
||||||
|
area = dt * (old_value + new_value) / 2.0f;
|
||||||
|
break;
|
||||||
|
case INTEGRATION_METHOD_LEFT:
|
||||||
|
area = dt * old_value;
|
||||||
|
break;
|
||||||
|
case INTEGRATION_METHOD_RIGHT:
|
||||||
|
area = dt * new_value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this->publish_and_save_(this->last_value_ + area);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace integration
|
||||||
|
} // namespace esphome
|
85
esphome/components/integration/integration_sensor.h
Normal file
85
esphome/components/integration/integration_sensor.h
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/preferences.h"
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace integration {
|
||||||
|
|
||||||
|
enum IntegrationSensorTime {
|
||||||
|
INTEGRATION_SENSOR_TIME_MILLISECOND = 0,
|
||||||
|
INTEGRATION_SENSOR_TIME_SECOND,
|
||||||
|
INTEGRATION_SENSOR_TIME_MINUTE,
|
||||||
|
INTEGRATION_SENSOR_TIME_HOUR,
|
||||||
|
INTEGRATION_SENSOR_TIME_DAY,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum IntegrationMethod {
|
||||||
|
INTEGRATION_METHOD_TRAPEZOID = 0,
|
||||||
|
INTEGRATION_METHOD_LEFT,
|
||||||
|
INTEGRATION_METHOD_RIGHT,
|
||||||
|
};
|
||||||
|
|
||||||
|
class IntegrationSensor : public sensor::Sensor, public Component {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
|
void set_sensor(Sensor *sensor) { sensor_ = sensor; }
|
||||||
|
void set_time(IntegrationSensorTime time) { time_ = time; }
|
||||||
|
void set_method(IntegrationMethod method) { method_ = method; }
|
||||||
|
void set_restore(bool restore) { restore_ = restore; }
|
||||||
|
void reset() { this->publish_and_save_(0.0f); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void process_sensor_value_(float value);
|
||||||
|
float get_time_factor_() {
|
||||||
|
switch (this->time_) {
|
||||||
|
case INTEGRATION_SENSOR_TIME_MILLISECOND:
|
||||||
|
return 1.0f;
|
||||||
|
case INTEGRATION_SENSOR_TIME_SECOND:
|
||||||
|
return 1.0f / 1000.0f;
|
||||||
|
case INTEGRATION_SENSOR_TIME_MINUTE:
|
||||||
|
return 1.0f / 60000.0f;
|
||||||
|
case INTEGRATION_SENSOR_TIME_HOUR:
|
||||||
|
return 1.0f / 3600000.0f;
|
||||||
|
case INTEGRATION_SENSOR_TIME_DAY:
|
||||||
|
return 1.0f / 86400000.0f;
|
||||||
|
default:
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void publish_and_save_(float result) {
|
||||||
|
this->result_ = result;
|
||||||
|
this->publish_state(result);
|
||||||
|
this->rtc_.save(&result);
|
||||||
|
}
|
||||||
|
std::string unit_of_measurement() override;
|
||||||
|
std::string icon() override { return this->sensor_->get_icon(); }
|
||||||
|
int8_t accuracy_decimals() override { return this->sensor_->get_accuracy_decimals() + 2; }
|
||||||
|
|
||||||
|
sensor::Sensor *sensor_;
|
||||||
|
IntegrationSensorTime time_;
|
||||||
|
IntegrationMethod method_;
|
||||||
|
bool restore_;
|
||||||
|
ESPPreferenceObject rtc_;
|
||||||
|
|
||||||
|
uint32_t last_update_;
|
||||||
|
float result_{0.0f};
|
||||||
|
float last_value_{0.0f};
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class ResetAction : public Action<Ts...> {
|
||||||
|
public:
|
||||||
|
explicit ResetAction(IntegrationSensor *parent) : parent_(parent) {}
|
||||||
|
|
||||||
|
void play(Ts... x) override { this->parent_->reset(); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
IntegrationSensor *parent_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace integration
|
||||||
|
} // namespace esphome
|
58
esphome/components/integration/sensor.py
Normal file
58
esphome/components/integration/sensor.py
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome import automation
|
||||||
|
from esphome.components import sensor
|
||||||
|
from esphome.const import CONF_ID, CONF_TIME_ID, CONF_SENSOR, CONF_RESTORE
|
||||||
|
|
||||||
|
integration_ns = cg.esphome_ns.namespace('integration')
|
||||||
|
IntegrationSensor = integration_ns.class_('IntegrationSensor', sensor.Sensor, cg.Component)
|
||||||
|
ResetAction = integration_ns.class_('ResetAction', automation.Action)
|
||||||
|
|
||||||
|
IntegrationSensorTime = integration_ns.enum('IntegrationSensorTime')
|
||||||
|
INTEGRATION_TIMES = {
|
||||||
|
'ms': IntegrationSensorTime.INTEGRATION_SENSOR_TIME_MILLISECOND,
|
||||||
|
's': IntegrationSensorTime.INTEGRATION_SENSOR_TIME_SECOND,
|
||||||
|
'min': IntegrationSensorTime.INTEGRATION_SENSOR_TIME_MINUTE,
|
||||||
|
'h': IntegrationSensorTime.INTEGRATION_SENSOR_TIME_HOUR,
|
||||||
|
'd': IntegrationSensorTime.INTEGRATION_SENSOR_TIME_DAY,
|
||||||
|
}
|
||||||
|
IntegrationMethod = integration_ns.enum('IntegrationMethod')
|
||||||
|
INTEGRATION_METHODS = {
|
||||||
|
'trapezoid': IntegrationMethod.INTEGRATION_METHOD_TRAPEZOID,
|
||||||
|
'left': IntegrationMethod.INTEGRATION_METHOD_LEFT,
|
||||||
|
'right': IntegrationMethod.INTEGRATION_METHOD_RIGHT,
|
||||||
|
}
|
||||||
|
|
||||||
|
CONF_TIME_UNIT = 'time_unit'
|
||||||
|
CONF_INTEGRATION_METHOD = 'integration_method'
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend({
|
||||||
|
cv.GenerateID(): cv.declare_id(IntegrationSensor),
|
||||||
|
cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor),
|
||||||
|
cv.Required(CONF_TIME_UNIT): cv.enum(INTEGRATION_TIMES, lower=True),
|
||||||
|
cv.Optional(CONF_INTEGRATION_METHOD, default='trapezoid'):
|
||||||
|
cv.enum(INTEGRATION_METHODS, lower=True),
|
||||||
|
cv.Optional(CONF_RESTORE, default=True): cv.boolean,
|
||||||
|
}).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
|
def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
|
||||||
|
yield cg.register_component(var, config)
|
||||||
|
yield sensor.register_sensor(var, config)
|
||||||
|
|
||||||
|
sens = yield cg.get_variable(config[CONF_SENSOR])
|
||||||
|
cg.add(var.set_sensor(sens))
|
||||||
|
cg.add(var.set_time(config[CONF_TIME_ID]))
|
||||||
|
cg.add(var.set_method(config[CONF_INTEGRATION_METHOD]))
|
||||||
|
cg.add(var.set_restore(config[CONF_RESTORE]))
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action('sensor.integration.reset', ResetAction, automation.maybe_simple_id({
|
||||||
|
cv.Required(CONF_ID): cv.use_id(IntegrationSensor),
|
||||||
|
}))
|
||||||
|
def sensor_integration_reset_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)
|
|
@ -1,8 +1,10 @@
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
from esphome.core import coroutine_with_priority
|
||||||
|
|
||||||
json_ns = cg.esphome_ns.namespace('json')
|
json_ns = cg.esphome_ns.namespace('json')
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine_with_priority(1.0)
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
cg.add_library('ArduinoJson-esphomelib', '5.13.3')
|
cg.add_library('ArduinoJson-esphomelib', '5.13.3')
|
||||||
cg.add_define('USE_JSON')
|
cg.add_define('USE_JSON')
|
||||||
|
|
|
@ -1,296 +1,17 @@
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome import automation
|
|
||||||
from esphome.automation import maybe_simple_id
|
|
||||||
from esphome.components import mqtt
|
from esphome.components import mqtt
|
||||||
from esphome.const import CONF_ALPHA, CONF_BLUE, CONF_BRIGHTNESS, CONF_COLORS, CONF_COLOR_CORRECT, \
|
from esphome.const import CONF_COLOR_CORRECT, \
|
||||||
CONF_COLOR_TEMPERATURE, CONF_DEFAULT_TRANSITION_LENGTH, CONF_DURATION, CONF_EFFECT, \
|
CONF_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, CONF_GAMMA_CORRECT, CONF_ID, \
|
||||||
CONF_EFFECTS, CONF_FLASH_LENGTH, CONF_GAMMA_CORRECT, CONF_GREEN, CONF_ID, \
|
CONF_INTERNAL, CONF_NAME, CONF_MQTT_ID
|
||||||
CONF_INTERNAL, CONF_LAMBDA, CONF_NAME, CONF_NUM_LEDS, CONF_RANDOM, CONF_RED, \
|
from esphome.core import coroutine, coroutine_with_priority
|
||||||
CONF_SPEED, CONF_STATE, CONF_TRANSITION_LENGTH, CONF_UPDATE_INTERVAL, CONF_WHITE, CONF_WIDTH, \
|
from .automation import light_control_to_code # noqa
|
||||||
CONF_MQTT_ID
|
from .effects import validate_effects, BINARY_EFFECTS, \
|
||||||
from esphome.core import coroutine
|
MONOCHROMATIC_EFFECTS, RGB_EFFECTS, ADDRESSABLE_EFFECTS, EFFECTS_REGISTRY
|
||||||
from esphome.util import Registry
|
from .types import ( # noqa
|
||||||
|
LightState, AddressableLightState, light_ns, LightOutput, AddressableLight)
|
||||||
|
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
|
|
||||||
# Base
|
|
||||||
light_ns = cg.esphome_ns.namespace('light')
|
|
||||||
LightState = light_ns.class_('LightState', cg.Nameable, cg.Component)
|
|
||||||
# Fake class for addressable lights
|
|
||||||
AddressableLightState = light_ns.class_('LightState', LightState)
|
|
||||||
LightOutput = light_ns.class_('LightOutput')
|
|
||||||
AddressableLight = light_ns.class_('AddressableLight')
|
|
||||||
AddressableLightRef = AddressableLight.operator('ref')
|
|
||||||
|
|
||||||
# Actions
|
|
||||||
ToggleAction = light_ns.class_('ToggleAction', automation.Action)
|
|
||||||
LightControlAction = light_ns.class_('LightControlAction', automation.Action)
|
|
||||||
|
|
||||||
LightColorValues = light_ns.class_('LightColorValues')
|
|
||||||
|
|
||||||
# Effects
|
|
||||||
LightEffect = light_ns.class_('LightEffect')
|
|
||||||
RandomLightEffect = light_ns.class_('RandomLightEffect', LightEffect)
|
|
||||||
LambdaLightEffect = light_ns.class_('LambdaLightEffect', LightEffect)
|
|
||||||
StrobeLightEffect = light_ns.class_('StrobeLightEffect', LightEffect)
|
|
||||||
StrobeLightEffectColor = light_ns.class_('StrobeLightEffectColor', LightEffect)
|
|
||||||
FlickerLightEffect = light_ns.class_('FlickerLightEffect', LightEffect)
|
|
||||||
AddressableLightEffect = light_ns.class_('AddressableLightEffect', LightEffect)
|
|
||||||
AddressableLambdaLightEffect = light_ns.class_('AddressableLambdaLightEffect',
|
|
||||||
AddressableLightEffect)
|
|
||||||
AddressableRainbowLightEffect = light_ns.class_('AddressableRainbowLightEffect',
|
|
||||||
AddressableLightEffect)
|
|
||||||
AddressableColorWipeEffect = light_ns.class_('AddressableColorWipeEffect', AddressableLightEffect)
|
|
||||||
AddressableColorWipeEffectColor = light_ns.struct('AddressableColorWipeEffectColor')
|
|
||||||
AddressableScanEffect = light_ns.class_('AddressableScanEffect', AddressableLightEffect)
|
|
||||||
AddressableTwinkleEffect = light_ns.class_('AddressableTwinkleEffect', AddressableLightEffect)
|
|
||||||
AddressableRandomTwinkleEffect = light_ns.class_('AddressableRandomTwinkleEffect',
|
|
||||||
AddressableLightEffect)
|
|
||||||
AddressableFireworksEffect = light_ns.class_('AddressableFireworksEffect', AddressableLightEffect)
|
|
||||||
AddressableFlickerEffect = light_ns.class_('AddressableFlickerEffect', AddressableLightEffect)
|
|
||||||
|
|
||||||
CONF_STROBE = 'strobe'
|
|
||||||
CONF_FLICKER = 'flicker'
|
|
||||||
CONF_ADDRESSABLE_LAMBDA = 'addressable_lambda'
|
|
||||||
CONF_ADDRESSABLE_RAINBOW = 'addressable_rainbow'
|
|
||||||
CONF_ADDRESSABLE_COLOR_WIPE = 'addressable_color_wipe'
|
|
||||||
CONF_ADDRESSABLE_SCAN = 'addressable_scan'
|
|
||||||
CONF_ADDRESSABLE_TWINKLE = 'addressable_twinkle'
|
|
||||||
CONF_ADDRESSABLE_RANDOM_TWINKLE = 'addressable_random_twinkle'
|
|
||||||
CONF_ADDRESSABLE_FIREWORKS = 'addressable_fireworks'
|
|
||||||
CONF_ADDRESSABLE_FLICKER = 'addressable_flicker'
|
|
||||||
|
|
||||||
CONF_ADD_LED_INTERVAL = 'add_led_interval'
|
|
||||||
CONF_REVERSE = 'reverse'
|
|
||||||
CONF_MOVE_INTERVAL = 'move_interval'
|
|
||||||
CONF_TWINKLE_PROBABILITY = 'twinkle_probability'
|
|
||||||
CONF_PROGRESS_INTERVAL = 'progress_interval'
|
|
||||||
CONF_SPARK_PROBABILITY = 'spark_probability'
|
|
||||||
CONF_USE_RANDOM_COLOR = 'use_random_color'
|
|
||||||
CONF_FADE_OUT_RATE = 'fade_out_rate'
|
|
||||||
CONF_INTENSITY = 'intensity'
|
|
||||||
|
|
||||||
BINARY_EFFECTS = [CONF_LAMBDA, CONF_STROBE]
|
|
||||||
MONOCHROMATIC_EFFECTS = BINARY_EFFECTS + [CONF_FLICKER]
|
|
||||||
RGB_EFFECTS = MONOCHROMATIC_EFFECTS + [CONF_RANDOM]
|
|
||||||
ADDRESSABLE_EFFECTS = RGB_EFFECTS + [CONF_ADDRESSABLE_LAMBDA, CONF_ADDRESSABLE_RAINBOW,
|
|
||||||
CONF_ADDRESSABLE_COLOR_WIPE, CONF_ADDRESSABLE_SCAN,
|
|
||||||
CONF_ADDRESSABLE_TWINKLE, CONF_ADDRESSABLE_RANDOM_TWINKLE,
|
|
||||||
CONF_ADDRESSABLE_FIREWORKS, CONF_ADDRESSABLE_FLICKER]
|
|
||||||
|
|
||||||
EFFECTS_REGISTRY = Registry()
|
|
||||||
|
|
||||||
|
|
||||||
def register_effect(name, effect_type, default_name, schema, *extra_validators):
|
|
||||||
schema = cv.Schema(schema).extend({
|
|
||||||
cv.Optional(CONF_NAME, default=default_name): cv.string_strict,
|
|
||||||
})
|
|
||||||
validator = cv.All(schema, *extra_validators)
|
|
||||||
return EFFECTS_REGISTRY.register(name, effect_type, validator)
|
|
||||||
|
|
||||||
|
|
||||||
@register_effect('lambda', LambdaLightEffect, "Lambda", {
|
|
||||||
cv.Required(CONF_LAMBDA): cv.lambda_,
|
|
||||||
cv.Optional(CONF_UPDATE_INTERVAL, default='0ms'): cv.update_interval,
|
|
||||||
})
|
|
||||||
def lambda_effect_to_code(config, effect_id):
|
|
||||||
lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], [], return_type=cg.void)
|
|
||||||
yield cg.new_Pvariable(effect_id, config[CONF_NAME], lambda_,
|
|
||||||
config[CONF_UPDATE_INTERVAL])
|
|
||||||
|
|
||||||
|
|
||||||
@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,
|
|
||||||
})
|
|
||||||
def random_effect_to_code(config, effect_id):
|
|
||||||
effect = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
|
||||||
cg.add(effect.set_transition_length(config[CONF_TRANSITION_LENGTH]))
|
|
||||||
cg.add(effect.set_update_interval(config[CONF_UPDATE_INTERVAL]))
|
|
||||||
yield effect
|
|
||||||
|
|
||||||
|
|
||||||
@register_effect('strobe', StrobeLightEffect, "Strobe", {
|
|
||||||
cv.Optional(CONF_COLORS, default=[
|
|
||||||
{CONF_STATE: True, CONF_DURATION: '0.5s'},
|
|
||||||
{CONF_STATE: False, CONF_DURATION: '0.5s'},
|
|
||||||
]): cv.All(cv.ensure_list(cv.Schema({
|
|
||||||
cv.Optional(CONF_STATE, default=True): cv.boolean,
|
|
||||||
cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage,
|
|
||||||
cv.Optional(CONF_RED, default=1.0): cv.percentage,
|
|
||||||
cv.Optional(CONF_GREEN, default=1.0): cv.percentage,
|
|
||||||
cv.Optional(CONF_BLUE, default=1.0): cv.percentage,
|
|
||||||
cv.Optional(CONF_WHITE, default=1.0): cv.percentage,
|
|
||||||
cv.Required(CONF_DURATION): cv.positive_time_period_milliseconds,
|
|
||||||
}), cv.has_at_least_one_key(CONF_STATE, CONF_BRIGHTNESS, CONF_RED, CONF_GREEN, CONF_BLUE,
|
|
||||||
CONF_WHITE)), cv.Length(min=2)),
|
|
||||||
})
|
|
||||||
def strobe_effect_to_code(config, effect_id):
|
|
||||||
var = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
|
||||||
colors = []
|
|
||||||
for color in config.get(CONF_COLORS, []):
|
|
||||||
colors.append(cg.StructInitializer(
|
|
||||||
StrobeLightEffectColor,
|
|
||||||
('color', LightColorValues(color[CONF_STATE], color[CONF_BRIGHTNESS],
|
|
||||||
color[CONF_RED], color[CONF_GREEN], color[CONF_BLUE],
|
|
||||||
color[CONF_WHITE])),
|
|
||||||
('duration', color[CONF_DURATION]),
|
|
||||||
))
|
|
||||||
cg.add(var.set_colors(colors))
|
|
||||||
yield var
|
|
||||||
|
|
||||||
|
|
||||||
@register_effect('flicker', FlickerLightEffect, "Flicker", {
|
|
||||||
cv.Optional(CONF_ALPHA, default=0.95): cv.percentage,
|
|
||||||
cv.Optional(CONF_INTENSITY, default=0.015): cv.percentage,
|
|
||||||
})
|
|
||||||
def flicker_effect_to_code(config, effect_id):
|
|
||||||
var = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
|
||||||
cg.add(var.set_alpha(config[CONF_ALPHA]))
|
|
||||||
cg.add(var.set_intensity(config[CONF_INTENSITY]))
|
|
||||||
yield var
|
|
||||||
|
|
||||||
|
|
||||||
@register_effect('addressable_lambda', AddressableLambdaLightEffect, "Addressable Lambda", {
|
|
||||||
cv.Required(CONF_LAMBDA): cv.lambda_,
|
|
||||||
cv.Optional(CONF_UPDATE_INTERVAL, default='0ms'): cv.positive_time_period_milliseconds,
|
|
||||||
})
|
|
||||||
def addressable_lambda_effect_to_code(config, effect_id):
|
|
||||||
args = [(AddressableLightRef, 'it')]
|
|
||||||
lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], args, return_type=cg.void)
|
|
||||||
var = cg.new_Pvariable(effect_id, config[CONF_NAME], lambda_,
|
|
||||||
config[CONF_UPDATE_INTERVAL])
|
|
||||||
yield var
|
|
||||||
|
|
||||||
|
|
||||||
@register_effect('addressable_rainbow', AddressableRainbowLightEffect, "Rainbow", {
|
|
||||||
cv.Optional(CONF_SPEED, default=10): cv.uint32_t,
|
|
||||||
cv.Optional(CONF_WIDTH, default=50): cv.uint32_t,
|
|
||||||
})
|
|
||||||
def addressable_rainbow_effect_to_code(config, effect_id):
|
|
||||||
var = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
|
||||||
cg.add(var.set_speed(config[CONF_SPEED]))
|
|
||||||
cg.add(var.set_width(config[CONF_WIDTH]))
|
|
||||||
yield var
|
|
||||||
|
|
||||||
|
|
||||||
@register_effect('addressable_color_wipe', AddressableColorWipeEffect, "Color Wipe", {
|
|
||||||
cv.Optional(CONF_COLORS, default=[{CONF_NUM_LEDS: 1, CONF_RANDOM: True}]): cv.ensure_list({
|
|
||||||
cv.Optional(CONF_RED, default=1.0): cv.percentage,
|
|
||||||
cv.Optional(CONF_GREEN, default=1.0): cv.percentage,
|
|
||||||
cv.Optional(CONF_BLUE, default=1.0): cv.percentage,
|
|
||||||
cv.Optional(CONF_WHITE, default=1.0): cv.percentage,
|
|
||||||
cv.Optional(CONF_RANDOM, default=False): cv.boolean,
|
|
||||||
cv.Required(CONF_NUM_LEDS): cv.All(cv.uint32_t, cv.Range(min=1)),
|
|
||||||
}),
|
|
||||||
cv.Optional(CONF_ADD_LED_INTERVAL, default='0.1s'): cv.positive_time_period_milliseconds,
|
|
||||||
cv.Optional(CONF_REVERSE, default=False): cv.boolean,
|
|
||||||
})
|
|
||||||
def addressable_color_wipe_effect_to_code(config, effect_id):
|
|
||||||
var = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
|
||||||
cg.add(var.set_add_led_interval(config[CONF_ADD_LED_INTERVAL]))
|
|
||||||
cg.add(var.set_reverse(config[CONF_REVERSE]))
|
|
||||||
colors = []
|
|
||||||
for color in config.get(CONF_COLORS, []):
|
|
||||||
colors.append(cg.StructInitializer(
|
|
||||||
AddressableColorWipeEffectColor,
|
|
||||||
('r', int(round(color[CONF_RED] * 255))),
|
|
||||||
('g', int(round(color[CONF_GREEN] * 255))),
|
|
||||||
('b', int(round(color[CONF_BLUE] * 255))),
|
|
||||||
('w', int(round(color[CONF_WHITE] * 255))),
|
|
||||||
('random', color[CONF_RANDOM]),
|
|
||||||
('num_leds', color[CONF_NUM_LEDS]),
|
|
||||||
))
|
|
||||||
cg.add(var.set_colors(colors))
|
|
||||||
yield var
|
|
||||||
|
|
||||||
|
|
||||||
@register_effect('addressable_scan', AddressableScanEffect, "Scan", {
|
|
||||||
cv.Optional(CONF_MOVE_INTERVAL, default='0.1s'): cv.positive_time_period_milliseconds,
|
|
||||||
})
|
|
||||||
def addressable_scan_effect_to_code(config, effect_id):
|
|
||||||
var = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
|
||||||
cg.add(var.set_move_interval(config[CONF_MOVE_INTERVAL]))
|
|
||||||
yield var
|
|
||||||
|
|
||||||
|
|
||||||
@register_effect('addressable_twinkle', AddressableTwinkleEffect, "Twinkle", {
|
|
||||||
cv.Optional(CONF_TWINKLE_PROBABILITY, default='5%'): cv.percentage,
|
|
||||||
cv.Optional(CONF_PROGRESS_INTERVAL, default='4ms'): cv.positive_time_period_milliseconds,
|
|
||||||
})
|
|
||||||
def addressable_twinkle_effect_to_code(config, effect_id):
|
|
||||||
var = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
|
||||||
cg.add(var.set_twinkle_probability(config[CONF_TWINKLE_PROBABILITY]))
|
|
||||||
cg.add(var.set_progress_interval(config[CONF_PROGRESS_INTERVAL]))
|
|
||||||
yield var
|
|
||||||
|
|
||||||
|
|
||||||
@register_effect('addressable_random_twinkle', AddressableRandomTwinkleEffect, "Random Twinkle", {
|
|
||||||
cv.Optional(CONF_TWINKLE_PROBABILITY, default='5%'): cv.percentage,
|
|
||||||
cv.Optional(CONF_PROGRESS_INTERVAL, default='32ms'): cv.positive_time_period_milliseconds,
|
|
||||||
})
|
|
||||||
def addressable_random_twinkle_effect_to_code(config, effect_id):
|
|
||||||
var = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
|
||||||
cg.add(var.set_twinkle_probability(config[CONF_TWINKLE_PROBABILITY]))
|
|
||||||
cg.add(var.set_progress_interval(config[CONF_PROGRESS_INTERVAL]))
|
|
||||||
yield var
|
|
||||||
|
|
||||||
|
|
||||||
@register_effect('addressable_fireworks', AddressableFireworksEffect, "Fireworks", {
|
|
||||||
cv.Optional(CONF_UPDATE_INTERVAL, default='32ms'): cv.positive_time_period_milliseconds,
|
|
||||||
cv.Optional(CONF_SPARK_PROBABILITY, default='10%'): cv.percentage,
|
|
||||||
cv.Optional(CONF_USE_RANDOM_COLOR, default=False): cv.boolean,
|
|
||||||
cv.Optional(CONF_FADE_OUT_RATE, default=120): cv.uint8_t,
|
|
||||||
})
|
|
||||||
def addressable_fireworks_effect_to_code(config, effect_id):
|
|
||||||
var = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
|
||||||
cg.add(var.set_update_interval(config[CONF_UPDATE_INTERVAL]))
|
|
||||||
cg.add(var.set_spark_probability(config[CONF_SPARK_PROBABILITY]))
|
|
||||||
cg.add(var.set_use_random_color(config[CONF_USE_RANDOM_COLOR]))
|
|
||||||
cg.add(var.set_fade_out_rate(config[CONF_FADE_OUT_RATE]))
|
|
||||||
yield var
|
|
||||||
|
|
||||||
|
|
||||||
@register_effect('addressable_flicker', AddressableFlickerEffect, "Addressable Flicker", {
|
|
||||||
cv.Optional(CONF_UPDATE_INTERVAL, default='16ms'): cv.positive_time_period_milliseconds,
|
|
||||||
cv.Optional(CONF_INTENSITY, default='5%'): cv.percentage,
|
|
||||||
})
|
|
||||||
def addressable_flicker_effect_to_code(config, effect_id):
|
|
||||||
var = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
|
||||||
cg.add(var.set_update_interval(config[CONF_UPDATE_INTERVAL]))
|
|
||||||
cg.add(var.set_intensity(config[CONF_INTENSITY]))
|
|
||||||
yield var
|
|
||||||
|
|
||||||
|
|
||||||
def validate_effects(allowed_effects):
|
|
||||||
def validator(value):
|
|
||||||
value = cv.validate_registry('effect', EFFECTS_REGISTRY)(value)
|
|
||||||
errors = []
|
|
||||||
names = set()
|
|
||||||
for i, x in enumerate(value):
|
|
||||||
key = next(it for it in x.keys())
|
|
||||||
if key not in allowed_effects:
|
|
||||||
errors.append(
|
|
||||||
cv.Invalid("The effect '{}' is not allowed for this "
|
|
||||||
"light type".format(key), [i])
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
name = x[key][CONF_NAME]
|
|
||||||
if name in names:
|
|
||||||
errors.append(
|
|
||||||
cv.Invalid(u"Found the effect name '{}' twice. All effects must have "
|
|
||||||
u"unique names".format(name), [i])
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
names.add(name)
|
|
||||||
if errors:
|
|
||||||
raise cv.MultipleInvalid(errors)
|
|
||||||
return value
|
|
||||||
|
|
||||||
return validator
|
|
||||||
|
|
||||||
|
|
||||||
LIGHT_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({
|
LIGHT_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({
|
||||||
cv.GenerateID(): cv.declare_id(LightState),
|
cv.GenerateID(): cv.declare_id(LightState),
|
||||||
cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_id(mqtt.MQTTJSONLightComponent),
|
cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_id(mqtt.MQTTJSONLightComponent),
|
||||||
|
@ -344,83 +65,7 @@ def register_light(output_var, config):
|
||||||
yield setup_light_core_(light_var, output_var, config)
|
yield setup_light_core_(light_var, output_var, config)
|
||||||
|
|
||||||
|
|
||||||
@automation.register_action('light.toggle', ToggleAction, maybe_simple_id({
|
@coroutine_with_priority(100.0)
|
||||||
cv.Required(CONF_ID): cv.use_id(LightState),
|
|
||||||
cv.Optional(CONF_TRANSITION_LENGTH): cv.templatable(cv.positive_time_period_milliseconds),
|
|
||||||
}))
|
|
||||||
def light_toggle_to_code(config, action_id, template_arg, args):
|
|
||||||
paren = yield cg.get_variable(config[CONF_ID])
|
|
||||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
|
||||||
if CONF_TRANSITION_LENGTH in config:
|
|
||||||
template_ = yield cg.templatable(config[CONF_TRANSITION_LENGTH], args, cg.uint32)
|
|
||||||
cg.add(var.set_transition_length(template_))
|
|
||||||
yield var
|
|
||||||
|
|
||||||
|
|
||||||
LIGHT_CONTROL_ACTION_SCHEMA = cv.Schema({
|
|
||||||
cv.Required(CONF_ID): cv.use_id(LightState),
|
|
||||||
cv.Optional(CONF_STATE): cv.templatable(cv.boolean),
|
|
||||||
cv.Exclusive(CONF_TRANSITION_LENGTH, 'transformer'):
|
|
||||||
cv.templatable(cv.positive_time_period_milliseconds),
|
|
||||||
cv.Exclusive(CONF_FLASH_LENGTH, 'transformer'):
|
|
||||||
cv.templatable(cv.positive_time_period_milliseconds),
|
|
||||||
cv.Exclusive(CONF_EFFECT, 'transformer'): cv.templatable(cv.string),
|
|
||||||
cv.Optional(CONF_BRIGHTNESS): cv.templatable(cv.percentage),
|
|
||||||
cv.Optional(CONF_RED): cv.templatable(cv.percentage),
|
|
||||||
cv.Optional(CONF_GREEN): cv.templatable(cv.percentage),
|
|
||||||
cv.Optional(CONF_BLUE): cv.templatable(cv.percentage),
|
|
||||||
cv.Optional(CONF_WHITE): cv.templatable(cv.percentage),
|
|
||||||
cv.Optional(CONF_COLOR_TEMPERATURE): cv.templatable(cv.color_temperature),
|
|
||||||
})
|
|
||||||
LIGHT_TURN_OFF_ACTION_SCHEMA = maybe_simple_id({
|
|
||||||
cv.Required(CONF_ID): cv.use_id(LightState),
|
|
||||||
cv.Optional(CONF_TRANSITION_LENGTH): cv.templatable(cv.positive_time_period_milliseconds),
|
|
||||||
cv.Optional(CONF_STATE, default=False): False,
|
|
||||||
})
|
|
||||||
LIGHT_TURN_ON_ACTION_SCHEMA = maybe_simple_id(LIGHT_CONTROL_ACTION_SCHEMA.extend({
|
|
||||||
cv.Optional(CONF_STATE, default=True): True,
|
|
||||||
}))
|
|
||||||
|
|
||||||
|
|
||||||
@automation.register_action('light.turn_off', LightControlAction, LIGHT_TURN_OFF_ACTION_SCHEMA)
|
|
||||||
@automation.register_action('light.turn_on', LightControlAction, LIGHT_TURN_ON_ACTION_SCHEMA)
|
|
||||||
@automation.register_action('light.control', LightControlAction, LIGHT_CONTROL_ACTION_SCHEMA)
|
|
||||||
def light_control_to_code(config, var, template_arg, args):
|
|
||||||
paren = yield cg.get_variable(config[CONF_ID])
|
|
||||||
var = cg.new_Pvariable(var, template_arg, paren)
|
|
||||||
if CONF_STATE in config:
|
|
||||||
template_ = yield cg.templatable(config[CONF_STATE], args, bool)
|
|
||||||
cg.add(var.set_state(template_))
|
|
||||||
if CONF_TRANSITION_LENGTH in config:
|
|
||||||
template_ = yield cg.templatable(config[CONF_TRANSITION_LENGTH], args, cg.uint32)
|
|
||||||
cg.add(var.set_transition_length(template_))
|
|
||||||
if CONF_FLASH_LENGTH in config:
|
|
||||||
template_ = yield cg.templatable(config[CONF_FLASH_LENGTH], args, cg.uint32)
|
|
||||||
cg.add(var.set_flash_length(template_))
|
|
||||||
if CONF_BRIGHTNESS in config:
|
|
||||||
template_ = yield cg.templatable(config[CONF_BRIGHTNESS], args, float)
|
|
||||||
cg.add(var.set_brightness(template_))
|
|
||||||
if CONF_RED in config:
|
|
||||||
template_ = yield cg.templatable(config[CONF_RED], args, float)
|
|
||||||
cg.add(var.set_red(template_))
|
|
||||||
if CONF_GREEN in config:
|
|
||||||
template_ = yield cg.templatable(config[CONF_GREEN], args, float)
|
|
||||||
cg.add(var.set_green(template_))
|
|
||||||
if CONF_BLUE in config:
|
|
||||||
template_ = yield cg.templatable(config[CONF_BLUE], args, float)
|
|
||||||
cg.add(var.set_blue(template_))
|
|
||||||
if CONF_WHITE in config:
|
|
||||||
template_ = yield cg.templatable(config[CONF_WHITE], args, float)
|
|
||||||
cg.add(var.set_white(template_))
|
|
||||||
if CONF_COLOR_TEMPERATURE in config:
|
|
||||||
template_ = yield cg.templatable(config[CONF_COLOR_TEMPERATURE], args, float)
|
|
||||||
cg.add(var.set_color_temperature(template_))
|
|
||||||
if CONF_EFFECT in config:
|
|
||||||
template_ = yield cg.templatable(config[CONF_EFFECT], args, cg.std_string)
|
|
||||||
cg.add(var.set_effect(template_))
|
|
||||||
yield var
|
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
cg.add_define('USE_LIGHT')
|
cg.add_define('USE_LIGHT')
|
||||||
cg.add_global(light_ns.using)
|
cg.add_global(light_ns.using)
|
||||||
|
|
84
esphome/components/light/addressable_light.cpp
Normal file
84
esphome/components/light/addressable_light.cpp
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
#include "addressable_light.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace light {
|
||||||
|
|
||||||
|
ESPColor 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);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESPRangeView::set(const ESPColor &color) {
|
||||||
|
for (int32_t i = this->begin_; i < this->end_; i++) {
|
||||||
|
(*this->parent_)[i] = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ESPColorView ESPRangeView::operator[](int32_t index) const { return (*this->parent_)[index]; }
|
||||||
|
|
||||||
|
ESPColorView ESPRangeView::Iterator::operator*() const { return (*this->range_->parent_)[this->i_]; }
|
||||||
|
|
||||||
|
} // namespace light
|
||||||
|
} // namespace esphome
|
|
@ -143,6 +143,10 @@ struct ESPColor {
|
||||||
return ESPColor(uint8_t((uint16_t(r) * 255U / max_rgb)), uint8_t((uint16_t(g) * 255U / max_rgb)),
|
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);
|
uint8_t((uint16_t(b) * 255U / max_rgb)), w);
|
||||||
}
|
}
|
||||||
|
ESPColor fade_to_white(uint8_t amnt) { return ESPColor(255, 255, 255, 255) - (*this * amnt); }
|
||||||
|
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; }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ESPHSVColor {
|
struct ESPHSVColor {
|
||||||
|
@ -168,72 +172,7 @@ struct ESPHSVColor {
|
||||||
inline ESPHSVColor(uint8_t hue, uint8_t saturation, uint8_t value) ALWAYS_INLINE : hue(hue),
|
inline ESPHSVColor(uint8_t hue, uint8_t saturation, uint8_t value) ALWAYS_INLINE : hue(hue),
|
||||||
saturation(saturation),
|
saturation(saturation),
|
||||||
value(value) {}
|
value(value) {}
|
||||||
ESPColor to_rgb() const {
|
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 {
|
class ESPColorCorrection {
|
||||||
|
@ -321,75 +260,85 @@ class ESPColorCorrection {
|
||||||
uint8_t local_brightness_{255};
|
uint8_t local_brightness_{255};
|
||||||
};
|
};
|
||||||
|
|
||||||
class ESPColorView {
|
class ESPColorSettable {
|
||||||
public:
|
public:
|
||||||
inline ESPColorView(uint8_t *red, uint8_t *green, uint8_t *blue, uint8_t *white, uint8_t *effect_data,
|
virtual void set(const ESPColor &color) = 0;
|
||||||
const ESPColorCorrection *color_correction) ALWAYS_INLINE : red_(red),
|
virtual void set_red(uint8_t red) = 0;
|
||||||
green_(green),
|
virtual void set_green(uint8_t green) = 0;
|
||||||
blue_(blue),
|
virtual void set_blue(uint8_t blue) = 0;
|
||||||
white_(white),
|
virtual void set_white(uint8_t white) = 0;
|
||||||
effect_data_(effect_data),
|
virtual void set_effect_data(uint8_t effect_data) = 0;
|
||||||
color_correction_(color_correction) {}
|
virtual void fade_to_white(uint8_t amnt) = 0;
|
||||||
inline const ESPColorView &operator=(const ESPColor &rhs) const ALWAYS_INLINE {
|
virtual void fade_to_black(uint8_t amnt) = 0;
|
||||||
this->set(rhs);
|
virtual void lighten(uint8_t delta) = 0;
|
||||||
return *this;
|
virtual void darken(uint8_t delta) = 0;
|
||||||
}
|
void set(const ESPHSVColor &color) { this->set_hsv(color); }
|
||||||
inline const ESPColorView &operator=(const ESPHSVColor &rhs) const ALWAYS_INLINE {
|
void set_hsv(const ESPHSVColor &color) {
|
||||||
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();
|
ESPColor rgb = color.to_rgb();
|
||||||
this->set_rgb(rgb.r, rgb.g, rgb.b);
|
this->set_rgb(rgb.r, rgb.g, rgb.b);
|
||||||
}
|
}
|
||||||
inline void set_red(uint8_t red) const ALWAYS_INLINE {
|
void set_rgb(uint8_t red, uint8_t green, uint8_t blue) {
|
||||||
*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_red(red);
|
||||||
this->set_green(green);
|
this->set_green(green);
|
||||||
this->set_blue(blue);
|
this->set_blue(blue);
|
||||||
}
|
}
|
||||||
inline void set_rgbw(uint8_t red, uint8_t green, uint8_t blue, uint8_t white) const ALWAYS_INLINE {
|
void set_rgbw(uint8_t red, uint8_t green, uint8_t blue, uint8_t white) {
|
||||||
this->set_rgb(red, green, blue);
|
this->set_rgb(red, green, blue);
|
||||||
this->set_white(white);
|
this->set_white(white);
|
||||||
}
|
}
|
||||||
inline void set_effect_data(uint8_t effect_data) const ALWAYS_INLINE {
|
};
|
||||||
|
|
||||||
|
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 ESPColor &rhs) {
|
||||||
|
this->set(rhs);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ESPColorView &operator=(const ESPHSVColor &rhs) {
|
||||||
|
this->set_hsv(rhs);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
void set(const ESPColor &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)
|
if (this->effect_data_ == nullptr)
|
||||||
return;
|
return;
|
||||||
*this->effect_data_ = effect_data;
|
*this->effect_data_ = effect_data;
|
||||||
}
|
}
|
||||||
inline ESPColor get() const ALWAYS_INLINE {
|
void fade_to_white(uint8_t amnt) override { this->set(this->get().fade_to_white(amnt)); }
|
||||||
return ESPColor(this->get_red(), this->get_green(), this->get_blue(), this->get_white());
|
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)); }
|
||||||
inline uint8_t get_red() const ALWAYS_INLINE { return this->color_correction_->color_uncorrect_red(*this->red_); }
|
void darken(uint8_t delta) override { this->set(this->get().darken(delta)); }
|
||||||
inline uint8_t get_green() const ALWAYS_INLINE {
|
ESPColor get() const { return ESPColor(this->get_red(), this->get_green(), this->get_blue(), this->get_white()); }
|
||||||
return this->color_correction_->color_uncorrect_green(*this->green_);
|
uint8_t get_red() const { return this->color_correction_->color_uncorrect_red(*this->red_); }
|
||||||
}
|
uint8_t get_green() const { 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_); }
|
uint8_t get_blue() const { return this->color_correction_->color_uncorrect_blue(*this->blue_); }
|
||||||
inline uint8_t get_white() const ALWAYS_INLINE {
|
uint8_t get_white() const {
|
||||||
if (this->white_ == nullptr)
|
if (this->white_ == nullptr)
|
||||||
return 0;
|
return 0;
|
||||||
return this->color_correction_->color_uncorrect_white(*this->white_);
|
return this->color_correction_->color_uncorrect_white(*this->white_);
|
||||||
}
|
}
|
||||||
inline uint8_t get_effect_data() const ALWAYS_INLINE {
|
uint8_t get_effect_data() const {
|
||||||
if (this->effect_data_ == nullptr)
|
if (this->effect_data_ == nullptr)
|
||||||
return 0;
|
return 0;
|
||||||
return *this->effect_data_;
|
return *this->effect_data_;
|
||||||
}
|
}
|
||||||
inline void raw_set_color_correction(const ESPColorCorrection *color_correction) ALWAYS_INLINE {
|
void raw_set_color_correction(const ESPColorCorrection *color_correction) {
|
||||||
this->color_correction_ = color_correction;
|
this->color_correction_ = color_correction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -402,11 +351,142 @@ class ESPColorView {
|
||||||
const ESPColorCorrection *color_correction_;
|
const ESPColorCorrection *color_correction_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class AddressableLight;
|
||||||
|
|
||||||
|
class ESPRangeView : public ESPColorSettable {
|
||||||
|
public:
|
||||||
|
class Iterator {
|
||||||
|
public:
|
||||||
|
Iterator(ESPRangeView *range, int32_t i) : range_(range), i_(i) {}
|
||||||
|
Iterator operator++() {
|
||||||
|
this->i_++;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
bool operator!=(const Iterator &other) const { return this->i_ != other.i_; }
|
||||||
|
ESPColorView operator*() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
ESPRangeView *range_;
|
||||||
|
int32_t i_;
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
Iterator begin() { return {this, this->begin_}; }
|
||||||
|
Iterator end() { return {this, this->end_}; }
|
||||||
|
|
||||||
|
void set(const ESPColor &color) override;
|
||||||
|
ESPRangeView &operator=(const ESPColor &rhs) {
|
||||||
|
this->set(rhs);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ESPRangeView &operator=(const ESPHSVColor &rhs) {
|
||||||
|
this->set_hsv(rhs);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ESPRangeView &operator=(const ESPRangeView &rhs) {
|
||||||
|
// 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 into rhs
|
||||||
|
for (int32_t i = 0; i < this->size(); i++)
|
||||||
|
rhs[i].set((*this)[i].get());
|
||||||
|
} else {
|
||||||
|
// Copy into this
|
||||||
|
for (int32_t i = 0; i < this->size(); i++)
|
||||||
|
(*this)[i].set(rhs[i].get());
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
void set_red(uint8_t red) override {
|
||||||
|
for (auto c : *this)
|
||||||
|
c.set_red(red);
|
||||||
|
}
|
||||||
|
void set_green(uint8_t green) override {
|
||||||
|
for (auto c : *this)
|
||||||
|
c.set_green(green);
|
||||||
|
}
|
||||||
|
void set_blue(uint8_t blue) override {
|
||||||
|
for (auto c : *this)
|
||||||
|
c.set_blue(blue);
|
||||||
|
}
|
||||||
|
void set_white(uint8_t white) override {
|
||||||
|
for (auto c : *this)
|
||||||
|
c.set_white(white);
|
||||||
|
}
|
||||||
|
void set_effect_data(uint8_t effect_data) override {
|
||||||
|
for (auto c : *this)
|
||||||
|
c.set_effect_data(effect_data);
|
||||||
|
}
|
||||||
|
void fade_to_white(uint8_t amnt) override {
|
||||||
|
for (auto c : *this)
|
||||||
|
c.fade_to_white(amnt);
|
||||||
|
}
|
||||||
|
void fade_to_black(uint8_t amnt) override {
|
||||||
|
for (auto c : *this)
|
||||||
|
c.fade_to_white(amnt);
|
||||||
|
}
|
||||||
|
void lighten(uint8_t delta) override {
|
||||||
|
for (auto c : *this)
|
||||||
|
c.lighten(delta);
|
||||||
|
}
|
||||||
|
void darken(uint8_t delta) override {
|
||||||
|
for (auto c : *this)
|
||||||
|
c.darken(delta);
|
||||||
|
}
|
||||||
|
int32_t size() const { return this->end_ - this->begin_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
AddressableLight *parent_;
|
||||||
|
int32_t begin_;
|
||||||
|
int32_t end_;
|
||||||
|
};
|
||||||
|
|
||||||
class AddressableLight : public LightOutput {
|
class AddressableLight : public LightOutput {
|
||||||
public:
|
public:
|
||||||
virtual int32_t size() const = 0;
|
virtual int32_t size() const = 0;
|
||||||
virtual ESPColorView operator[](int32_t index) const = 0;
|
virtual ESPColorView operator[](int32_t index) const = 0;
|
||||||
virtual void clear_effect_data() = 0;
|
virtual void clear_effect_data() = 0;
|
||||||
|
ESPRangeView range(int32_t from, int32_t to) { 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(); }
|
||||||
|
void shift_left(int32_t amnt) {
|
||||||
|
if (amnt < 0) {
|
||||||
|
this->shift_right(-amnt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (amnt > this->size())
|
||||||
|
amnt = this->size();
|
||||||
|
this->range(0, this->size() - amnt) = this->range(amnt, this->size());
|
||||||
|
}
|
||||||
|
void shift_right(int32_t amnt) {
|
||||||
|
if (amnt < 0) {
|
||||||
|
this->shift_left(-amnt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (amnt > this->size())
|
||||||
|
amnt = this->size();
|
||||||
|
this->range(amnt, this->size()) = this->range(0, this->size() - amnt);
|
||||||
|
}
|
||||||
bool is_effect_active() const { return this->effect_active_; }
|
bool is_effect_active() const { return this->effect_active_; }
|
||||||
void set_effect_active(bool effect_active) { this->effect_active_ = effect_active; }
|
void set_effect_active(bool effect_active) { this->effect_active_ = effect_active; }
|
||||||
void write_state(LightState *state) override {
|
void write_state(LightState *state) override {
|
||||||
|
@ -423,10 +503,7 @@ class AddressableLight : public LightOutput {
|
||||||
// white is not affected by brightness; so manually scale by state
|
// white is not affected by brightness; so manually scale by state
|
||||||
uint8_t(roundf(val.get_white() * val.get_state() * 255.0f)));
|
uint8_t(roundf(val.get_white() * val.get_state() * 255.0f)));
|
||||||
|
|
||||||
for (int i = 0; i < this->size(); i++) {
|
this->all() = color;
|
||||||
(*this)[i] = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->schedule_show();
|
this->schedule_show();
|
||||||
}
|
}
|
||||||
void set_correction(float red, float green, float blue, float white = 1.0f) {
|
void set_correction(float red, float green, float blue, float white = 1.0f) {
|
||||||
|
|
|
@ -76,9 +76,9 @@ class AddressableRainbowLightEffect : public AddressableLightEffect {
|
||||||
hsv.saturation = 240;
|
hsv.saturation = 240;
|
||||||
uint16_t hue = (millis() * this->speed_) % 0xFFFF;
|
uint16_t hue = (millis() * this->speed_) % 0xFFFF;
|
||||||
const uint16_t add = 0xFFFF / this->width_;
|
const uint16_t add = 0xFFFF / this->width_;
|
||||||
for (int i = 0; i < it.size(); i++) {
|
for (auto var : it) {
|
||||||
hsv.hue = hue >> 8;
|
hsv.hue = hue >> 8;
|
||||||
it[i] = hsv;
|
var = hsv;
|
||||||
hue += add;
|
hue += add;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,15 +107,10 @@ class AddressableColorWipeEffect : public AddressableLightEffect {
|
||||||
if (now - this->last_add_ < this->add_led_interval_)
|
if (now - this->last_add_ < this->add_led_interval_)
|
||||||
return;
|
return;
|
||||||
this->last_add_ = now;
|
this->last_add_ = now;
|
||||||
if (!this->reverse_) {
|
if (this->reverse_)
|
||||||
for (int i = 0; i < it.size() - 1; i++) {
|
it.shift_left(1);
|
||||||
it[i] = it[i + 1].get();
|
else
|
||||||
}
|
it.shift_right(1);
|
||||||
} else {
|
|
||||||
for (int i = it.size() - 1; i > 0; i--) {
|
|
||||||
it[i] = it[i - 1].get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const AddressableColorWipeEffectColor color = this->colors_[this->at_color_];
|
const AddressableColorWipeEffectColor color = this->colors_[this->at_color_];
|
||||||
const ESPColor esp_color = ESPColor(color.r, color.g, color.b, color.w);
|
const ESPColor esp_color = ESPColor(color.r, color.g, color.b, color.w);
|
||||||
if (!this->reverse_) {
|
if (!this->reverse_) {
|
||||||
|
@ -149,18 +144,14 @@ class AddressableScanEffect : public AddressableLightEffect {
|
||||||
public:
|
public:
|
||||||
explicit AddressableScanEffect(const std::string &name) : AddressableLightEffect(name) {}
|
explicit AddressableScanEffect(const std::string &name) : AddressableLightEffect(name) {}
|
||||||
void set_move_interval(uint32_t move_interval) { this->move_interval_ = move_interval; }
|
void set_move_interval(uint32_t move_interval) { this->move_interval_ = move_interval; }
|
||||||
void apply(AddressableLight &addressable, const ESPColor ¤t_color) override {
|
void apply(AddressableLight &it, const ESPColor ¤t_color) override {
|
||||||
for (int i = 0; i < addressable.size(); i++) {
|
it.all() = ESPColor(0, 0, 0, 0);
|
||||||
if (i == this->at_led_)
|
it[this->at_led_] = current_color;
|
||||||
addressable[i] = current_color;
|
|
||||||
else
|
|
||||||
addressable[i] = ESPColor(0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
const uint32_t now = millis();
|
const uint32_t now = millis();
|
||||||
if (now - this->last_move_ > this->move_interval_) {
|
if (now - this->last_move_ > this->move_interval_) {
|
||||||
if (direction_) {
|
if (direction_) {
|
||||||
this->at_led_++;
|
this->at_led_++;
|
||||||
if (this->at_led_ == addressable.size() - 1)
|
if (this->at_led_ == it.size() - 1)
|
||||||
this->direction_ = false;
|
this->direction_ = false;
|
||||||
} else {
|
} else {
|
||||||
this->at_led_--;
|
this->at_led_--;
|
||||||
|
@ -189,8 +180,7 @@ class AddressableTwinkleEffect : public AddressableLightEffect {
|
||||||
pos_add = pos_add32;
|
pos_add = pos_add32;
|
||||||
this->last_progress_ += pos_add32 * this->progress_interval_;
|
this->last_progress_ += pos_add32 * this->progress_interval_;
|
||||||
}
|
}
|
||||||
for (int i = 0; i < addressable.size(); i++) {
|
for (auto view : addressable) {
|
||||||
ESPColorView view = addressable[i];
|
|
||||||
if (view.get_effect_data() != 0) {
|
if (view.get_effect_data() != 0) {
|
||||||
const uint8_t sine = half_sin8(view.get_effect_data());
|
const uint8_t sine = half_sin8(view.get_effect_data());
|
||||||
view = current_color * sine;
|
view = current_color * sine;
|
||||||
|
@ -230,8 +220,8 @@ class AddressableRandomTwinkleEffect : public AddressableLightEffect {
|
||||||
this->last_progress_ = now;
|
this->last_progress_ = now;
|
||||||
}
|
}
|
||||||
uint8_t subsine = ((8 * (now - this->last_progress_)) / this->progress_interval_) & 0b111;
|
uint8_t subsine = ((8 * (now - this->last_progress_)) / this->progress_interval_) & 0b111;
|
||||||
for (int i = 0; i < it.size(); i++) {
|
for (auto &&i : it) {
|
||||||
ESPColorView view = it[i];
|
ESPColorView view = i;
|
||||||
if (view.get_effect_data() != 0) {
|
if (view.get_effect_data() != 0) {
|
||||||
const uint8_t x = (view.get_effect_data() >> 3) & 0b11111;
|
const uint8_t x = (view.get_effect_data() >> 3) & 0b11111;
|
||||||
const uint8_t color = view.get_effect_data() & 0b111;
|
const uint8_t color = view.get_effect_data() & 0b111;
|
||||||
|
@ -282,11 +272,11 @@ class AddressableFireworksEffect : public AddressableLightEffect {
|
||||||
this->last_update_ = now;
|
this->last_update_ = now;
|
||||||
// "invert" the fade out parameter so that higher values make fade out faster
|
// "invert" the fade out parameter so that higher values make fade out faster
|
||||||
const uint8_t fade_out_mult = 255u - this->fade_out_rate_;
|
const uint8_t fade_out_mult = 255u - this->fade_out_rate_;
|
||||||
for (int i = 0; i < it.size(); i++) {
|
for (auto view : it) {
|
||||||
ESPColor target = it[i].get() * fade_out_mult;
|
ESPColor target = view.get() * fade_out_mult;
|
||||||
if (target.r < 64)
|
if (target.r < 64)
|
||||||
target *= 170;
|
target *= 170;
|
||||||
it[i] = target;
|
view = target;
|
||||||
}
|
}
|
||||||
int last = it.size() - 1;
|
int last = it.size() - 1;
|
||||||
it[0].set(it[0].get() + (it[1].get() * 128));
|
it[0].set(it[0].get() + (it[1].get() * 128));
|
||||||
|
@ -326,9 +316,9 @@ class AddressableFlickerEffect : public AddressableLightEffect {
|
||||||
return;
|
return;
|
||||||
this->last_update_ = now;
|
this->last_update_ = now;
|
||||||
fast_random_set_seed(random_uint32());
|
fast_random_set_seed(random_uint32());
|
||||||
for (int i = 0; i < it.size(); i++) {
|
for (auto var : it) {
|
||||||
const uint8_t flicker = fast_random_8() % this->intensity_;
|
const uint8_t flicker = fast_random_8() % this->intensity_;
|
||||||
it[i] = (it[i].get() * delta_intensity) + (current_color * flicker);
|
var = (var.get() * delta_intensity) + (current_color * flicker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }
|
void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }
|
||||||
|
|
|
@ -56,6 +56,30 @@ template<typename... Ts> class LightControlAction : public Action<Ts...> {
|
||||||
LightState *parent_;
|
LightState *parent_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class DimRelativeAction : public Action<Ts...> {
|
||||||
|
public:
|
||||||
|
explicit DimRelativeAction(LightState *parent) : parent_(parent) {}
|
||||||
|
|
||||||
|
TEMPLATABLE_VALUE(float, relative_brightness)
|
||||||
|
TEMPLATABLE_VALUE(uint32_t, transition_length)
|
||||||
|
|
||||||
|
void play(Ts... x) override {
|
||||||
|
auto call = this->parent_->make_call();
|
||||||
|
float rel = this->relative_brightness_.value(x...);
|
||||||
|
float cur;
|
||||||
|
this->parent_->remote_values.as_brightness(&cur);
|
||||||
|
float new_brightness = clamp(cur + rel, 0.0f, 1.0f);
|
||||||
|
call.set_state(new_brightness != 0.0f);
|
||||||
|
call.set_brightness(new_brightness);
|
||||||
|
|
||||||
|
call.set_transition_length(this->transition_length_.optional_value(x...));
|
||||||
|
call.perform();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
LightState *parent_;
|
||||||
|
};
|
||||||
|
|
||||||
template<typename... Ts> class LightIsOnCondition : public Condition<Ts...> {
|
template<typename... Ts> class LightIsOnCondition : public Condition<Ts...> {
|
||||||
public:
|
public:
|
||||||
explicit LightIsOnCondition(LightState *state) : state_(state) {}
|
explicit LightIsOnCondition(LightState *state) : state_(state) {}
|
||||||
|
|
105
esphome/components/light/automation.py
Normal file
105
esphome/components/light/automation.py
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome import automation
|
||||||
|
from esphome.const import CONF_ID, CONF_TRANSITION_LENGTH, CONF_STATE, CONF_FLASH_LENGTH, \
|
||||||
|
CONF_EFFECT, CONF_BRIGHTNESS, CONF_RED, CONF_GREEN, CONF_BLUE, CONF_WHITE, \
|
||||||
|
CONF_COLOR_TEMPERATURE
|
||||||
|
from .types import DimRelativeAction, ToggleAction, LightState, LightControlAction
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action('light.toggle', ToggleAction, automation.maybe_simple_id({
|
||||||
|
cv.Required(CONF_ID): cv.use_id(LightState),
|
||||||
|
cv.Optional(CONF_TRANSITION_LENGTH): cv.templatable(cv.positive_time_period_milliseconds),
|
||||||
|
}))
|
||||||
|
def light_toggle_to_code(config, action_id, template_arg, args):
|
||||||
|
paren = yield cg.get_variable(config[CONF_ID])
|
||||||
|
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
|
if CONF_TRANSITION_LENGTH in config:
|
||||||
|
template_ = yield cg.templatable(config[CONF_TRANSITION_LENGTH], args, cg.uint32)
|
||||||
|
cg.add(var.set_transition_length(template_))
|
||||||
|
yield var
|
||||||
|
|
||||||
|
|
||||||
|
LIGHT_CONTROL_ACTION_SCHEMA = cv.Schema({
|
||||||
|
cv.Required(CONF_ID): cv.use_id(LightState),
|
||||||
|
cv.Optional(CONF_STATE): cv.templatable(cv.boolean),
|
||||||
|
cv.Exclusive(CONF_TRANSITION_LENGTH, 'transformer'):
|
||||||
|
cv.templatable(cv.positive_time_period_milliseconds),
|
||||||
|
cv.Exclusive(CONF_FLASH_LENGTH, 'transformer'):
|
||||||
|
cv.templatable(cv.positive_time_period_milliseconds),
|
||||||
|
cv.Exclusive(CONF_EFFECT, 'transformer'): cv.templatable(cv.string),
|
||||||
|
cv.Optional(CONF_BRIGHTNESS): cv.templatable(cv.percentage),
|
||||||
|
cv.Optional(CONF_RED): cv.templatable(cv.percentage),
|
||||||
|
cv.Optional(CONF_GREEN): cv.templatable(cv.percentage),
|
||||||
|
cv.Optional(CONF_BLUE): cv.templatable(cv.percentage),
|
||||||
|
cv.Optional(CONF_WHITE): cv.templatable(cv.percentage),
|
||||||
|
cv.Optional(CONF_COLOR_TEMPERATURE): cv.templatable(cv.color_temperature),
|
||||||
|
})
|
||||||
|
LIGHT_TURN_OFF_ACTION_SCHEMA = automation.maybe_simple_id({
|
||||||
|
cv.Required(CONF_ID): cv.use_id(LightState),
|
||||||
|
cv.Optional(CONF_TRANSITION_LENGTH): cv.templatable(cv.positive_time_period_milliseconds),
|
||||||
|
cv.Optional(CONF_STATE, default=False): False,
|
||||||
|
})
|
||||||
|
LIGHT_TURN_ON_ACTION_SCHEMA = automation.maybe_simple_id(LIGHT_CONTROL_ACTION_SCHEMA.extend({
|
||||||
|
cv.Optional(CONF_STATE, default=True): True,
|
||||||
|
}))
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action('light.turn_off', LightControlAction, LIGHT_TURN_OFF_ACTION_SCHEMA)
|
||||||
|
@automation.register_action('light.turn_on', LightControlAction, LIGHT_TURN_ON_ACTION_SCHEMA)
|
||||||
|
@automation.register_action('light.control', LightControlAction, LIGHT_CONTROL_ACTION_SCHEMA)
|
||||||
|
def light_control_to_code(config, action_id, template_arg, args):
|
||||||
|
paren = yield cg.get_variable(config[CONF_ID])
|
||||||
|
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
|
if CONF_STATE in config:
|
||||||
|
template_ = yield cg.templatable(config[CONF_STATE], args, bool)
|
||||||
|
cg.add(var.set_state(template_))
|
||||||
|
if CONF_TRANSITION_LENGTH in config:
|
||||||
|
template_ = yield cg.templatable(config[CONF_TRANSITION_LENGTH], args, cg.uint32)
|
||||||
|
cg.add(var.set_transition_length(template_))
|
||||||
|
if CONF_FLASH_LENGTH in config:
|
||||||
|
template_ = yield cg.templatable(config[CONF_FLASH_LENGTH], args, cg.uint32)
|
||||||
|
cg.add(var.set_flash_length(template_))
|
||||||
|
if CONF_BRIGHTNESS in config:
|
||||||
|
template_ = yield cg.templatable(config[CONF_BRIGHTNESS], args, float)
|
||||||
|
cg.add(var.set_brightness(template_))
|
||||||
|
if CONF_RED in config:
|
||||||
|
template_ = yield cg.templatable(config[CONF_RED], args, float)
|
||||||
|
cg.add(var.set_red(template_))
|
||||||
|
if CONF_GREEN in config:
|
||||||
|
template_ = yield cg.templatable(config[CONF_GREEN], args, float)
|
||||||
|
cg.add(var.set_green(template_))
|
||||||
|
if CONF_BLUE in config:
|
||||||
|
template_ = yield cg.templatable(config[CONF_BLUE], args, float)
|
||||||
|
cg.add(var.set_blue(template_))
|
||||||
|
if CONF_WHITE in config:
|
||||||
|
template_ = yield cg.templatable(config[CONF_WHITE], args, float)
|
||||||
|
cg.add(var.set_white(template_))
|
||||||
|
if CONF_COLOR_TEMPERATURE in config:
|
||||||
|
template_ = yield cg.templatable(config[CONF_COLOR_TEMPERATURE], args, float)
|
||||||
|
cg.add(var.set_color_temperature(template_))
|
||||||
|
if CONF_EFFECT in config:
|
||||||
|
template_ = yield cg.templatable(config[CONF_EFFECT], args, cg.std_string)
|
||||||
|
cg.add(var.set_effect(template_))
|
||||||
|
yield var
|
||||||
|
|
||||||
|
|
||||||
|
CONF_RELATIVE_BRIGHTNESS = 'relative_brightness'
|
||||||
|
LIGHT_DIM_RELATIVE_ACTION_SCHEMA = cv.Schema({
|
||||||
|
cv.Required(CONF_ID): cv.use_id(LightState),
|
||||||
|
cv.Required(CONF_RELATIVE_BRIGHTNESS): cv.templatable(cv.percentage),
|
||||||
|
cv.Optional(CONF_TRANSITION_LENGTH): cv.templatable(cv.positive_time_period_milliseconds),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action('light.dim_relative', DimRelativeAction,
|
||||||
|
LIGHT_DIM_RELATIVE_ACTION_SCHEMA)
|
||||||
|
def light_dim_relative_to_code(config, action_id, template_arg, args):
|
||||||
|
paren = yield cg.get_variable(config[CONF_ID])
|
||||||
|
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
|
templ = yield cg.templatable(config[CONF_RELATIVE_BRIGHTNESS], args, float)
|
||||||
|
cg.add(var.set_relative_brightness(templ))
|
||||||
|
if CONF_TRANSITION_LENGTH in config:
|
||||||
|
templ = yield cg.templatable(config[CONF_TRANSITION_LENGTH], args, cg.uint32)
|
||||||
|
cg.add(var.set_transition_length(templ))
|
||||||
|
yield var
|
249
esphome/components/light/effects.py
Normal file
249
esphome/components/light/effects.py
Normal file
|
@ -0,0 +1,249 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
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
|
||||||
|
from esphome.util import Registry
|
||||||
|
from .types import LambdaLightEffect, RandomLightEffect, StrobeLightEffect, \
|
||||||
|
StrobeLightEffectColor, LightColorValues, AddressableLightRef, AddressableLambdaLightEffect, \
|
||||||
|
FlickerLightEffect, AddressableRainbowLightEffect, AddressableColorWipeEffect, \
|
||||||
|
AddressableColorWipeEffectColor, AddressableScanEffect, AddressableTwinkleEffect, \
|
||||||
|
AddressableRandomTwinkleEffect, AddressableFireworksEffect, AddressableFlickerEffect
|
||||||
|
|
||||||
|
CONF_ADD_LED_INTERVAL = 'add_led_interval'
|
||||||
|
CONF_REVERSE = 'reverse'
|
||||||
|
CONF_MOVE_INTERVAL = 'move_interval'
|
||||||
|
CONF_TWINKLE_PROBABILITY = 'twinkle_probability'
|
||||||
|
CONF_PROGRESS_INTERVAL = 'progress_interval'
|
||||||
|
CONF_SPARK_PROBABILITY = 'spark_probability'
|
||||||
|
CONF_USE_RANDOM_COLOR = 'use_random_color'
|
||||||
|
CONF_FADE_OUT_RATE = 'fade_out_rate'
|
||||||
|
CONF_STROBE = 'strobe'
|
||||||
|
CONF_FLICKER = 'flicker'
|
||||||
|
CONF_ADDRESSABLE_LAMBDA = 'addressable_lambda'
|
||||||
|
CONF_ADDRESSABLE_RAINBOW = 'addressable_rainbow'
|
||||||
|
CONF_ADDRESSABLE_COLOR_WIPE = 'addressable_color_wipe'
|
||||||
|
CONF_ADDRESSABLE_SCAN = 'addressable_scan'
|
||||||
|
CONF_ADDRESSABLE_TWINKLE = 'addressable_twinkle'
|
||||||
|
CONF_ADDRESSABLE_RANDOM_TWINKLE = 'addressable_random_twinkle'
|
||||||
|
CONF_ADDRESSABLE_FIREWORKS = 'addressable_fireworks'
|
||||||
|
CONF_ADDRESSABLE_FLICKER = 'addressable_flicker'
|
||||||
|
|
||||||
|
BINARY_EFFECTS = ['lambda', 'strobe']
|
||||||
|
MONOCHROMATIC_EFFECTS = BINARY_EFFECTS + ['flicker']
|
||||||
|
RGB_EFFECTS = MONOCHROMATIC_EFFECTS + ['random']
|
||||||
|
ADDRESSABLE_EFFECTS = RGB_EFFECTS + [CONF_ADDRESSABLE_LAMBDA, CONF_ADDRESSABLE_RAINBOW,
|
||||||
|
CONF_ADDRESSABLE_COLOR_WIPE, CONF_ADDRESSABLE_SCAN,
|
||||||
|
CONF_ADDRESSABLE_TWINKLE, CONF_ADDRESSABLE_RANDOM_TWINKLE,
|
||||||
|
CONF_ADDRESSABLE_FIREWORKS, CONF_ADDRESSABLE_FLICKER]
|
||||||
|
|
||||||
|
EFFECTS_REGISTRY = Registry()
|
||||||
|
|
||||||
|
|
||||||
|
def register_effect(name, effect_type, default_name, schema, *extra_validators):
|
||||||
|
schema = cv.Schema(schema).extend({
|
||||||
|
cv.Optional(CONF_NAME, default=default_name): cv.string_strict,
|
||||||
|
})
|
||||||
|
validator = cv.All(schema, *extra_validators)
|
||||||
|
return EFFECTS_REGISTRY.register(name, effect_type, validator)
|
||||||
|
|
||||||
|
|
||||||
|
@register_effect('lambda', LambdaLightEffect, "Lambda", {
|
||||||
|
cv.Required(CONF_LAMBDA): cv.lambda_,
|
||||||
|
cv.Optional(CONF_UPDATE_INTERVAL, default='0ms'): cv.update_interval,
|
||||||
|
})
|
||||||
|
def lambda_effect_to_code(config, effect_id):
|
||||||
|
lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], [], return_type=cg.void)
|
||||||
|
yield cg.new_Pvariable(effect_id, config[CONF_NAME], lambda_,
|
||||||
|
config[CONF_UPDATE_INTERVAL])
|
||||||
|
|
||||||
|
|
||||||
|
@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,
|
||||||
|
})
|
||||||
|
def random_effect_to_code(config, effect_id):
|
||||||
|
effect = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
||||||
|
cg.add(effect.set_transition_length(config[CONF_TRANSITION_LENGTH]))
|
||||||
|
cg.add(effect.set_update_interval(config[CONF_UPDATE_INTERVAL]))
|
||||||
|
yield effect
|
||||||
|
|
||||||
|
|
||||||
|
@register_effect('strobe', StrobeLightEffect, "Strobe", {
|
||||||
|
cv.Optional(CONF_COLORS, default=[
|
||||||
|
{CONF_STATE: True, CONF_DURATION: '0.5s'},
|
||||||
|
{CONF_STATE: False, CONF_DURATION: '0.5s'},
|
||||||
|
]): cv.All(cv.ensure_list(cv.Schema({
|
||||||
|
cv.Optional(CONF_STATE, default=True): cv.boolean,
|
||||||
|
cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage,
|
||||||
|
cv.Optional(CONF_RED, default=1.0): cv.percentage,
|
||||||
|
cv.Optional(CONF_GREEN, default=1.0): cv.percentage,
|
||||||
|
cv.Optional(CONF_BLUE, default=1.0): cv.percentage,
|
||||||
|
cv.Optional(CONF_WHITE, default=1.0): cv.percentage,
|
||||||
|
cv.Required(CONF_DURATION): cv.positive_time_period_milliseconds,
|
||||||
|
}), cv.has_at_least_one_key(CONF_STATE, CONF_BRIGHTNESS, CONF_RED, CONF_GREEN, CONF_BLUE,
|
||||||
|
CONF_WHITE)), cv.Length(min=2)),
|
||||||
|
})
|
||||||
|
def strobe_effect_to_code(config, effect_id):
|
||||||
|
var = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
||||||
|
colors = []
|
||||||
|
for color in config.get(CONF_COLORS, []):
|
||||||
|
colors.append(cg.StructInitializer(
|
||||||
|
StrobeLightEffectColor,
|
||||||
|
('color', LightColorValues(color[CONF_STATE], color[CONF_BRIGHTNESS],
|
||||||
|
color[CONF_RED], color[CONF_GREEN], color[CONF_BLUE],
|
||||||
|
color[CONF_WHITE])),
|
||||||
|
('duration', color[CONF_DURATION]),
|
||||||
|
))
|
||||||
|
cg.add(var.set_colors(colors))
|
||||||
|
yield var
|
||||||
|
|
||||||
|
|
||||||
|
@register_effect('flicker', FlickerLightEffect, "Flicker", {
|
||||||
|
cv.Optional(CONF_ALPHA, default=0.95): cv.percentage,
|
||||||
|
cv.Optional(CONF_INTENSITY, default=0.015): cv.percentage,
|
||||||
|
})
|
||||||
|
def flicker_effect_to_code(config, effect_id):
|
||||||
|
var = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
||||||
|
cg.add(var.set_alpha(config[CONF_ALPHA]))
|
||||||
|
cg.add(var.set_intensity(config[CONF_INTENSITY]))
|
||||||
|
yield var
|
||||||
|
|
||||||
|
|
||||||
|
@register_effect('addressable_lambda', AddressableLambdaLightEffect, "Addressable Lambda", {
|
||||||
|
cv.Required(CONF_LAMBDA): cv.lambda_,
|
||||||
|
cv.Optional(CONF_UPDATE_INTERVAL, default='0ms'): cv.positive_time_period_milliseconds,
|
||||||
|
})
|
||||||
|
def addressable_lambda_effect_to_code(config, effect_id):
|
||||||
|
args = [(AddressableLightRef, 'it')]
|
||||||
|
lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], args, return_type=cg.void)
|
||||||
|
var = cg.new_Pvariable(effect_id, config[CONF_NAME], lambda_,
|
||||||
|
config[CONF_UPDATE_INTERVAL])
|
||||||
|
yield var
|
||||||
|
|
||||||
|
|
||||||
|
@register_effect('addressable_rainbow', AddressableRainbowLightEffect, "Rainbow", {
|
||||||
|
cv.Optional(CONF_SPEED, default=10): cv.uint32_t,
|
||||||
|
cv.Optional(CONF_WIDTH, default=50): cv.uint32_t,
|
||||||
|
})
|
||||||
|
def addressable_rainbow_effect_to_code(config, effect_id):
|
||||||
|
var = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
||||||
|
cg.add(var.set_speed(config[CONF_SPEED]))
|
||||||
|
cg.add(var.set_width(config[CONF_WIDTH]))
|
||||||
|
yield var
|
||||||
|
|
||||||
|
|
||||||
|
@register_effect('addressable_color_wipe', AddressableColorWipeEffect, "Color Wipe", {
|
||||||
|
cv.Optional(CONF_COLORS, default=[{CONF_NUM_LEDS: 1, CONF_RANDOM: True}]): cv.ensure_list({
|
||||||
|
cv.Optional(CONF_RED, default=1.0): cv.percentage,
|
||||||
|
cv.Optional(CONF_GREEN, default=1.0): cv.percentage,
|
||||||
|
cv.Optional(CONF_BLUE, default=1.0): cv.percentage,
|
||||||
|
cv.Optional(CONF_WHITE, default=1.0): cv.percentage,
|
||||||
|
cv.Optional(CONF_RANDOM, default=False): cv.boolean,
|
||||||
|
cv.Required(CONF_NUM_LEDS): cv.All(cv.uint32_t, cv.Range(min=1)),
|
||||||
|
}),
|
||||||
|
cv.Optional(CONF_ADD_LED_INTERVAL, default='0.1s'): cv.positive_time_period_milliseconds,
|
||||||
|
cv.Optional(CONF_REVERSE, default=False): cv.boolean,
|
||||||
|
})
|
||||||
|
def addressable_color_wipe_effect_to_code(config, effect_id):
|
||||||
|
var = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
||||||
|
cg.add(var.set_add_led_interval(config[CONF_ADD_LED_INTERVAL]))
|
||||||
|
cg.add(var.set_reverse(config[CONF_REVERSE]))
|
||||||
|
colors = []
|
||||||
|
for color in config.get(CONF_COLORS, []):
|
||||||
|
colors.append(cg.StructInitializer(
|
||||||
|
AddressableColorWipeEffectColor,
|
||||||
|
('r', int(round(color[CONF_RED] * 255))),
|
||||||
|
('g', int(round(color[CONF_GREEN] * 255))),
|
||||||
|
('b', int(round(color[CONF_BLUE] * 255))),
|
||||||
|
('w', int(round(color[CONF_WHITE] * 255))),
|
||||||
|
('random', color[CONF_RANDOM]),
|
||||||
|
('num_leds', color[CONF_NUM_LEDS]),
|
||||||
|
))
|
||||||
|
cg.add(var.set_colors(colors))
|
||||||
|
yield var
|
||||||
|
|
||||||
|
|
||||||
|
@register_effect('addressable_scan', AddressableScanEffect, "Scan", {
|
||||||
|
cv.Optional(CONF_MOVE_INTERVAL, default='0.1s'): cv.positive_time_period_milliseconds,
|
||||||
|
})
|
||||||
|
def addressable_scan_effect_to_code(config, effect_id):
|
||||||
|
var = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
||||||
|
cg.add(var.set_move_interval(config[CONF_MOVE_INTERVAL]))
|
||||||
|
yield var
|
||||||
|
|
||||||
|
|
||||||
|
@register_effect('addressable_twinkle', AddressableTwinkleEffect, "Twinkle", {
|
||||||
|
cv.Optional(CONF_TWINKLE_PROBABILITY, default='5%'): cv.percentage,
|
||||||
|
cv.Optional(CONF_PROGRESS_INTERVAL, default='4ms'): cv.positive_time_period_milliseconds,
|
||||||
|
})
|
||||||
|
def addressable_twinkle_effect_to_code(config, effect_id):
|
||||||
|
var = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
||||||
|
cg.add(var.set_twinkle_probability(config[CONF_TWINKLE_PROBABILITY]))
|
||||||
|
cg.add(var.set_progress_interval(config[CONF_PROGRESS_INTERVAL]))
|
||||||
|
yield var
|
||||||
|
|
||||||
|
|
||||||
|
@register_effect('addressable_random_twinkle', AddressableRandomTwinkleEffect, "Random Twinkle", {
|
||||||
|
cv.Optional(CONF_TWINKLE_PROBABILITY, default='5%'): cv.percentage,
|
||||||
|
cv.Optional(CONF_PROGRESS_INTERVAL, default='32ms'): cv.positive_time_period_milliseconds,
|
||||||
|
})
|
||||||
|
def addressable_random_twinkle_effect_to_code(config, effect_id):
|
||||||
|
var = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
||||||
|
cg.add(var.set_twinkle_probability(config[CONF_TWINKLE_PROBABILITY]))
|
||||||
|
cg.add(var.set_progress_interval(config[CONF_PROGRESS_INTERVAL]))
|
||||||
|
yield var
|
||||||
|
|
||||||
|
|
||||||
|
@register_effect('addressable_fireworks', AddressableFireworksEffect, "Fireworks", {
|
||||||
|
cv.Optional(CONF_UPDATE_INTERVAL, default='32ms'): cv.positive_time_period_milliseconds,
|
||||||
|
cv.Optional(CONF_SPARK_PROBABILITY, default='10%'): cv.percentage,
|
||||||
|
cv.Optional(CONF_USE_RANDOM_COLOR, default=False): cv.boolean,
|
||||||
|
cv.Optional(CONF_FADE_OUT_RATE, default=120): cv.uint8_t,
|
||||||
|
})
|
||||||
|
def addressable_fireworks_effect_to_code(config, effect_id):
|
||||||
|
var = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
||||||
|
cg.add(var.set_update_interval(config[CONF_UPDATE_INTERVAL]))
|
||||||
|
cg.add(var.set_spark_probability(config[CONF_SPARK_PROBABILITY]))
|
||||||
|
cg.add(var.set_use_random_color(config[CONF_USE_RANDOM_COLOR]))
|
||||||
|
cg.add(var.set_fade_out_rate(config[CONF_FADE_OUT_RATE]))
|
||||||
|
yield var
|
||||||
|
|
||||||
|
|
||||||
|
@register_effect('addressable_flicker', AddressableFlickerEffect, "Addressable Flicker", {
|
||||||
|
cv.Optional(CONF_UPDATE_INTERVAL, default='16ms'): cv.positive_time_period_milliseconds,
|
||||||
|
cv.Optional(CONF_INTENSITY, default='5%'): cv.percentage,
|
||||||
|
})
|
||||||
|
def addressable_flicker_effect_to_code(config, effect_id):
|
||||||
|
var = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
||||||
|
cg.add(var.set_update_interval(config[CONF_UPDATE_INTERVAL]))
|
||||||
|
cg.add(var.set_intensity(config[CONF_INTENSITY]))
|
||||||
|
yield var
|
||||||
|
|
||||||
|
|
||||||
|
def validate_effects(allowed_effects):
|
||||||
|
def validator(value):
|
||||||
|
value = cv.validate_registry('effect', EFFECTS_REGISTRY)(value)
|
||||||
|
errors = []
|
||||||
|
names = set()
|
||||||
|
for i, x in enumerate(value):
|
||||||
|
key = next(it for it in x.keys())
|
||||||
|
if key not in allowed_effects:
|
||||||
|
errors.append(
|
||||||
|
cv.Invalid("The effect '{}' is not allowed for this "
|
||||||
|
"light type".format(key), [i])
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
name = x[key][CONF_NAME]
|
||||||
|
if name in names:
|
||||||
|
errors.append(
|
||||||
|
cv.Invalid(u"Found the effect name '{}' twice. All effects must have "
|
||||||
|
u"unique names".format(name), [i])
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
names.add(name)
|
||||||
|
if errors:
|
||||||
|
raise cv.MultipleInvalid(errors)
|
||||||
|
return value
|
||||||
|
|
||||||
|
return validator
|
38
esphome/components/light/types.py
Normal file
38
esphome/components/light/types.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome import automation
|
||||||
|
|
||||||
|
# Base
|
||||||
|
light_ns = cg.esphome_ns.namespace('light')
|
||||||
|
LightState = light_ns.class_('LightState', cg.Nameable, cg.Component)
|
||||||
|
# Fake class for addressable lights
|
||||||
|
AddressableLightState = light_ns.class_('LightState', LightState)
|
||||||
|
LightOutput = light_ns.class_('LightOutput')
|
||||||
|
AddressableLight = light_ns.class_('AddressableLight')
|
||||||
|
AddressableLightRef = AddressableLight.operator('ref')
|
||||||
|
LightColorValues = light_ns.class_('LightColorValues')
|
||||||
|
|
||||||
|
# Actions
|
||||||
|
ToggleAction = light_ns.class_('ToggleAction', automation.Action)
|
||||||
|
LightControlAction = light_ns.class_('LightControlAction', automation.Action)
|
||||||
|
DimRelativeAction = light_ns.class_('DimRelativeAction', automation.Action)
|
||||||
|
|
||||||
|
# Effects
|
||||||
|
LightEffect = light_ns.class_('LightEffect')
|
||||||
|
RandomLightEffect = light_ns.class_('RandomLightEffect', LightEffect)
|
||||||
|
LambdaLightEffect = light_ns.class_('LambdaLightEffect', LightEffect)
|
||||||
|
StrobeLightEffect = light_ns.class_('StrobeLightEffect', LightEffect)
|
||||||
|
StrobeLightEffectColor = light_ns.class_('StrobeLightEffectColor', LightEffect)
|
||||||
|
FlickerLightEffect = light_ns.class_('FlickerLightEffect', LightEffect)
|
||||||
|
AddressableLightEffect = light_ns.class_('AddressableLightEffect', LightEffect)
|
||||||
|
AddressableLambdaLightEffect = light_ns.class_('AddressableLambdaLightEffect',
|
||||||
|
AddressableLightEffect)
|
||||||
|
AddressableRainbowLightEffect = light_ns.class_('AddressableRainbowLightEffect',
|
||||||
|
AddressableLightEffect)
|
||||||
|
AddressableColorWipeEffect = light_ns.class_('AddressableColorWipeEffect', AddressableLightEffect)
|
||||||
|
AddressableColorWipeEffectColor = light_ns.struct('AddressableColorWipeEffectColor')
|
||||||
|
AddressableScanEffect = light_ns.class_('AddressableScanEffect', AddressableLightEffect)
|
||||||
|
AddressableTwinkleEffect = light_ns.class_('AddressableTwinkleEffect', AddressableLightEffect)
|
||||||
|
AddressableRandomTwinkleEffect = light_ns.class_('AddressableRandomTwinkleEffect',
|
||||||
|
AddressableLightEffect)
|
||||||
|
AddressableFireworksEffect = light_ns.class_('AddressableFireworksEffect', AddressableLightEffect)
|
||||||
|
AddressableFlickerEffect = light_ns.class_('AddressableFlickerEffect', AddressableLightEffect)
|
|
@ -150,6 +150,10 @@ def exp_mqtt_message(config):
|
||||||
|
|
||||||
@coroutine_with_priority(40.0)
|
@coroutine_with_priority(40.0)
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
|
cg.add_library('AsyncMqttClient', '0.8.2')
|
||||||
|
cg.add_define('USE_MQTT')
|
||||||
|
cg.add_global(mqtt_ns.using)
|
||||||
|
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
|
||||||
cg.add(var.set_broker_address(config[CONF_BROKER]))
|
cg.add(var.set_broker_address(config[CONF_BROKER]))
|
||||||
|
@ -217,10 +221,6 @@ def to_code(config):
|
||||||
trig = cg.new_Pvariable(conf[CONF_TRIGGER_ID], conf[CONF_TOPIC], conf[CONF_QOS])
|
trig = cg.new_Pvariable(conf[CONF_TRIGGER_ID], conf[CONF_TOPIC], conf[CONF_QOS])
|
||||||
yield automation.build_automation(trig, [(cg.JsonObjectConstRef, 'x')], conf)
|
yield automation.build_automation(trig, [(cg.JsonObjectConstRef, 'x')], conf)
|
||||||
|
|
||||||
cg.add_library('AsyncMqttClient', '0.8.2')
|
|
||||||
cg.add_define('USE_MQTT')
|
|
||||||
cg.add_global(mqtt_ns.using)
|
|
||||||
|
|
||||||
|
|
||||||
MQTT_PUBLISH_ACTION_SCHEMA = cv.Schema({
|
MQTT_PUBLISH_ACTION_SCHEMA = cv.Schema({
|
||||||
cv.GenerateID(): cv.use_id(MQTTClientComponent),
|
cv.GenerateID(): cv.use_id(MQTTClientComponent),
|
||||||
|
|
|
@ -10,7 +10,7 @@ from esphome.const import CONF_ABOVE, CONF_ACCURACY_DECIMALS, CONF_ALPHA, CONF_B
|
||||||
CONF_SEND_EVERY, CONF_SEND_FIRST_AT, CONF_TO, CONF_TRIGGER_ID, \
|
CONF_SEND_EVERY, CONF_SEND_FIRST_AT, CONF_TO, CONF_TRIGGER_ID, \
|
||||||
CONF_UNIT_OF_MEASUREMENT, \
|
CONF_UNIT_OF_MEASUREMENT, \
|
||||||
CONF_WINDOW_SIZE, CONF_NAME, CONF_MQTT_ID
|
CONF_WINDOW_SIZE, CONF_NAME, CONF_MQTT_ID
|
||||||
from esphome.core import CORE, coroutine
|
from esphome.core import CORE, coroutine, coroutine_with_priority
|
||||||
from esphome.util import Registry
|
from esphome.util import Registry
|
||||||
|
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
|
@ -305,6 +305,7 @@ def fit_linear(x, y):
|
||||||
return k, b
|
return k, b
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine_with_priority(40.0)
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
cg.add_define('USE_SENSOR')
|
cg.add_define('USE_SENSOR')
|
||||||
cg.add_global(sensor_ns.using)
|
cg.add_global(sensor_ns.using)
|
||||||
|
|
|
@ -3,7 +3,7 @@ import esphome.config_validation as cv
|
||||||
from esphome import pins
|
from esphome import pins
|
||||||
from esphome.const import CONF_CLK_PIN, CONF_ID, CONF_MISO_PIN, CONF_MOSI_PIN, CONF_SPI_ID, \
|
from esphome.const import CONF_CLK_PIN, CONF_ID, CONF_MISO_PIN, CONF_MOSI_PIN, CONF_SPI_ID, \
|
||||||
CONF_CS_PIN
|
CONF_CS_PIN
|
||||||
from esphome.core import coroutine
|
from esphome.core import coroutine, coroutine_with_priority
|
||||||
|
|
||||||
spi_ns = cg.esphome_ns.namespace('spi')
|
spi_ns = cg.esphome_ns.namespace('spi')
|
||||||
SPIComponent = spi_ns.class_('SPIComponent', cg.Component)
|
SPIComponent = spi_ns.class_('SPIComponent', cg.Component)
|
||||||
|
@ -18,7 +18,9 @@ CONFIG_SCHEMA = cv.All(cv.Schema({
|
||||||
}), cv.has_at_least_one_key(CONF_MISO_PIN, CONF_MOSI_PIN))
|
}), cv.has_at_least_one_key(CONF_MISO_PIN, CONF_MOSI_PIN))
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine_with_priority(1.0)
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
|
cg.add_global(spi_ns.using)
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
yield cg.register_component(var, config)
|
yield cg.register_component(var, config)
|
||||||
|
|
||||||
|
@ -31,8 +33,6 @@ def to_code(config):
|
||||||
mosi = yield cg.gpio_pin_expression(config[CONF_MOSI_PIN])
|
mosi = yield cg.gpio_pin_expression(config[CONF_MOSI_PIN])
|
||||||
cg.add(var.set_mosi(mosi))
|
cg.add(var.set_mosi(mosi))
|
||||||
|
|
||||||
cg.add_global(spi_ns.using)
|
|
||||||
|
|
||||||
|
|
||||||
SPI_DEVICE_SCHEMA = cv.Schema({
|
SPI_DEVICE_SCHEMA = cv.Schema({
|
||||||
cv.GenerateID(CONF_SPI_ID): cv.use_id(SPIComponent),
|
cv.GenerateID(CONF_SPI_ID): cv.use_id(SPIComponent),
|
||||||
|
|
|
@ -2,8 +2,8 @@ import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome import automation
|
from esphome import automation
|
||||||
from esphome.const import CONF_ACCELERATION, CONF_DECELERATION, CONF_ID, CONF_MAX_SPEED, \
|
from esphome.const import CONF_ACCELERATION, CONF_DECELERATION, CONF_ID, CONF_MAX_SPEED, \
|
||||||
CONF_POSITION, CONF_TARGET
|
CONF_POSITION, CONF_TARGET, CONF_SPEED
|
||||||
from esphome.core import CORE, coroutine
|
from esphome.core import CORE, coroutine, coroutine_with_priority
|
||||||
|
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ Stepper = stepper_ns.class_('Stepper')
|
||||||
|
|
||||||
SetTargetAction = stepper_ns.class_('SetTargetAction', automation.Action)
|
SetTargetAction = stepper_ns.class_('SetTargetAction', automation.Action)
|
||||||
ReportPositionAction = stepper_ns.class_('ReportPositionAction', automation.Action)
|
ReportPositionAction = stepper_ns.class_('ReportPositionAction', automation.Action)
|
||||||
|
SetSpeedAction = stepper_ns.class_('SetSpeedAction', automation.Action)
|
||||||
|
|
||||||
|
|
||||||
def validate_acceleration(value):
|
def validate_acceleration(value):
|
||||||
|
@ -103,5 +104,18 @@ def stepper_report_position_to_code(config, action_id, template_arg, args):
|
||||||
yield var
|
yield var
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action('stepper.set_speed', SetSpeedAction, cv.Schema({
|
||||||
|
cv.Required(CONF_ID): cv.use_id(Stepper),
|
||||||
|
cv.Required(CONF_SPEED): cv.templatable(validate_speed),
|
||||||
|
}))
|
||||||
|
def stepper_set_speed_to_code(config, action_id, template_arg, args):
|
||||||
|
paren = yield cg.get_variable(config[CONF_ID])
|
||||||
|
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
|
template_ = yield cg.templatable(config[CONF_SPEED], args, cg.int32)
|
||||||
|
cg.add(var.set_speed(template_))
|
||||||
|
yield var
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine_with_priority(100.0)
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
cg.add_global(stepper_ns.using)
|
cg.add_global(stepper_ns.using)
|
||||||
|
|
|
@ -19,6 +19,7 @@ class Stepper {
|
||||||
void set_acceleration(float acceleration) { this->acceleration_ = acceleration; }
|
void set_acceleration(float acceleration) { this->acceleration_ = acceleration; }
|
||||||
void set_deceleration(float deceleration) { this->deceleration_ = deceleration; }
|
void set_deceleration(float deceleration) { this->deceleration_ = deceleration; }
|
||||||
void set_max_speed(float max_speed) { this->max_speed_ = max_speed; }
|
void set_max_speed(float max_speed) { this->max_speed_ = max_speed; }
|
||||||
|
virtual void on_update_speed() {}
|
||||||
bool has_reached_target() { return this->current_position == this->target_position; }
|
bool has_reached_target() { return this->current_position == this->target_position; }
|
||||||
|
|
||||||
int32_t current_position{0};
|
int32_t current_position{0};
|
||||||
|
@ -60,5 +61,21 @@ template<typename... Ts> class ReportPositionAction : public Action<Ts...> {
|
||||||
Stepper *parent_;
|
Stepper *parent_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class SetSpeedAction : public Action<Ts...> {
|
||||||
|
public:
|
||||||
|
explicit SetSpeedAction(Stepper *parent) : parent_(parent) {}
|
||||||
|
|
||||||
|
TEMPLATABLE_VALUE(float, speed);
|
||||||
|
|
||||||
|
void play(Ts... x) override {
|
||||||
|
float speed = this->speed_.value(x...);
|
||||||
|
this->parent_->set_max_speed(speed);
|
||||||
|
this->parent_->on_update_speed();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Stepper *parent_;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace stepper
|
} // namespace stepper
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -5,7 +5,7 @@ from esphome.automation import Condition, maybe_simple_id
|
||||||
from esphome.components import mqtt
|
from esphome.components import mqtt
|
||||||
from esphome.const import CONF_ICON, CONF_ID, CONF_INTERNAL, CONF_INVERTED, CONF_ON_TURN_OFF, \
|
from esphome.const import CONF_ICON, CONF_ID, CONF_INTERNAL, CONF_INVERTED, CONF_ON_TURN_OFF, \
|
||||||
CONF_ON_TURN_ON, CONF_TRIGGER_ID, CONF_MQTT_ID, CONF_NAME
|
CONF_ON_TURN_ON, CONF_TRIGGER_ID, CONF_MQTT_ID, CONF_NAME
|
||||||
from esphome.core import CORE, coroutine
|
from esphome.core import CORE, coroutine, coroutine_with_priority
|
||||||
|
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
|
|
||||||
|
@ -92,6 +92,7 @@ def switch_is_off_to_code(config, condition_id, template_arg, args):
|
||||||
yield cg.new_Pvariable(condition_id, template_arg, paren)
|
yield cg.new_Pvariable(condition_id, template_arg, paren)
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine_with_priority(100.0)
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
cg.add_global(switch_ns.using)
|
cg.add_global(switch_ns.using)
|
||||||
cg.add_define('USE_SWITCH')
|
cg.add_define('USE_SWITCH')
|
||||||
|
|
|
@ -3,8 +3,8 @@ import esphome.config_validation as cv
|
||||||
from esphome import automation
|
from esphome import automation
|
||||||
from esphome.components import mqtt
|
from esphome.components import mqtt
|
||||||
from esphome.const import CONF_ICON, CONF_ID, CONF_INTERNAL, CONF_ON_VALUE, \
|
from esphome.const import CONF_ICON, CONF_ID, CONF_INTERNAL, CONF_ON_VALUE, \
|
||||||
CONF_TRIGGER_ID, CONF_MQTT_ID, CONF_NAME
|
CONF_TRIGGER_ID, CONF_MQTT_ID, CONF_NAME, CONF_STATE
|
||||||
from esphome.core import CORE, coroutine
|
from esphome.core import CORE, coroutine, coroutine_with_priority
|
||||||
|
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ TextSensorPtr = TextSensor.operator('ptr')
|
||||||
TextSensorStateTrigger = text_sensor_ns.class_('TextSensorStateTrigger',
|
TextSensorStateTrigger = text_sensor_ns.class_('TextSensorStateTrigger',
|
||||||
automation.Trigger.template(cg.std_string))
|
automation.Trigger.template(cg.std_string))
|
||||||
TextSensorPublishAction = text_sensor_ns.class_('TextSensorPublishAction', automation.Action)
|
TextSensorPublishAction = text_sensor_ns.class_('TextSensorPublishAction', automation.Action)
|
||||||
|
TextSensorStateCondition = text_sensor_ns.class_('TextSensorStateCondition', automation.Condition)
|
||||||
|
|
||||||
icon = cv.icon
|
icon = cv.icon
|
||||||
|
|
||||||
|
@ -53,6 +54,19 @@ def register_text_sensor(var, config):
|
||||||
yield setup_text_sensor_core_(var, config)
|
yield setup_text_sensor_core_(var, config)
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine_with_priority(100.0)
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
cg.add_define('USE_TEXT_SENSOR')
|
cg.add_define('USE_TEXT_SENSOR')
|
||||||
cg.add_global(text_sensor_ns.using)
|
cg.add_global(text_sensor_ns.using)
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_condition('text_sensor.state', TextSensorStateCondition, cv.Schema({
|
||||||
|
cv.Required(CONF_ID): cv.use_id(TextSensor),
|
||||||
|
cv.Required(CONF_STATE): cv.templatable(cv.string_strict),
|
||||||
|
}))
|
||||||
|
def text_sensor_state_to_code(config, condition_id, template_arg, args):
|
||||||
|
paren = yield cg.get_variable(config[CONF_ID])
|
||||||
|
var = cg.new_Pvariable(condition_id, template_arg, paren)
|
||||||
|
templ = yield cg.templatable(config[CONF_STATE], args, cg.std_string)
|
||||||
|
cg.add(var.set_state(templ))
|
||||||
|
yield var
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
#include "automation.h"
|
|
||||||
#include "esphome/core/log.h"
|
|
||||||
|
|
||||||
namespace esphome {
|
|
||||||
namespace text_sensor {
|
|
||||||
|
|
||||||
static const char *TAG = "text_sensor.automation";
|
|
||||||
|
|
||||||
} // namespace text_sensor
|
|
||||||
} // namespace esphome
|
|
|
@ -14,6 +14,18 @@ class TextSensorStateTrigger : public Trigger<std::string> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class TextSensorStateCondition : public Condition<Ts...> {
|
||||||
|
public:
|
||||||
|
explicit TextSensorStateCondition(TextSensor *parent) : parent_(parent) {}
|
||||||
|
|
||||||
|
TEMPLATABLE_VALUE(std::string, state)
|
||||||
|
|
||||||
|
bool check(Ts... x) override { return this->parent_->state == this->state_.value(x...); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
TextSensor *parent_;
|
||||||
|
};
|
||||||
|
|
||||||
template<typename... Ts> class TextSensorPublishAction : public Action<Ts...> {
|
template<typename... Ts> class TextSensorPublishAction : public Action<Ts...> {
|
||||||
public:
|
public:
|
||||||
TextSensorPublishAction(TextSensor *sensor) : sensor_(sensor) {}
|
TextSensorPublishAction(TextSensor *sensor) : sensor_(sensor) {}
|
||||||
|
|
|
@ -12,7 +12,7 @@ from esphome import automation
|
||||||
from esphome.const import CONF_CRON, CONF_DAYS_OF_MONTH, CONF_DAYS_OF_WEEK, CONF_HOURS, \
|
from esphome.const import CONF_CRON, CONF_DAYS_OF_MONTH, CONF_DAYS_OF_WEEK, CONF_HOURS, \
|
||||||
CONF_MINUTES, CONF_MONTHS, CONF_ON_TIME, CONF_SECONDS, CONF_TIMEZONE, CONF_TRIGGER_ID, \
|
CONF_MINUTES, CONF_MONTHS, CONF_ON_TIME, CONF_SECONDS, CONF_TIMEZONE, CONF_TRIGGER_ID, \
|
||||||
CONF_AT, CONF_SECOND, CONF_HOUR, CONF_MINUTE
|
CONF_AT, CONF_SECOND, CONF_HOUR, CONF_MINUTE
|
||||||
from esphome.core import coroutine
|
from esphome.core import coroutine, coroutine_with_priority
|
||||||
from esphome.py_compat import string_types
|
from esphome.py_compat import string_types
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -293,6 +293,7 @@ def register_time(time_var, config):
|
||||||
yield setup_time_core_(time_var, config)
|
yield setup_time_core_(time_var, config)
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine_with_priority(100.0)
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
cg.add_define('USE_TIME')
|
cg.add_define('USE_TIME')
|
||||||
cg.add_global(time_ns.using)
|
cg.add_global(time_ns.using)
|
||||||
|
|
|
@ -120,6 +120,9 @@ def _lookup_module(domain, is_platform):
|
||||||
try:
|
try:
|
||||||
module = importlib.import_module(path)
|
module = importlib.import_module(path)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
import traceback
|
||||||
|
_LOGGER.error("Unable to import component %s:", domain)
|
||||||
|
traceback.print_exc()
|
||||||
return None
|
return None
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
import traceback
|
import traceback
|
||||||
|
@ -390,6 +393,7 @@ def validate_config(config):
|
||||||
if component is None:
|
if component is None:
|
||||||
result.add_str_error(u"Component not found: {}".format(domain), path)
|
result.add_str_error(u"Component not found: {}".format(domain), path)
|
||||||
continue
|
continue
|
||||||
|
CORE.loaded_integrations.add(domain)
|
||||||
|
|
||||||
# Process AUTO_LOAD
|
# Process AUTO_LOAD
|
||||||
for load in component.auto_load:
|
for load in component.auto_load:
|
||||||
|
@ -432,6 +436,7 @@ def validate_config(config):
|
||||||
if platform is None:
|
if platform is None:
|
||||||
result.add_str_error(u"Platform not found: '{}'".format(p_domain), path)
|
result.add_str_error(u"Platform not found: '{}'".format(p_domain), path)
|
||||||
continue
|
continue
|
||||||
|
CORE.loaded_integrations.add(p_name)
|
||||||
|
|
||||||
# Process AUTO_LOAD
|
# Process AUTO_LOAD
|
||||||
for load in platform.auto_load:
|
for load in platform.auto_load:
|
||||||
|
|
|
@ -60,8 +60,6 @@ RESERVED_IDS = [
|
||||||
'App', 'pinMode', 'delay', 'delayMicroseconds', 'digitalRead', 'digitalWrite', 'INPUT',
|
'App', 'pinMode', 'delay', 'delayMicroseconds', 'digitalRead', 'digitalWrite', 'INPUT',
|
||||||
'OUTPUT',
|
'OUTPUT',
|
||||||
'uint8_t', 'uint16_t', 'uint32_t', 'uint64_t', 'int8_t', 'int16_t', 'int32_t', 'int64_t',
|
'uint8_t', 'uint16_t', 'uint32_t', 'uint64_t', 'int8_t', 'int16_t', 'int32_t', 'int64_t',
|
||||||
'display', 'i2c', 'spi', 'uart', 'sensor', 'binary_sensor', 'climate', 'cover', 'text_sensor',
|
|
||||||
'api', 'fan', 'light', 'gpio', 'mqtt', 'ota', 'power_supply', 'wifi'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -127,6 +125,8 @@ def string(value):
|
||||||
check_not_templatable(value)
|
check_not_templatable(value)
|
||||||
if isinstance(value, (dict, list)):
|
if isinstance(value, (dict, list)):
|
||||||
raise Invalid("string value cannot be dictionary or list.")
|
raise Invalid("string value cannot be dictionary or list.")
|
||||||
|
if isinstance(value, bool):
|
||||||
|
raise Invalid("Auto-converted this value to boolean, please wrap the value in quotes.")
|
||||||
if isinstance(value, text_type):
|
if isinstance(value, text_type):
|
||||||
return value
|
return value
|
||||||
if value is not None:
|
if value is not None:
|
||||||
|
@ -168,7 +168,7 @@ def boolean(value):
|
||||||
check_not_templatable(value)
|
check_not_templatable(value)
|
||||||
if isinstance(value, bool):
|
if isinstance(value, bool):
|
||||||
return value
|
return value
|
||||||
if isinstance(value, str):
|
if isinstance(value, string_types):
|
||||||
value = value.lower()
|
value = value.lower()
|
||||||
if value in ('true', 'yes', 'on', 'enable'):
|
if value in ('true', 'yes', 'on', 'enable'):
|
||||||
return True
|
return True
|
||||||
|
@ -285,7 +285,10 @@ def validate_id_name(value):
|
||||||
u"character and numbers. The character '{}' cannot be used"
|
u"character and numbers. The character '{}' cannot be used"
|
||||||
u"".format(char))
|
u"".format(char))
|
||||||
if value in RESERVED_IDS:
|
if value in RESERVED_IDS:
|
||||||
raise Invalid(u"ID {} is reserved internally and cannot be used".format(value))
|
raise Invalid(u"ID '{}' is reserved internally and cannot be used".format(value))
|
||||||
|
if value in CORE.loaded_integrations:
|
||||||
|
raise Invalid(u"ID '{}' conflicts with the name of an esphome integration, please use "
|
||||||
|
u"another ID name.".format(value))
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
@ -330,7 +333,7 @@ def templatable(other_validators):
|
||||||
|
|
||||||
def validator(value):
|
def validator(value):
|
||||||
if isinstance(value, Lambda):
|
if isinstance(value, Lambda):
|
||||||
return value
|
return lambda_(value)
|
||||||
if isinstance(other_validators, dict):
|
if isinstance(other_validators, dict):
|
||||||
return schema(value)
|
return schema(value)
|
||||||
return schema(value)
|
return schema(value)
|
||||||
|
@ -953,11 +956,22 @@ def enum(mapping, **kwargs):
|
||||||
return validator
|
return validator
|
||||||
|
|
||||||
|
|
||||||
|
LAMBDA_ENTITY_ID_PROG = re.compile(r'id\(\s*([a-zA-Z0-9_]+\.[.a-zA-Z0-9_]+)\s*\)')
|
||||||
|
|
||||||
|
|
||||||
def lambda_(value):
|
def lambda_(value):
|
||||||
"""Coerce this configuration option to a lambda."""
|
"""Coerce this configuration option to a lambda."""
|
||||||
if isinstance(value, Lambda):
|
if not isinstance(value, Lambda):
|
||||||
return value
|
value = Lambda(string_strict(value))
|
||||||
return Lambda(string_strict(value))
|
entity_id_parts = re.split(LAMBDA_ENTITY_ID_PROG, value.value)
|
||||||
|
if len(entity_id_parts) != 1:
|
||||||
|
entity_ids = ' '.join("'{}'".format(entity_id_parts[i])
|
||||||
|
for i in range(1, len(entity_id_parts), 2))
|
||||||
|
raise Invalid("Lambda contains reference to entity-id-style ID {}. "
|
||||||
|
"The id() wrapper only works for ESPHome-internal types. For importing "
|
||||||
|
"states from Home Assistant use the 'homeassistant' sensor platforms."
|
||||||
|
"".format(entity_ids))
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
def dimensions(value):
|
def dimensions(value):
|
||||||
|
|
|
@ -327,6 +327,7 @@ CONF_REPOSITORY = 'repository'
|
||||||
CONF_RESET_PIN = 'reset_pin'
|
CONF_RESET_PIN = 'reset_pin'
|
||||||
CONF_RESIZE = 'resize'
|
CONF_RESIZE = 'resize'
|
||||||
CONF_RESOLUTION = 'resolution'
|
CONF_RESOLUTION = 'resolution'
|
||||||
|
CONF_RESTORE = 'restore'
|
||||||
CONF_RESTORE_MODE = 'restore_mode'
|
CONF_RESTORE_MODE = 'restore_mode'
|
||||||
CONF_RESTORE_STATE = 'restore_state'
|
CONF_RESTORE_STATE = 'restore_state'
|
||||||
CONF_RESTORE_VALUE = 'restore_value'
|
CONF_RESTORE_VALUE = 'restore_value'
|
||||||
|
@ -397,6 +398,7 @@ CONF_THEN = 'then'
|
||||||
CONF_THRESHOLD = 'threshold'
|
CONF_THRESHOLD = 'threshold'
|
||||||
CONF_THROTTLE = 'throttle'
|
CONF_THROTTLE = 'throttle'
|
||||||
CONF_TILT = 'tilt'
|
CONF_TILT = 'tilt'
|
||||||
|
CONF_TIME = 'time'
|
||||||
CONF_TIMEOUT = 'timeout'
|
CONF_TIMEOUT = 'timeout'
|
||||||
CONF_TIMES = 'times'
|
CONF_TIMES = 'times'
|
||||||
CONF_TIMEZONE = 'timezone'
|
CONF_TIMEZONE = 'timezone'
|
||||||
|
|
|
@ -502,6 +502,8 @@ class EsphomeCore(object):
|
||||||
# A dictionary of started coroutines, used to warn when a coroutine was not
|
# A dictionary of started coroutines, used to warn when a coroutine was not
|
||||||
# awaited.
|
# awaited.
|
||||||
self.active_coroutines = {} # type: Dict[int, Any]
|
self.active_coroutines = {} # type: Dict[int, Any]
|
||||||
|
# A set of strings of names of loaded integrations, used to find namespace ID conflicts
|
||||||
|
self.loaded_integrations = set()
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.dashboard = False
|
self.dashboard = False
|
||||||
|
@ -521,6 +523,7 @@ class EsphomeCore(object):
|
||||||
self.build_flags = set()
|
self.build_flags = set()
|
||||||
self.defines = set()
|
self.defines = set()
|
||||||
self.active_coroutines = {}
|
self.active_coroutines = {}
|
||||||
|
self.loaded_integrations = set()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def address(self): # type: () -> str
|
def address(self): # type: () -> str
|
||||||
|
|
|
@ -55,6 +55,32 @@ template<typename... Ts> class LambdaCondition : public Condition<Ts...> {
|
||||||
std::function<bool(Ts...)> f_;
|
std::function<bool(Ts...)> f_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class ForCondition : public Condition<Ts...>, public Component {
|
||||||
|
public:
|
||||||
|
explicit ForCondition(Condition<> *condition) : condition_(condition) {}
|
||||||
|
|
||||||
|
TEMPLATABLE_VALUE(uint32_t, time);
|
||||||
|
|
||||||
|
void loop() override { this->check_internal(); }
|
||||||
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
|
bool check_internal() {
|
||||||
|
bool cond = this->condition_->check();
|
||||||
|
if (!cond)
|
||||||
|
this->last_inactive_ = millis();
|
||||||
|
return cond;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool check(Ts... x) override {
|
||||||
|
if (!this->check_internal())
|
||||||
|
return false;
|
||||||
|
return millis() - this->last_inactive_ < this->time_.value(x...);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Condition<> *condition_;
|
||||||
|
uint32_t last_inactive_{0};
|
||||||
|
};
|
||||||
|
|
||||||
class StartupTrigger : public Trigger<>, public Component {
|
class StartupTrigger : public Trigger<>, public Component {
|
||||||
public:
|
public:
|
||||||
explicit StartupTrigger(float setup_priority) : setup_priority_(setup_priority) {}
|
explicit StartupTrigger(float setup_priority) : setup_priority_(setup_priority) {}
|
||||||
|
|
|
@ -304,4 +304,9 @@ float clamp(float val, float min, float max) {
|
||||||
}
|
}
|
||||||
float lerp(float completion, float start, float end) { return start + (end - start) * completion; }
|
float lerp(float completion, float start, float end) { return start + (end - start) * completion; }
|
||||||
|
|
||||||
|
bool str_startswith(const std::string &full, const std::string &start) { return full.rfind(start, 0) == 0; }
|
||||||
|
bool str_endswith(const std::string &full, const std::string &ending) {
|
||||||
|
return full.rfind(ending) == (full.size() - ending.size());
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -52,6 +52,8 @@ std::string to_lowercase_underscore(std::string s);
|
||||||
|
|
||||||
/// Compare string a to string b (ignoring case) and return whether they are equal.
|
/// Compare string a to string b (ignoring case) and return whether they are equal.
|
||||||
bool str_equals_case_insensitive(const std::string &a, const std::string &b);
|
bool str_equals_case_insensitive(const std::string &a, const std::string &b);
|
||||||
|
bool str_startswith(const std::string &full, const std::string &start);
|
||||||
|
bool str_endswith(const std::string &full, const std::string &ending);
|
||||||
|
|
||||||
class HighFrequencyLoopRequester {
|
class HighFrequencyLoopRequester {
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -151,7 +151,7 @@ def add_includes(includes):
|
||||||
# Add includes at the very end, so that the included files can access global variables
|
# Add includes at the very end, so that the included files can access global variables
|
||||||
for include in includes:
|
for include in includes:
|
||||||
path = CORE.relative_config_path(include)
|
path = CORE.relative_config_path(include)
|
||||||
res = os.path.relpath(path, CORE.relative_build_path('src'))
|
res = os.path.relpath(path, CORE.relative_build_path('src')).replace(os.path.sep, '/')
|
||||||
cg.add_global(cg.RawExpression(u'#include "{}"'.format(res)))
|
cg.add_global(cg.RawExpression(u'#include "{}"'.format(res)))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -72,6 +72,9 @@ class ExpressionList(Expression):
|
||||||
text = u", ".join(text_type(x) for x in self.args)
|
text = u", ".join(text_type(x) for x in self.args)
|
||||||
return indent_all_but_first_and_last(text)
|
return indent_all_but_first_and_last(text)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self.args)
|
||||||
|
|
||||||
|
|
||||||
class TemplateArguments(Expression):
|
class TemplateArguments(Expression):
|
||||||
def __init__(self, *args): # type: (*SafeExpType) -> None
|
def __init__(self, *args): # type: (*SafeExpType) -> None
|
||||||
|
@ -81,6 +84,9 @@ class TemplateArguments(Expression):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return u'<{}>'.format(self.args)
|
return u'<{}>'.format(self.args)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self.args)
|
||||||
|
|
||||||
|
|
||||||
class CallExpression(Expression):
|
class CallExpression(Expression):
|
||||||
def __init__(self, base, *args): # type: (Expression, *SafeExpType) -> None
|
def __init__(self, base, *args): # type: (Expression, *SafeExpType) -> None
|
||||||
|
@ -400,7 +406,7 @@ def Pvariable(id, # type: ID
|
||||||
|
|
||||||
|
|
||||||
def new_Pvariable(id, # type: ID
|
def new_Pvariable(id, # type: ID
|
||||||
*args # type: SafeExpType
|
*args # type: *SafeExpType
|
||||||
):
|
):
|
||||||
"""Declare a new pointer variable in the code generation by calling it's constructor
|
"""Declare a new pointer variable in the code generation by calling it's constructor
|
||||||
with the given arguments.
|
with the given arguments.
|
||||||
|
@ -480,6 +486,21 @@ def get_variable(id): # type: (ID) -> Generator[MockObj]
|
||||||
yield var
|
yield var
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine
|
||||||
|
def get_variable_with_full_id(id): # type: (ID) -> Generator[ID, MockObj]
|
||||||
|
"""
|
||||||
|
Wait for the given ID to be defined in the code generation and
|
||||||
|
return it as a MockObj.
|
||||||
|
|
||||||
|
This is a coroutine, you need to await it with a 'yield' expression!
|
||||||
|
|
||||||
|
:param id: The ID to retrieve
|
||||||
|
:return: The variable as a MockObj.
|
||||||
|
"""
|
||||||
|
full_id, var = yield CORE.get_variable_with_full_id(id)
|
||||||
|
yield full_id, var
|
||||||
|
|
||||||
|
|
||||||
@coroutine
|
@coroutine
|
||||||
def process_lambda(value, # type: Lambda
|
def process_lambda(value, # type: Lambda
|
||||||
parameters, # type: List[Tuple[SafeExpType, str]]
|
parameters, # type: List[Tuple[SafeExpType, str]]
|
||||||
|
@ -572,7 +593,7 @@ class MockObj(Expression):
|
||||||
attr = attr[1:]
|
attr = attr[1:]
|
||||||
return MockObj(u'{}{}{}'.format(self.base, self.op, attr), next_op)
|
return MockObj(u'{}{}{}'.format(self.base, self.op, attr), next_op)
|
||||||
|
|
||||||
def __call__(self, *args, **kwargs): # type: (*Any, **Any) -> MockObj
|
def __call__(self, *args): # type: (SafeExpType) -> MockObj
|
||||||
call = CallExpression(self.base, *args)
|
call = CallExpression(self.base, *args)
|
||||||
return MockObj(call, self.op)
|
return MockObj(call, self.op)
|
||||||
|
|
||||||
|
@ -583,37 +604,32 @@ class MockObj(Expression):
|
||||||
return u'MockObj<{}>'.format(text_type(self.base))
|
return u'MockObj<{}>'.format(text_type(self.base))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _(self):
|
def _(self): # type: () -> MockObj
|
||||||
return MockObj(u'{}{}'.format(self.base, self.op))
|
return MockObj(u'{}{}'.format(self.base, self.op))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def new(self):
|
def new(self): # type: () -> MockObj
|
||||||
return MockObj(u'new {}'.format(self.base), u'->')
|
return MockObj(u'new {}'.format(self.base), u'->')
|
||||||
|
|
||||||
def template(self, *args): # type: (Tuple[Union[TemplateArguments, Expression]]) -> MockObj
|
def template(self, *args): # type: (*SafeExpType) -> MockObj
|
||||||
if len(args) != 1 or not isinstance(args[0], TemplateArguments):
|
if len(args) != 1 or not isinstance(args[0], TemplateArguments):
|
||||||
args = TemplateArguments(*args)
|
args = TemplateArguments(*args)
|
||||||
else:
|
else:
|
||||||
args = args[0]
|
args = args[0]
|
||||||
obj = MockObj(u'{}{}'.format(self.base, args))
|
return MockObj(u'{}{}'.format(self.base, args))
|
||||||
return obj
|
|
||||||
|
|
||||||
def namespace(self, name): # type: (str) -> MockObj
|
def namespace(self, name): # type: (str) -> MockObj
|
||||||
obj = MockObj(u'{}{}{}'.format(self.base, self.op, name), u'::')
|
return MockObj(u'{}{}'.format(self._, name), u'::')
|
||||||
return obj
|
|
||||||
|
|
||||||
def class_(self, name, *parents): # type: (str, *MockObjClass) -> MockObjClass
|
def class_(self, name, *parents): # type: (str, *MockObjClass) -> MockObjClass
|
||||||
op = '' if self.op == '' else '::'
|
op = '' if self.op == '' else '::'
|
||||||
obj = MockObjClass(u'{}{}{}'.format(self.base, op, name), u'.', parents=parents)
|
return MockObjClass(u'{}{}{}'.format(self.base, op, name), u'.', parents=parents)
|
||||||
return obj
|
|
||||||
|
|
||||||
def struct(self, name): # type: (str) -> MockObjClass
|
def struct(self, name): # type: (str) -> MockObjClass
|
||||||
return self.class_(name)
|
return self.class_(name)
|
||||||
|
|
||||||
def enum(self, name, is_class=False): # type: (str, bool) -> MockObj
|
def enum(self, name, is_class=False): # type: (str, bool) -> MockObj
|
||||||
if is_class:
|
return MockObjEnum(enum=name, is_class=is_class, base=self.base, op=self.op)
|
||||||
return self.namespace(name)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def operator(self, name): # type: (str) -> MockObj
|
def operator(self, name): # type: (str) -> MockObj
|
||||||
if name == 'ref':
|
if name == 'ref':
|
||||||
|
@ -625,7 +641,7 @@ class MockObj(Expression):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def using(self):
|
def using(self): # type: () -> MockObj
|
||||||
assert self.op == '::'
|
assert self.op == '::'
|
||||||
return MockObj(u'using namespace {}'.format(self.base))
|
return MockObj(u'using namespace {}'.format(self.base))
|
||||||
|
|
||||||
|
@ -637,6 +653,26 @@ class MockObj(Expression):
|
||||||
return MockObj(u'{}[{}]'.format(self.base, item), next_op)
|
return MockObj(u'{}[{}]'.format(self.base, item), next_op)
|
||||||
|
|
||||||
|
|
||||||
|
class MockObjEnum(MockObj):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self._enum = kwargs.pop('enum')
|
||||||
|
self._is_class = kwargs.pop('is_class')
|
||||||
|
base = kwargs.pop('base')
|
||||||
|
if self._is_class:
|
||||||
|
base = base + '::' + self._enum
|
||||||
|
kwargs['op'] = '::'
|
||||||
|
kwargs['base'] = base
|
||||||
|
MockObj.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
def __str__(self): # type: () -> unicode
|
||||||
|
if self._is_class:
|
||||||
|
return super(MockObjEnum, self).__str__()
|
||||||
|
return u'{}{}{}'.format(self.base, self.op, self._enum)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return u'MockObj<{}>'.format(text_type(self.base))
|
||||||
|
|
||||||
|
|
||||||
class MockObjClass(MockObj):
|
class MockObjClass(MockObj):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
parens = kwargs.pop('parents')
|
parens = kwargs.pop('parents')
|
||||||
|
@ -657,10 +693,8 @@ class MockObjClass(MockObj):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def template(self,
|
def template(self, *args):
|
||||||
*args # type: Tuple[Union[TemplateArguments, Expression]]
|
# type: (*SafeExpType) -> MockObjClass
|
||||||
):
|
|
||||||
# type: (...) -> MockObjClass
|
|
||||||
if len(args) != 1 or not isinstance(args[0], TemplateArguments):
|
if len(args) != 1 or not isinstance(args[0], TemplateArguments):
|
||||||
args = TemplateArguments(*args)
|
args = TemplateArguments(*args)
|
||||||
else:
|
else:
|
||||||
|
|
Loading…
Reference in a new issue