mirror of
https://github.com/esphome/esphome.git
synced 2024-11-22 11:47:30 +01:00
Updates
This commit is contained in:
parent
0a0713f0e2
commit
766f6c045d
@ -1,7 +1,7 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
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.util import Registry
|
||||
|
||||
@ -55,6 +55,7 @@ UpdateComponentAction = cg.esphome_ns.class_('UpdateComponentAction', Action)
|
||||
Automation = cg.esphome_ns.class_('Automation')
|
||||
|
||||
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):
|
||||
@ -78,6 +79,8 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
||||
try:
|
||||
return cv.Schema([schema])(value)
|
||||
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):
|
||||
raise 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_)
|
||||
|
||||
|
||||
@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))
|
||||
def delay_action_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
|
@ -13,7 +13,7 @@ from esphome.cpp_generator import ( # noqa
|
||||
StructInitializer, ArrayInitializer, safe_exp, Statement,
|
||||
progmem_array, statement, variable, Pvariable, new_Pvariable,
|
||||
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)
|
||||
from esphome.cpp_helpers import ( # noqa
|
||||
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_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
|
||||
from esphome.core import CORE, coroutine
|
||||
from esphome.core import CORE, coroutine, coroutine_with_priority
|
||||
from esphome.py_compat import string_types
|
||||
from esphome.util import Registry
|
||||
|
||||
@ -282,7 +282,8 @@ def new_binary_sensor(config):
|
||||
|
||||
BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id({
|
||||
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)
|
||||
def binary_sensor_is_on_to_code(config, condition_id, template_arg, args):
|
||||
paren = yield cg.get_variable(config[CONF_ID])
|
||||
yield cg.new_Pvariable(condition_id, template_arg, paren, True, config.get(CONF_FOR))
|
||||
yield cg.new_Pvariable(condition_id, template_arg, paren, True)
|
||||
|
||||
|
||||
@automation.register_condition('binary_sensor.is_off', BinarySensorCondition,
|
||||
BINARY_SENSOR_CONDITION_SCHEMA)
|
||||
def binary_sensor_is_off_to_code(config, condition_id, template_arg, args):
|
||||
paren = yield cg.get_variable(config[CONF_ID])
|
||||
yield cg.new_Pvariable(condition_id, template_arg, paren, False, config.get(CONF_FOR))
|
||||
yield cg.new_Pvariable(condition_id, template_arg, paren, False)
|
||||
|
||||
|
||||
@coroutine_with_priority(100.0)
|
||||
def to_code(config):
|
||||
cg.add_define('USE_BINARY_SENSOR')
|
||||
cg.add_global(binary_sensor_ns.using)
|
||||
|
@ -125,22 +125,12 @@ class StateTrigger : public Trigger<bool> {
|
||||
|
||||
template<typename... Ts> class BinarySensorCondition : public Condition<Ts...> {
|
||||
public:
|
||||
BinarySensorCondition(BinarySensor *parent, bool state, uint32_t for_time = 0)
|
||||
: parent_(parent), state_(state), for_time_(for_time) {
|
||||
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_;
|
||||
}
|
||||
BinarySensorCondition(BinarySensor *parent, bool state) : parent_(parent), state_(state) {}
|
||||
bool check(Ts... x) override { return this->parent_->state == this->state_; }
|
||||
|
||||
protected:
|
||||
BinarySensor *parent_;
|
||||
bool state_;
|
||||
uint32_t last_state_time_{0};
|
||||
uint32_t for_time_{0};
|
||||
};
|
||||
|
||||
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_TARGET_TEMPERATURE_HIGH, CONF_TARGET_TEMPERATURE_LOW, CONF_TEMPERATURE_STEP, CONF_VISUAL, \
|
||||
CONF_MQTT_ID, CONF_NAME
|
||||
from esphome.core import CORE, coroutine
|
||||
from esphome.core import CORE, coroutine, coroutine_with_priority
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
@ -15,7 +15,6 @@ climate_ns = cg.esphome_ns.namespace('climate')
|
||||
ClimateDevice = climate_ns.class_('Climate', cg.Nameable)
|
||||
ClimateCall = climate_ns.class_('ClimateCall')
|
||||
ClimateTraits = climate_ns.class_('ClimateTraits')
|
||||
# MQTTClimateComponent = climate_ns.class_('MQTTClimateComponent', mqtt.MQTTComponent)
|
||||
|
||||
ClimateMode = climate_ns.enum('ClimateMode')
|
||||
CLIMATE_MODES = {
|
||||
@ -100,6 +99,7 @@ def climate_control_to_code(config, action_id, template_arg, args):
|
||||
yield var
|
||||
|
||||
|
||||
@coroutine_with_priority(100.0)
|
||||
def to_code(config):
|
||||
cg.add_define('USE_CLIMATE')
|
||||
cg.add_global(climate_ns.using)
|
||||
|
@ -18,7 +18,8 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
|
||||
|
||||
void play(Ts... x) override {
|
||||
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_high(this->target_temperature_high_.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.const import CONF_ID, CONF_INTERNAL, CONF_DEVICE_CLASS, CONF_STATE, \
|
||||
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
|
||||
|
||||
@ -124,6 +124,7 @@ def cover_control_to_code(config, action_id, template_arg, args):
|
||||
yield var
|
||||
|
||||
|
||||
@coroutine_with_priority(100.0)
|
||||
def to_code(config):
|
||||
cg.add_define('USE_COVER')
|
||||
cg.add_global(cover_ns.using)
|
||||
|
@ -4,7 +4,7 @@ import esphome.config_validation as cv
|
||||
from esphome import core, automation
|
||||
from esphome.automation import maybe_simple_id
|
||||
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
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@coroutine_with_priority(100.0)
|
||||
def to_code(config):
|
||||
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, \
|
||||
CONF_OSCILLATION_COMMAND_TOPIC, CONF_OSCILLATION_STATE_TOPIC, CONF_SPEED, \
|
||||
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
|
||||
|
||||
@ -107,6 +107,7 @@ def fan_turn_on_to_code(config, action_id, template_arg, args):
|
||||
yield var
|
||||
|
||||
|
||||
@coroutine_with_priority(100.0)
|
||||
def to_code(config):
|
||||
cg.add_define('USE_FAN')
|
||||
cg.add_global(fan_ns.using)
|
||||
|
@ -1,23 +1,26 @@
|
||||
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.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
|
||||
|
||||
globals_ns = cg.esphome_ns.namespace('globals')
|
||||
GlobalsComponent = globals_ns.class_('GlobalsComponent', cg.Component)
|
||||
GlobalVarSetAction = globals_ns.class_('GlobalVarSetAction', automation.Action)
|
||||
|
||||
MULTI_CONF = True
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.Required(CONF_ID): cv.declare_id(GlobalsComponent),
|
||||
cv.Required(CONF_TYPE): 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)
|
||||
|
||||
|
||||
# Run with low priority so that namespaces are registered first
|
||||
@coroutine_with_priority(-100.0)
|
||||
def to_code(config):
|
||||
type_ = cg.RawExpression(config[CONF_TYPE])
|
||||
template_args = cg.TemplateArguments(type_)
|
||||
@ -31,9 +34,22 @@ def to_code(config):
|
||||
glob = cg.Pvariable(config[CONF_ID], rhs, type=res_type)
|
||||
yield cg.register_component(glob, config)
|
||||
|
||||
if config.get(CONF_RESTORE_VALUE, False):
|
||||
if config[CONF_RESTORE_VALUE]:
|
||||
value = config[CONF_ID].id
|
||||
if IS_PY3 and isinstance(value, str):
|
||||
value = value.encode()
|
||||
hash_ = int(hashlib.md5(value).hexdigest()[:8], 16)
|
||||
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 {
|
||||
public:
|
||||
using value_type = T;
|
||||
explicit GlobalsComponent() = default;
|
||||
explicit GlobalsComponent(T initial_value) : 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_;
|
||||
};
|
||||
|
||||
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 esphome
|
||||
|
@ -3,7 +3,7 @@ import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.const import CONF_FREQUENCY, CONF_ID, CONF_SCAN, CONF_SCL, CONF_SDA, CONF_ADDRESS, \
|
||||
CONF_I2C_ID
|
||||
from esphome.core import coroutine
|
||||
from esphome.core import coroutine, coroutine_with_priority
|
||||
|
||||
i2c_ns = cg.esphome_ns.namespace('i2c')
|
||||
I2CComponent = i2c_ns.class_('I2CComponent', cg.Component)
|
||||
@ -20,7 +20,9 @@ CONFIG_SCHEMA = cv.Schema({
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
@coroutine_with_priority(1.0)
|
||||
def to_code(config):
|
||||
cg.add_global(i2c_ns.using)
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
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_scan(config[CONF_SCAN]))
|
||||
cg.add_library('Wire', None)
|
||||
cg.add_global(i2c_ns.using)
|
||||
|
||||
|
||||
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
|
||||
from esphome.core import coroutine_with_priority
|
||||
|
||||
json_ns = cg.esphome_ns.namespace('json')
|
||||
|
||||
|
||||
@coroutine_with_priority(1.0)
|
||||
def to_code(config):
|
||||
cg.add_library('ArduinoJson-esphomelib', '5.13.3')
|
||||
cg.add_define('USE_JSON')
|
||||
|
@ -1,296 +1,17 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.automation import maybe_simple_id
|
||||
from esphome.components import mqtt
|
||||
from esphome.const import CONF_ALPHA, CONF_BLUE, CONF_BRIGHTNESS, CONF_COLORS, CONF_COLOR_CORRECT, \
|
||||
CONF_COLOR_TEMPERATURE, CONF_DEFAULT_TRANSITION_LENGTH, CONF_DURATION, CONF_EFFECT, \
|
||||
CONF_EFFECTS, CONF_FLASH_LENGTH, CONF_GAMMA_CORRECT, CONF_GREEN, CONF_ID, \
|
||||
CONF_INTERNAL, CONF_LAMBDA, CONF_NAME, CONF_NUM_LEDS, CONF_RANDOM, CONF_RED, \
|
||||
CONF_SPEED, CONF_STATE, CONF_TRANSITION_LENGTH, CONF_UPDATE_INTERVAL, CONF_WHITE, CONF_WIDTH, \
|
||||
CONF_MQTT_ID
|
||||
from esphome.core import coroutine
|
||||
from esphome.util import Registry
|
||||
from esphome.const import CONF_COLOR_CORRECT, \
|
||||
CONF_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, CONF_GAMMA_CORRECT, CONF_ID, \
|
||||
CONF_INTERNAL, CONF_NAME, CONF_MQTT_ID
|
||||
from esphome.core import coroutine, coroutine_with_priority
|
||||
from .automation import light_control_to_code # noqa
|
||||
from .effects import validate_effects, BINARY_EFFECTS, \
|
||||
MONOCHROMATIC_EFFECTS, RGB_EFFECTS, ADDRESSABLE_EFFECTS, EFFECTS_REGISTRY
|
||||
from .types import ( # noqa
|
||||
LightState, AddressableLightState, light_ns, LightOutput, AddressableLight)
|
||||
|
||||
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({
|
||||
cv.GenerateID(): cv.declare_id(LightState),
|
||||
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)
|
||||
|
||||
|
||||
@automation.register_action('light.toggle', ToggleAction, 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 = 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
|
||||
|
||||
|
||||
@coroutine_with_priority(100.0)
|
||||
def to_code(config):
|
||||
cg.add_define('USE_LIGHT')
|
||||
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)),
|
||||
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 {
|
||||
@ -168,72 +172,7 @@ struct ESPHSVColor {
|
||||
inline ESPHSVColor(uint8_t hue, uint8_t saturation, uint8_t value) ALWAYS_INLINE : hue(hue),
|
||||
saturation(saturation),
|
||||
value(value) {}
|
||||
ESPColor to_rgb() const {
|
||||
// based on FastLED's hsv rainbow to rgb
|
||||
const uint8_t hue = this->hue;
|
||||
const uint8_t sat = this->saturation;
|
||||
const uint8_t val = this->value;
|
||||
// upper 3 hue bits are for branch selection, lower 5 are for values
|
||||
const uint8_t offset8 = (hue & 0x1F) << 3; // 0..248
|
||||
// third of the offset, 255/3 = 85 (actually only up to 82; 164)
|
||||
const uint8_t third = esp_scale8(offset8, 85);
|
||||
const uint8_t two_thirds = esp_scale8(offset8, 170);
|
||||
ESPColor rgb(255, 255, 255, 0);
|
||||
switch (hue >> 5) {
|
||||
case 0b000:
|
||||
rgb.r = 255 - third;
|
||||
rgb.g = third;
|
||||
rgb.b = 0;
|
||||
break;
|
||||
case 0b001:
|
||||
rgb.r = 171;
|
||||
rgb.g = 85 + third;
|
||||
rgb.b = 0;
|
||||
break;
|
||||
case 0b010:
|
||||
rgb.r = 171 - two_thirds;
|
||||
rgb.g = 170 + third;
|
||||
rgb.b = 0;
|
||||
break;
|
||||
case 0b011:
|
||||
rgb.r = 0;
|
||||
rgb.g = 255 - third;
|
||||
rgb.b = third;
|
||||
break;
|
||||
case 0b100:
|
||||
rgb.r = 0;
|
||||
rgb.g = 171 - two_thirds;
|
||||
rgb.b = 85 + two_thirds;
|
||||
break;
|
||||
case 0b101:
|
||||
rgb.r = third;
|
||||
rgb.g = 0;
|
||||
rgb.b = 255 - third;
|
||||
break;
|
||||
case 0b110:
|
||||
rgb.r = 85 + third;
|
||||
rgb.g = 0;
|
||||
rgb.b = 171 - third;
|
||||
break;
|
||||
case 0b111:
|
||||
rgb.r = 170 + third;
|
||||
rgb.g = 0;
|
||||
rgb.b = 85 - third;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// low saturation -> add uniform color to orig. hue
|
||||
// high saturation -> use hue directly
|
||||
// scales with square of saturation
|
||||
// (r,g,b) = (r,g,b) * sat + (1 - sat)^2
|
||||
rgb *= sat;
|
||||
const uint8_t desat = 255 - sat;
|
||||
rgb += esp_scale8(desat, desat);
|
||||
// (r,g,b) = (r,g,b) * val
|
||||
rgb *= val;
|
||||
return rgb;
|
||||
}
|
||||
ESPColor to_rgb() const;
|
||||
};
|
||||
|
||||
class ESPColorCorrection {
|
||||
@ -321,75 +260,85 @@ class ESPColorCorrection {
|
||||
uint8_t local_brightness_{255};
|
||||
};
|
||||
|
||||
class ESPColorView {
|
||||
class ESPColorSettable {
|
||||
public:
|
||||
inline ESPColorView(uint8_t *red, uint8_t *green, uint8_t *blue, uint8_t *white, uint8_t *effect_data,
|
||||
const ESPColorCorrection *color_correction) ALWAYS_INLINE : red_(red),
|
||||
green_(green),
|
||||
blue_(blue),
|
||||
white_(white),
|
||||
effect_data_(effect_data),
|
||||
color_correction_(color_correction) {}
|
||||
inline const ESPColorView &operator=(const ESPColor &rhs) const ALWAYS_INLINE {
|
||||
this->set(rhs);
|
||||
return *this;
|
||||
}
|
||||
inline const ESPColorView &operator=(const ESPHSVColor &rhs) const ALWAYS_INLINE {
|
||||
this->set(rhs);
|
||||
return *this;
|
||||
}
|
||||
inline void set(const ESPColor &color) const ALWAYS_INLINE { this->set_rgbw(color.r, color.g, color.b, color.w); }
|
||||
inline void set(const ESPHSVColor &color) const ALWAYS_INLINE {
|
||||
virtual void set(const ESPColor &color) = 0;
|
||||
virtual void set_red(uint8_t red) = 0;
|
||||
virtual void set_green(uint8_t green) = 0;
|
||||
virtual void set_blue(uint8_t blue) = 0;
|
||||
virtual void set_white(uint8_t white) = 0;
|
||||
virtual void set_effect_data(uint8_t effect_data) = 0;
|
||||
virtual void fade_to_white(uint8_t amnt) = 0;
|
||||
virtual void fade_to_black(uint8_t amnt) = 0;
|
||||
virtual void lighten(uint8_t delta) = 0;
|
||||
virtual void darken(uint8_t delta) = 0;
|
||||
void set(const ESPHSVColor &color) { this->set_hsv(color); }
|
||||
void set_hsv(const ESPHSVColor &color) {
|
||||
ESPColor rgb = color.to_rgb();
|
||||
this->set_rgb(rgb.r, rgb.g, rgb.b);
|
||||
}
|
||||
inline void set_red(uint8_t red) const ALWAYS_INLINE {
|
||||
*this->red_ = this->color_correction_->color_correct_red(red);
|
||||
}
|
||||
inline void set_green(uint8_t green) const ALWAYS_INLINE {
|
||||
*this->green_ = this->color_correction_->color_correct_green(green);
|
||||
}
|
||||
inline void set_blue(uint8_t blue) const ALWAYS_INLINE {
|
||||
*this->blue_ = this->color_correction_->color_correct_blue(blue);
|
||||
}
|
||||
inline void set_white(uint8_t white) const ALWAYS_INLINE {
|
||||
if (this->white_ == nullptr)
|
||||
return;
|
||||
*this->white_ = this->color_correction_->color_correct_white(white);
|
||||
}
|
||||
inline void set_rgb(uint8_t red, uint8_t green, uint8_t blue) const ALWAYS_INLINE {
|
||||
void set_rgb(uint8_t red, uint8_t green, uint8_t blue) {
|
||||
this->set_red(red);
|
||||
this->set_green(green);
|
||||
this->set_blue(blue);
|
||||
}
|
||||
inline void set_rgbw(uint8_t red, uint8_t green, uint8_t blue, uint8_t white) const ALWAYS_INLINE {
|
||||
void set_rgbw(uint8_t red, uint8_t green, uint8_t blue, uint8_t white) {
|
||||
this->set_rgb(red, green, blue);
|
||||
this->set_white(white);
|
||||
}
|
||||
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)
|
||||
return;
|
||||
*this->effect_data_ = effect_data;
|
||||
}
|
||||
inline ESPColor get() const ALWAYS_INLINE {
|
||||
return ESPColor(this->get_red(), this->get_green(), this->get_blue(), this->get_white());
|
||||
}
|
||||
inline uint8_t get_red() const ALWAYS_INLINE { return this->color_correction_->color_uncorrect_red(*this->red_); }
|
||||
inline uint8_t get_green() const ALWAYS_INLINE {
|
||||
return this->color_correction_->color_uncorrect_green(*this->green_);
|
||||
}
|
||||
inline uint8_t get_blue() const ALWAYS_INLINE { return this->color_correction_->color_uncorrect_blue(*this->blue_); }
|
||||
inline uint8_t get_white() const ALWAYS_INLINE {
|
||||
void fade_to_white(uint8_t amnt) override { this->set(this->get().fade_to_white(amnt)); }
|
||||
void fade_to_black(uint8_t amnt) override { this->set(this->get().fade_to_black(amnt)); }
|
||||
void lighten(uint8_t delta) override { this->set(this->get().lighten(delta)); }
|
||||
void darken(uint8_t delta) override { this->set(this->get().darken(delta)); }
|
||||
ESPColor get() const { return ESPColor(this->get_red(), this->get_green(), this->get_blue(), this->get_white()); }
|
||||
uint8_t get_red() const { return this->color_correction_->color_uncorrect_red(*this->red_); }
|
||||
uint8_t get_green() const { return this->color_correction_->color_uncorrect_green(*this->green_); }
|
||||
uint8_t get_blue() const { return this->color_correction_->color_uncorrect_blue(*this->blue_); }
|
||||
uint8_t get_white() const {
|
||||
if (this->white_ == nullptr)
|
||||
return 0;
|
||||
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)
|
||||
return 0;
|
||||
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;
|
||||
}
|
||||
|
||||
@ -402,11 +351,142 @@ class ESPColorView {
|
||||
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 {
|
||||
public:
|
||||
virtual int32_t size() const = 0;
|
||||
virtual ESPColorView operator[](int32_t index) const = 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_; }
|
||||
void set_effect_active(bool effect_active) { this->effect_active_ = effect_active; }
|
||||
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
|
||||
uint8_t(roundf(val.get_white() * val.get_state() * 255.0f)));
|
||||
|
||||
for (int i = 0; i < this->size(); i++) {
|
||||
(*this)[i] = color;
|
||||
}
|
||||
|
||||
this->all() = color;
|
||||
this->schedule_show();
|
||||
}
|
||||
void set_correction(float red, float green, float blue, float white = 1.0f) {
|
||||
|
@ -76,9 +76,9 @@ class AddressableRainbowLightEffect : public AddressableLightEffect {
|
||||
hsv.saturation = 240;
|
||||
uint16_t hue = (millis() * this->speed_) % 0xFFFF;
|
||||
const uint16_t add = 0xFFFF / this->width_;
|
||||
for (int i = 0; i < it.size(); i++) {
|
||||
for (auto var : it) {
|
||||
hsv.hue = hue >> 8;
|
||||
it[i] = hsv;
|
||||
var = hsv;
|
||||
hue += add;
|
||||
}
|
||||
}
|
||||
@ -107,15 +107,10 @@ class AddressableColorWipeEffect : public AddressableLightEffect {
|
||||
if (now - this->last_add_ < this->add_led_interval_)
|
||||
return;
|
||||
this->last_add_ = now;
|
||||
if (!this->reverse_) {
|
||||
for (int i = 0; i < it.size() - 1; i++) {
|
||||
it[i] = it[i + 1].get();
|
||||
}
|
||||
} else {
|
||||
for (int i = it.size() - 1; i > 0; i--) {
|
||||
it[i] = it[i - 1].get();
|
||||
}
|
||||
}
|
||||
if (this->reverse_)
|
||||
it.shift_left(1);
|
||||
else
|
||||
it.shift_right(1);
|
||||
const AddressableColorWipeEffectColor color = this->colors_[this->at_color_];
|
||||
const ESPColor esp_color = ESPColor(color.r, color.g, color.b, color.w);
|
||||
if (!this->reverse_) {
|
||||
@ -149,18 +144,14 @@ class AddressableScanEffect : public AddressableLightEffect {
|
||||
public:
|
||||
explicit AddressableScanEffect(const std::string &name) : AddressableLightEffect(name) {}
|
||||
void set_move_interval(uint32_t move_interval) { this->move_interval_ = move_interval; }
|
||||
void apply(AddressableLight &addressable, const ESPColor ¤t_color) override {
|
||||
for (int i = 0; i < addressable.size(); i++) {
|
||||
if (i == this->at_led_)
|
||||
addressable[i] = current_color;
|
||||
else
|
||||
addressable[i] = ESPColor(0, 0, 0, 0);
|
||||
}
|
||||
void apply(AddressableLight &it, const ESPColor ¤t_color) override {
|
||||
it.all() = ESPColor(0, 0, 0, 0);
|
||||
it[this->at_led_] = current_color;
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_move_ > this->move_interval_) {
|
||||
if (direction_) {
|
||||
this->at_led_++;
|
||||
if (this->at_led_ == addressable.size() - 1)
|
||||
if (this->at_led_ == it.size() - 1)
|
||||
this->direction_ = false;
|
||||
} else {
|
||||
this->at_led_--;
|
||||
@ -189,8 +180,7 @@ class AddressableTwinkleEffect : public AddressableLightEffect {
|
||||
pos_add = pos_add32;
|
||||
this->last_progress_ += pos_add32 * this->progress_interval_;
|
||||
}
|
||||
for (int i = 0; i < addressable.size(); i++) {
|
||||
ESPColorView view = addressable[i];
|
||||
for (auto view : addressable) {
|
||||
if (view.get_effect_data() != 0) {
|
||||
const uint8_t sine = half_sin8(view.get_effect_data());
|
||||
view = current_color * sine;
|
||||
@ -230,8 +220,8 @@ class AddressableRandomTwinkleEffect : public AddressableLightEffect {
|
||||
this->last_progress_ = now;
|
||||
}
|
||||
uint8_t subsine = ((8 * (now - this->last_progress_)) / this->progress_interval_) & 0b111;
|
||||
for (int i = 0; i < it.size(); i++) {
|
||||
ESPColorView view = it[i];
|
||||
for (auto &&i : it) {
|
||||
ESPColorView view = i;
|
||||
if (view.get_effect_data() != 0) {
|
||||
const uint8_t x = (view.get_effect_data() >> 3) & 0b11111;
|
||||
const uint8_t color = view.get_effect_data() & 0b111;
|
||||
@ -282,11 +272,11 @@ class AddressableFireworksEffect : public AddressableLightEffect {
|
||||
this->last_update_ = now;
|
||||
// "invert" the fade out parameter so that higher values make fade out faster
|
||||
const uint8_t fade_out_mult = 255u - this->fade_out_rate_;
|
||||
for (int i = 0; i < it.size(); i++) {
|
||||
ESPColor target = it[i].get() * fade_out_mult;
|
||||
for (auto view : it) {
|
||||
ESPColor target = view.get() * fade_out_mult;
|
||||
if (target.r < 64)
|
||||
target *= 170;
|
||||
it[i] = target;
|
||||
view = target;
|
||||
}
|
||||
int last = it.size() - 1;
|
||||
it[0].set(it[0].get() + (it[1].get() * 128));
|
||||
@ -326,9 +316,9 @@ class AddressableFlickerEffect : public AddressableLightEffect {
|
||||
return;
|
||||
this->last_update_ = now;
|
||||
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_;
|
||||
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; }
|
||||
|
@ -56,6 +56,30 @@ template<typename... Ts> class LightControlAction : public Action<Ts...> {
|
||||
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...> {
|
||||
public:
|
||||
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)
|
||||
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])
|
||||
|
||||
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])
|
||||
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({
|
||||
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_UNIT_OF_MEASUREMENT, \
|
||||
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
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
@ -305,6 +305,7 @@ def fit_linear(x, y):
|
||||
return k, b
|
||||
|
||||
|
||||
@coroutine_with_priority(40.0)
|
||||
def to_code(config):
|
||||
cg.add_define('USE_SENSOR')
|
||||
cg.add_global(sensor_ns.using)
|
||||
|
@ -3,7 +3,7 @@ import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.const import CONF_CLK_PIN, CONF_ID, CONF_MISO_PIN, CONF_MOSI_PIN, CONF_SPI_ID, \
|
||||
CONF_CS_PIN
|
||||
from esphome.core import coroutine
|
||||
from esphome.core import coroutine, coroutine_with_priority
|
||||
|
||||
spi_ns = cg.esphome_ns.namespace('spi')
|
||||
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))
|
||||
|
||||
|
||||
@coroutine_with_priority(1.0)
|
||||
def to_code(config):
|
||||
cg.add_global(spi_ns.using)
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
|
||||
@ -31,8 +33,6 @@ def to_code(config):
|
||||
mosi = yield cg.gpio_pin_expression(config[CONF_MOSI_PIN])
|
||||
cg.add(var.set_mosi(mosi))
|
||||
|
||||
cg.add_global(spi_ns.using)
|
||||
|
||||
|
||||
SPI_DEVICE_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(CONF_SPI_ID): cv.use_id(SPIComponent),
|
||||
|
@ -2,8 +2,8 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.const import CONF_ACCELERATION, CONF_DECELERATION, CONF_ID, CONF_MAX_SPEED, \
|
||||
CONF_POSITION, CONF_TARGET
|
||||
from esphome.core import CORE, coroutine
|
||||
CONF_POSITION, CONF_TARGET, CONF_SPEED
|
||||
from esphome.core import CORE, coroutine, coroutine_with_priority
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
@ -13,6 +13,7 @@ Stepper = stepper_ns.class_('Stepper')
|
||||
|
||||
SetTargetAction = stepper_ns.class_('SetTargetAction', automation.Action)
|
||||
ReportPositionAction = stepper_ns.class_('ReportPositionAction', automation.Action)
|
||||
SetSpeedAction = stepper_ns.class_('SetSpeedAction', automation.Action)
|
||||
|
||||
|
||||
def validate_acceleration(value):
|
||||
@ -103,5 +104,18 @@ def stepper_report_position_to_code(config, action_id, template_arg, args):
|
||||
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):
|
||||
cg.add_global(stepper_ns.using)
|
||||
|
@ -19,6 +19,7 @@ class Stepper {
|
||||
void set_acceleration(float acceleration) { this->acceleration_ = acceleration; }
|
||||
void set_deceleration(float deceleration) { this->deceleration_ = deceleration; }
|
||||
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; }
|
||||
|
||||
int32_t current_position{0};
|
||||
@ -60,5 +61,21 @@ template<typename... Ts> class ReportPositionAction : public Action<Ts...> {
|
||||
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 esphome
|
||||
|
@ -5,7 +5,7 @@ from esphome.automation import Condition, maybe_simple_id
|
||||
from esphome.components import mqtt
|
||||
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
|
||||
from esphome.core import CORE, coroutine
|
||||
from esphome.core import CORE, coroutine, coroutine_with_priority
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@coroutine_with_priority(100.0)
|
||||
def to_code(config):
|
||||
cg.add_global(switch_ns.using)
|
||||
cg.add_define('USE_SWITCH')
|
||||
|
@ -3,8 +3,8 @@ import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.components import mqtt
|
||||
from esphome.const import CONF_ICON, CONF_ID, CONF_INTERNAL, CONF_ON_VALUE, \
|
||||
CONF_TRIGGER_ID, CONF_MQTT_ID, CONF_NAME
|
||||
from esphome.core import CORE, coroutine
|
||||
CONF_TRIGGER_ID, CONF_MQTT_ID, CONF_NAME, CONF_STATE
|
||||
from esphome.core import CORE, coroutine, coroutine_with_priority
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
@ -16,6 +16,7 @@ TextSensorPtr = TextSensor.operator('ptr')
|
||||
TextSensorStateTrigger = text_sensor_ns.class_('TextSensorStateTrigger',
|
||||
automation.Trigger.template(cg.std_string))
|
||||
TextSensorPublishAction = text_sensor_ns.class_('TextSensorPublishAction', automation.Action)
|
||||
TextSensorStateCondition = text_sensor_ns.class_('TextSensorStateCondition', automation.Condition)
|
||||
|
||||
icon = cv.icon
|
||||
|
||||
@ -53,6 +54,19 @@ def register_text_sensor(var, config):
|
||||
yield setup_text_sensor_core_(var, config)
|
||||
|
||||
|
||||
@coroutine_with_priority(100.0)
|
||||
def to_code(config):
|
||||
cg.add_define('USE_TEXT_SENSOR')
|
||||
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...> {
|
||||
public:
|
||||
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, \
|
||||
CONF_MINUTES, CONF_MONTHS, CONF_ON_TIME, CONF_SECONDS, CONF_TIMEZONE, CONF_TRIGGER_ID, \
|
||||
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
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -293,6 +293,7 @@ def register_time(time_var, config):
|
||||
yield setup_time_core_(time_var, config)
|
||||
|
||||
|
||||
@coroutine_with_priority(100.0)
|
||||
def to_code(config):
|
||||
cg.add_define('USE_TIME')
|
||||
cg.add_global(time_ns.using)
|
||||
|
@ -120,6 +120,9 @@ def _lookup_module(domain, is_platform):
|
||||
try:
|
||||
module = importlib.import_module(path)
|
||||
except ImportError:
|
||||
import traceback
|
||||
_LOGGER.error("Unable to import component %s:", domain)
|
||||
traceback.print_exc()
|
||||
return None
|
||||
except Exception: # pylint: disable=broad-except
|
||||
import traceback
|
||||
@ -390,6 +393,7 @@ def validate_config(config):
|
||||
if component is None:
|
||||
result.add_str_error(u"Component not found: {}".format(domain), path)
|
||||
continue
|
||||
CORE.loaded_integrations.add(domain)
|
||||
|
||||
# Process AUTO_LOAD
|
||||
for load in component.auto_load:
|
||||
@ -432,6 +436,7 @@ def validate_config(config):
|
||||
if platform is None:
|
||||
result.add_str_error(u"Platform not found: '{}'".format(p_domain), path)
|
||||
continue
|
||||
CORE.loaded_integrations.add(p_name)
|
||||
|
||||
# Process AUTO_LOAD
|
||||
for load in platform.auto_load:
|
||||
|
@ -60,8 +60,6 @@ RESERVED_IDS = [
|
||||
'App', 'pinMode', 'delay', 'delayMicroseconds', 'digitalRead', 'digitalWrite', 'INPUT',
|
||||
'OUTPUT',
|
||||
'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)
|
||||
if isinstance(value, (dict, 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):
|
||||
return value
|
||||
if value is not None:
|
||||
@ -168,7 +168,7 @@ def boolean(value):
|
||||
check_not_templatable(value)
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
if isinstance(value, str):
|
||||
if isinstance(value, string_types):
|
||||
value = value.lower()
|
||||
if value in ('true', 'yes', 'on', 'enable'):
|
||||
return True
|
||||
@ -285,7 +285,10 @@ def validate_id_name(value):
|
||||
u"character and numbers. The character '{}' cannot be used"
|
||||
u"".format(char))
|
||||
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
|
||||
|
||||
|
||||
@ -330,7 +333,7 @@ def templatable(other_validators):
|
||||
|
||||
def validator(value):
|
||||
if isinstance(value, Lambda):
|
||||
return value
|
||||
return lambda_(value)
|
||||
if isinstance(other_validators, dict):
|
||||
return schema(value)
|
||||
return schema(value)
|
||||
@ -953,11 +956,22 @@ def enum(mapping, **kwargs):
|
||||
return validator
|
||||
|
||||
|
||||
LAMBDA_ENTITY_ID_PROG = re.compile(r'id\(\s*([a-zA-Z0-9_]+\.[.a-zA-Z0-9_]+)\s*\)')
|
||||
|
||||
|
||||
def lambda_(value):
|
||||
"""Coerce this configuration option to a lambda."""
|
||||
if isinstance(value, Lambda):
|
||||
return value
|
||||
return Lambda(string_strict(value))
|
||||
if not isinstance(value, Lambda):
|
||||
value = 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):
|
||||
|
@ -327,6 +327,7 @@ CONF_REPOSITORY = 'repository'
|
||||
CONF_RESET_PIN = 'reset_pin'
|
||||
CONF_RESIZE = 'resize'
|
||||
CONF_RESOLUTION = 'resolution'
|
||||
CONF_RESTORE = 'restore'
|
||||
CONF_RESTORE_MODE = 'restore_mode'
|
||||
CONF_RESTORE_STATE = 'restore_state'
|
||||
CONF_RESTORE_VALUE = 'restore_value'
|
||||
@ -397,6 +398,7 @@ CONF_THEN = 'then'
|
||||
CONF_THRESHOLD = 'threshold'
|
||||
CONF_THROTTLE = 'throttle'
|
||||
CONF_TILT = 'tilt'
|
||||
CONF_TIME = 'time'
|
||||
CONF_TIMEOUT = 'timeout'
|
||||
CONF_TIMES = 'times'
|
||||
CONF_TIMEZONE = 'timezone'
|
||||
|
@ -502,6 +502,8 @@ class EsphomeCore(object):
|
||||
# A dictionary of started coroutines, used to warn when a coroutine was not
|
||||
# awaited.
|
||||
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):
|
||||
self.dashboard = False
|
||||
@ -521,6 +523,7 @@ class EsphomeCore(object):
|
||||
self.build_flags = set()
|
||||
self.defines = set()
|
||||
self.active_coroutines = {}
|
||||
self.loaded_integrations = set()
|
||||
|
||||
@property
|
||||
def address(self): # type: () -> str
|
||||
|
@ -55,6 +55,32 @@ template<typename... Ts> class LambdaCondition : public Condition<Ts...> {
|
||||
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 {
|
||||
public:
|
||||
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; }
|
||||
|
||||
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
|
||||
|
@ -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.
|
||||
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 {
|
||||
public:
|
||||
|
@ -151,7 +151,7 @@ def add_includes(includes):
|
||||
# Add includes at the very end, so that the included files can access global variables
|
||||
for include in includes:
|
||||
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)))
|
||||
|
||||
|
||||
|
@ -72,6 +72,9 @@ class ExpressionList(Expression):
|
||||
text = u", ".join(text_type(x) for x in self.args)
|
||||
return indent_all_but_first_and_last(text)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.args)
|
||||
|
||||
|
||||
class TemplateArguments(Expression):
|
||||
def __init__(self, *args): # type: (*SafeExpType) -> None
|
||||
@ -81,6 +84,9 @@ class TemplateArguments(Expression):
|
||||
def __str__(self):
|
||||
return u'<{}>'.format(self.args)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.args)
|
||||
|
||||
|
||||
class CallExpression(Expression):
|
||||
def __init__(self, base, *args): # type: (Expression, *SafeExpType) -> None
|
||||
@ -400,7 +406,7 @@ def 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
|
||||
with the given arguments.
|
||||
@ -480,6 +486,21 @@ def get_variable(id): # type: (ID) -> Generator[MockObj]
|
||||
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
|
||||
def process_lambda(value, # type: Lambda
|
||||
parameters, # type: List[Tuple[SafeExpType, str]]
|
||||
@ -572,7 +593,7 @@ class MockObj(Expression):
|
||||
attr = attr[1:]
|
||||
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)
|
||||
return MockObj(call, self.op)
|
||||
|
||||
@ -583,37 +604,32 @@ class MockObj(Expression):
|
||||
return u'MockObj<{}>'.format(text_type(self.base))
|
||||
|
||||
@property
|
||||
def _(self):
|
||||
def _(self): # type: () -> MockObj
|
||||
return MockObj(u'{}{}'.format(self.base, self.op))
|
||||
|
||||
@property
|
||||
def new(self):
|
||||
def new(self): # type: () -> MockObj
|
||||
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):
|
||||
args = TemplateArguments(*args)
|
||||
else:
|
||||
args = args[0]
|
||||
obj = MockObj(u'{}{}'.format(self.base, args))
|
||||
return obj
|
||||
return MockObj(u'{}{}'.format(self.base, args))
|
||||
|
||||
def namespace(self, name): # type: (str) -> MockObj
|
||||
obj = MockObj(u'{}{}{}'.format(self.base, self.op, name), u'::')
|
||||
return obj
|
||||
return MockObj(u'{}{}'.format(self._, name), u'::')
|
||||
|
||||
def class_(self, name, *parents): # type: (str, *MockObjClass) -> MockObjClass
|
||||
op = '' if self.op == '' else '::'
|
||||
obj = MockObjClass(u'{}{}{}'.format(self.base, op, name), u'.', parents=parents)
|
||||
return obj
|
||||
return MockObjClass(u'{}{}{}'.format(self.base, op, name), u'.', parents=parents)
|
||||
|
||||
def struct(self, name): # type: (str) -> MockObjClass
|
||||
return self.class_(name)
|
||||
|
||||
def enum(self, name, is_class=False): # type: (str, bool) -> MockObj
|
||||
if is_class:
|
||||
return self.namespace(name)
|
||||
return self
|
||||
return MockObjEnum(enum=name, is_class=is_class, base=self.base, op=self.op)
|
||||
|
||||
def operator(self, name): # type: (str) -> MockObj
|
||||
if name == 'ref':
|
||||
@ -625,7 +641,7 @@ class MockObj(Expression):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def using(self):
|
||||
def using(self): # type: () -> MockObj
|
||||
assert self.op == '::'
|
||||
return MockObj(u'using namespace {}'.format(self.base))
|
||||
|
||||
@ -637,6 +653,26 @@ class MockObj(Expression):
|
||||
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):
|
||||
def __init__(self, *args, **kwargs):
|
||||
parens = kwargs.pop('parents')
|
||||
@ -657,10 +693,8 @@ class MockObjClass(MockObj):
|
||||
return True
|
||||
return False
|
||||
|
||||
def template(self,
|
||||
*args # type: Tuple[Union[TemplateArguments, Expression]]
|
||||
):
|
||||
# type: (...) -> MockObjClass
|
||||
def template(self, *args):
|
||||
# type: (*SafeExpType) -> MockObjClass
|
||||
if len(args) != 1 or not isinstance(args[0], TemplateArguments):
|
||||
args = TemplateArguments(*args)
|
||||
else:
|
||||
|
Loading…
Reference in New Issue
Block a user