This commit is contained in:
Otto Winter 2019-04-24 23:49:02 +02:00
parent 0a0713f0e2
commit 766f6c045d
No known key found for this signature in database
GPG Key ID: DB66C0BE6013F97E
44 changed files with 1202 additions and 592 deletions

View File

@ -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)

View File

@ -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,

View File

@ -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)

View File

@ -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...> {

View File

@ -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)

View File

@ -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...));

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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):

View 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

View 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

View 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)

View File

@ -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')

View File

@ -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)

View 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

View File

@ -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),
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);
}
void set_rgb(uint8_t red, uint8_t green, uint8_t blue) {
this->set_red(red);
this->set_green(green);
this->set_blue(blue);
}
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);
}
};
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) {}
inline const ESPColorView &operator=(const ESPColor &rhs) const ALWAYS_INLINE {
ESPColorView &operator=(const ESPColor &rhs) {
this->set(rhs);
return *this;
}
inline const ESPColorView &operator=(const ESPHSVColor &rhs) const ALWAYS_INLINE {
this->set(rhs);
ESPColorView &operator=(const ESPHSVColor &rhs) {
this->set_hsv(rhs);
return *this;
}
inline void set(const ESPColor &color) const ALWAYS_INLINE { this->set_rgbw(color.r, color.g, color.b, color.w); }
inline void set(const ESPHSVColor &color) const ALWAYS_INLINE {
ESPColor rgb = color.to_rgb();
this->set_rgb(rgb.r, rgb.g, rgb.b);
}
inline void set_red(uint8_t red) const ALWAYS_INLINE {
*this->red_ = this->color_correction_->color_correct_red(red);
}
inline void set_green(uint8_t green) const ALWAYS_INLINE {
*this->green_ = this->color_correction_->color_correct_green(green);
}
inline void set_blue(uint8_t blue) const ALWAYS_INLINE {
*this->blue_ = this->color_correction_->color_correct_blue(blue);
}
inline void set_white(uint8_t white) const ALWAYS_INLINE {
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);
}
inline void set_rgb(uint8_t red, uint8_t green, uint8_t blue) const ALWAYS_INLINE {
this->set_red(red);
this->set_green(green);
this->set_blue(blue);
}
inline void set_rgbw(uint8_t red, uint8_t green, uint8_t blue, uint8_t white) const ALWAYS_INLINE {
this->set_rgb(red, green, blue);
this->set_white(white);
}
inline void set_effect_data(uint8_t effect_data) const ALWAYS_INLINE {
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) {

View File

@ -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 &current_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 &current_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; }

View File

@ -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) {}

View 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

View 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

View 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)

View File

@ -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),

View File

@ -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)

View File

@ -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),

View File

@ -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)

View File

@ -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

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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) {}

View File

@ -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)

View File

@ -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:

View File

@ -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):
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
return Lambda(string_strict(value))
def dimensions(value):

View File

@ -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'

View File

@ -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

View File

@ -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) {}

View File

@ -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

View File

@ -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:

View File

@ -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)))

View File

@ -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: