mirror of
https://github.com/Threnklyn/esphome-dev.git
synced 2026-06-03 19:38:30 +02:00
🏗 Merge C++ into python codebase (#504)
## Description: Move esphome-core codebase into esphome (and a bunch of other refactors). See https://github.com/esphome/feature-requests/issues/97 Yes this is a shit ton of work and no there's no way to automate it :( But it will be worth it 👍 Progress: - Core support (file copy etc): 80% - Base Abstractions (light, switch): ~50% - Integrations: ~10% - Working? Yes, (but only with ported components). Other refactors: - Moves all codegen related stuff into a single class: `esphome.codegen` (imported as `cg`) - Rework coroutine syntax - Move from `component/platform.py` to `domain/component.py` structure as with HA - Move all defaults out of C++ and into config validation. - Remove `make_...` helpers from Application class. Reason: Merge conflicts with every single new integration. - Pointer Variables are stored globally instead of locally in setup(). Reason: stack size limit. Future work: - Rework const.py - Move all `CONF_...` into a conf class (usage `conf.UPDATE_INTERVAL` vs `CONF_UPDATE_INTERVAL`). Reason: Less convoluted import block - Enable loading from `custom_components` folder. **Related issue (if applicable):** https://github.com/esphome/feature-requests/issues/97 **Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** esphome/esphome-docs#<esphome-docs PR number goes here> ## Checklist: - [ ] The code change is tested and works locally. - [ ] Tests have been added to verify that the new code works (under `tests/` folder). If user exposed functionality or configuration variables are added/changed: - [ ] Documentation added/updated in [esphomedocs](https://github.com/OttoWinter/esphomedocs).
This commit is contained in:
@@ -1,42 +1,33 @@
|
||||
import voluptuous as vol
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.automation import ACTION_REGISTRY, maybe_simple_id
|
||||
from esphome.components import mqtt
|
||||
from esphome.components.mqtt import setup_mqtt_component
|
||||
import esphome.config_validation as cv
|
||||
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_EFFECT_ID, CONF_FLASH_LENGTH, CONF_GAMMA_CORRECT, CONF_GREEN, CONF_ID, \
|
||||
CONF_INTERNAL, CONF_LAMBDA, CONF_MQTT_ID, CONF_NAME, CONF_NUM_LEDS, CONF_RANDOM, CONF_RED, \
|
||||
CONF_SPEED, CONF_STATE, CONF_TRANSITION_LENGTH, CONF_UPDATE_INTERVAL, CONF_WHITE, CONF_WIDTH
|
||||
from esphome.core import CORE, coroutine
|
||||
from esphome.cpp_generator import Pvariable, StructInitializer, add, get_variable, process_lambda, \
|
||||
templatable
|
||||
from esphome.cpp_types import Action, Application, Component, Nameable, esphome_ns, std_string, \
|
||||
uint32, void
|
||||
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 ServiceRegistry
|
||||
|
||||
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
|
||||
|
||||
})
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
# Base
|
||||
light_ns = esphome_ns.namespace('light')
|
||||
LightState = light_ns.class_('LightState', Nameable, Component)
|
||||
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)
|
||||
MakeLight = Application.struct('MakeLight')
|
||||
LightOutput = light_ns.class_('LightOutput')
|
||||
AddressableLight = light_ns.class_('AddressableLight')
|
||||
AddressableLightRef = AddressableLight.operator('ref')
|
||||
|
||||
# Actions
|
||||
ToggleAction = light_ns.class_('ToggleAction', Action)
|
||||
LightControlAction = light_ns.class_('LightControlAction', Action)
|
||||
ToggleAction = light_ns.class_('ToggleAction', cg.Action)
|
||||
LightControlAction = light_ns.class_('LightControlAction', cg.Action)
|
||||
|
||||
LightColorValues = light_ns.class_('LightColorValues')
|
||||
|
||||
MQTTJSONLightComponent = light_ns.class_('MQTTJSONLightComponent', mqtt.MQTTComponent)
|
||||
|
||||
# Effects
|
||||
LightEffect = light_ns.class_('LightEffect')
|
||||
RandomLightEffect = light_ns.class_('RandomLightEffect', LightEffect)
|
||||
@@ -87,435 +78,370 @@ ADDRESSABLE_EFFECTS = RGB_EFFECTS + [CONF_ADDRESSABLE_LAMBDA, CONF_ADDRESSABLE_R
|
||||
CONF_ADDRESSABLE_TWINKLE, CONF_ADDRESSABLE_RANDOM_TWINKLE,
|
||||
CONF_ADDRESSABLE_FIREWORKS, CONF_ADDRESSABLE_FLICKER]
|
||||
|
||||
EFFECTS_SCHEMA = cv.Schema({
|
||||
vol.Optional(CONF_LAMBDA): cv.Schema({
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
vol.Required(CONF_LAMBDA): cv.lambda_,
|
||||
vol.Optional(CONF_UPDATE_INTERVAL, default='0ms'): cv.positive_time_period_milliseconds,
|
||||
}),
|
||||
vol.Optional(CONF_RANDOM): cv.Schema({
|
||||
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(RandomLightEffect),
|
||||
vol.Optional(CONF_NAME, default="Random"): cv.string,
|
||||
vol.Optional(CONF_TRANSITION_LENGTH): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds,
|
||||
}),
|
||||
vol.Optional(CONF_STROBE): cv.Schema({
|
||||
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(StrobeLightEffect),
|
||||
vol.Optional(CONF_NAME, default="Strobe"): cv.string,
|
||||
vol.Optional(CONF_COLORS): vol.All(cv.ensure_list(cv.Schema({
|
||||
vol.Optional(CONF_STATE, default=True): cv.boolean,
|
||||
vol.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage,
|
||||
vol.Optional(CONF_RED, default=1.0): cv.percentage,
|
||||
vol.Optional(CONF_GREEN, default=1.0): cv.percentage,
|
||||
vol.Optional(CONF_BLUE, default=1.0): cv.percentage,
|
||||
vol.Optional(CONF_WHITE, default=1.0): cv.percentage,
|
||||
vol.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)), vol.Length(min=2)),
|
||||
}),
|
||||
vol.Optional(CONF_FLICKER): cv.Schema({
|
||||
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(FlickerLightEffect),
|
||||
vol.Optional(CONF_NAME, default="Flicker"): cv.string,
|
||||
vol.Optional(CONF_ALPHA): cv.percentage,
|
||||
vol.Optional(CONF_INTENSITY): cv.percentage,
|
||||
}),
|
||||
vol.Optional(CONF_ADDRESSABLE_LAMBDA): cv.Schema({
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
vol.Required(CONF_LAMBDA): cv.lambda_,
|
||||
vol.Optional(CONF_UPDATE_INTERVAL, default='0ms'): cv.positive_time_period_milliseconds,
|
||||
}),
|
||||
vol.Optional(CONF_ADDRESSABLE_RAINBOW): cv.Schema({
|
||||
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(AddressableRainbowLightEffect),
|
||||
vol.Optional(CONF_NAME, default="Rainbow"): cv.string,
|
||||
vol.Optional(CONF_SPEED): cv.uint32_t,
|
||||
vol.Optional(CONF_WIDTH): cv.uint32_t,
|
||||
}),
|
||||
vol.Optional(CONF_ADDRESSABLE_COLOR_WIPE): cv.Schema({
|
||||
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(AddressableColorWipeEffect),
|
||||
vol.Optional(CONF_NAME, default="Color Wipe"): cv.string,
|
||||
vol.Optional(CONF_COLORS): cv.ensure_list({
|
||||
vol.Optional(CONF_RED, default=1.0): cv.percentage,
|
||||
vol.Optional(CONF_GREEN, default=1.0): cv.percentage,
|
||||
vol.Optional(CONF_BLUE, default=1.0): cv.percentage,
|
||||
vol.Optional(CONF_WHITE, default=1.0): cv.percentage,
|
||||
vol.Optional(CONF_RANDOM, default=False): cv.boolean,
|
||||
vol.Required(CONF_NUM_LEDS): vol.All(cv.uint32_t, vol.Range(min=1)),
|
||||
}),
|
||||
vol.Optional(CONF_ADD_LED_INTERVAL): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_REVERSE): cv.boolean,
|
||||
}),
|
||||
vol.Optional(CONF_ADDRESSABLE_SCAN): cv.Schema({
|
||||
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(AddressableScanEffect),
|
||||
vol.Optional(CONF_NAME, default="Scan"): cv.string,
|
||||
vol.Optional(CONF_MOVE_INTERVAL): cv.positive_time_period_milliseconds,
|
||||
}),
|
||||
vol.Optional(CONF_ADDRESSABLE_TWINKLE): cv.Schema({
|
||||
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(AddressableTwinkleEffect),
|
||||
vol.Optional(CONF_NAME, default="Twinkle"): cv.string,
|
||||
vol.Optional(CONF_TWINKLE_PROBABILITY): cv.percentage,
|
||||
vol.Optional(CONF_PROGRESS_INTERVAL): cv.positive_time_period_milliseconds,
|
||||
}),
|
||||
vol.Optional(CONF_ADDRESSABLE_RANDOM_TWINKLE): cv.Schema({
|
||||
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(AddressableRandomTwinkleEffect),
|
||||
vol.Optional(CONF_NAME, default="Random Twinkle"): cv.string,
|
||||
vol.Optional(CONF_TWINKLE_PROBABILITY): cv.percentage,
|
||||
vol.Optional(CONF_PROGRESS_INTERVAL): cv.positive_time_period_milliseconds,
|
||||
}),
|
||||
vol.Optional(CONF_ADDRESSABLE_FIREWORKS): cv.Schema({
|
||||
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(AddressableFireworksEffect),
|
||||
vol.Optional(CONF_NAME, default="Fireworks"): cv.string,
|
||||
vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_SPARK_PROBABILITY): cv.percentage,
|
||||
vol.Optional(CONF_USE_RANDOM_COLOR): cv.boolean,
|
||||
vol.Optional(CONF_FADE_OUT_RATE): cv.uint8_t,
|
||||
}),
|
||||
vol.Optional(CONF_ADDRESSABLE_FLICKER): cv.Schema({
|
||||
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(AddressableFlickerEffect),
|
||||
vol.Optional(CONF_NAME, default="Addressable Flicker"): cv.string,
|
||||
vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_INTENSITY): cv.percentage,
|
||||
}),
|
||||
EFFECTS_REGISTRY = ServiceRegistry()
|
||||
|
||||
|
||||
def register_effect(name, effect_type, default_name, schema, *extra_validators):
|
||||
schema = cv.Schema(schema).extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(effect_type),
|
||||
cv.Optional(CONF_NAME, default=default_name): cv.string_strict,
|
||||
})
|
||||
validator = cv.All(schema, *extra_validators)
|
||||
register = EFFECTS_REGISTRY.register(name, validator)
|
||||
|
||||
return register
|
||||
|
||||
|
||||
@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):
|
||||
lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], [], return_type=cg.void)
|
||||
yield cg.new_Pvariable(config[CONF_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 = cg.new_Pvariable(config[CONF_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):
|
||||
var = cg.new_Pvariable(config[CONF_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):
|
||||
var = cg.new_Pvariable(config[CONF_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):
|
||||
args = [(AddressableLightRef, 'it')]
|
||||
lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], args, return_type=cg.void)
|
||||
var = cg.new_Pvariable(config[CONF_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):
|
||||
var = cg.new_Pvariable(config[CONF_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):
|
||||
var = cg.new_Pvariable(config[CONF_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):
|
||||
var = cg.new_Pvariable(config[CONF_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):
|
||||
var = cg.new_Pvariable(config[CONF_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):
|
||||
var = cg.new_Pvariable(config[CONF_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):
|
||||
var = cg.new_Pvariable(config[CONF_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):
|
||||
var = cg.new_Pvariable(config[CONF_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):
|
||||
is_list = isinstance(value, list)
|
||||
if not is_list:
|
||||
value = [value]
|
||||
names = set()
|
||||
ret = []
|
||||
value = cv.validate_registry('effect', EFFECTS_REGISTRY, [])(value)
|
||||
errors = []
|
||||
for i, effect in enumerate(value):
|
||||
path = [i] if is_list else []
|
||||
if not isinstance(effect, dict):
|
||||
errors.append(
|
||||
vol.Invalid("Each effect must be a dictionary, not {}".format(type(value)),
|
||||
path)
|
||||
)
|
||||
continue
|
||||
if len(effect) > 1:
|
||||
errors.append(
|
||||
vol.Invalid("Each entry in the 'effects:' option must be a single effect.",
|
||||
path)
|
||||
)
|
||||
continue
|
||||
if not effect:
|
||||
errors.append(
|
||||
vol.Invalid("Found no effect for the {}th entry in 'effects:'!".format(i),
|
||||
path)
|
||||
)
|
||||
continue
|
||||
key = next(iter(effect.keys()))
|
||||
if key.startswith('fastled'):
|
||||
errors.append(
|
||||
vol.Invalid("FastLED effects have been renamed to addressable effects. "
|
||||
"Please use '{}'".format(key.replace('fastled', 'addressable')),
|
||||
path)
|
||||
)
|
||||
continue
|
||||
names = set()
|
||||
for i, x in enumerate(value):
|
||||
key = next(it for it in x.keys())
|
||||
if key not in allowed_effects:
|
||||
errors.append(
|
||||
vol.Invalid("The effect '{}' does not exist or is not allowed for this "
|
||||
"light type".format(key), path)
|
||||
cv.Invalid("The effect '{}' is not allowed for this "
|
||||
"light type".format(key), [i])
|
||||
)
|
||||
continue
|
||||
effect[key] = effect[key] or {}
|
||||
try:
|
||||
conf = EFFECTS_SCHEMA(effect)
|
||||
except vol.Invalid as err:
|
||||
err.prepend(path)
|
||||
errors.append(err)
|
||||
continue
|
||||
name = conf[key][CONF_NAME]
|
||||
name = x[key][CONF_NAME]
|
||||
if name in names:
|
||||
errors.append(
|
||||
vol.Invalid(u"Found the effect name '{}' twice. All effects must have "
|
||||
u"unique names".format(name), [i])
|
||||
cv.Invalid(u"Found the effect name '{}' twice. All effects must have "
|
||||
u"unique names".format(name), [i])
|
||||
)
|
||||
continue
|
||||
names.add(name)
|
||||
ret.append(conf)
|
||||
if errors:
|
||||
raise vol.MultipleInvalid(errors)
|
||||
return ret
|
||||
raise cv.MultipleInvalid(errors)
|
||||
return value
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
LIGHT_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(LightState),
|
||||
cv.GenerateID(CONF_MQTT_ID): cv.declare_variable_id(MQTTJSONLightComponent),
|
||||
cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_variable_id(mqtt.MQTTJSONLightComponent),
|
||||
})
|
||||
|
||||
BINARY_LIGHT_SCHEMA = LIGHT_SCHEMA.extend({
|
||||
vol.Optional(CONF_EFFECTS): validate_effects(BINARY_EFFECTS),
|
||||
cv.Optional(CONF_EFFECTS): validate_effects(BINARY_EFFECTS),
|
||||
})
|
||||
|
||||
BRIGHTNESS_ONLY_LIGHT_SCHEMA = LIGHT_SCHEMA.extend({
|
||||
vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float,
|
||||
vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_EFFECTS): validate_effects(MONOCHROMATIC_EFFECTS),
|
||||
cv.Optional(CONF_GAMMA_CORRECT, default=2.8): cv.positive_float,
|
||||
cv.Optional(CONF_DEFAULT_TRANSITION_LENGTH, default='1s'): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_EFFECTS): validate_effects(MONOCHROMATIC_EFFECTS),
|
||||
})
|
||||
|
||||
RGB_LIGHT_SCHEMA = BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend({
|
||||
vol.Optional(CONF_EFFECTS): validate_effects(RGB_EFFECTS),
|
||||
cv.Optional(CONF_EFFECTS): validate_effects(RGB_EFFECTS),
|
||||
})
|
||||
|
||||
ADDRESSABLE_LIGHT_SCHEMA = RGB_LIGHT_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(AddressableLightState),
|
||||
vol.Optional(CONF_EFFECTS): validate_effects(ADDRESSABLE_EFFECTS),
|
||||
vol.Optional(CONF_COLOR_CORRECT): vol.All([cv.percentage], vol.Length(min=3, max=4)),
|
||||
cv.Optional(CONF_EFFECTS): validate_effects(ADDRESSABLE_EFFECTS),
|
||||
cv.Optional(CONF_COLOR_CORRECT): cv.All([cv.percentage], cv.Length(min=3, max=4)),
|
||||
})
|
||||
|
||||
|
||||
@coroutine
|
||||
def build_effect(full_config):
|
||||
key, config = next(iter(full_config.items()))
|
||||
if key == CONF_LAMBDA:
|
||||
lambda_ = yield process_lambda(config[CONF_LAMBDA], [], return_type=void)
|
||||
yield LambdaLightEffect.new(config[CONF_NAME], lambda_, config[CONF_UPDATE_INTERVAL])
|
||||
elif key == CONF_RANDOM:
|
||||
rhs = RandomLightEffect.new(config[CONF_NAME])
|
||||
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
|
||||
if CONF_TRANSITION_LENGTH in config:
|
||||
add(effect.set_transition_length(config[CONF_TRANSITION_LENGTH]))
|
||||
if CONF_UPDATE_INTERVAL in config:
|
||||
add(effect.set_update_interval(config[CONF_UPDATE_INTERVAL]))
|
||||
yield effect
|
||||
elif key == CONF_STROBE:
|
||||
rhs = StrobeLightEffect.new(config[CONF_NAME])
|
||||
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
|
||||
colors = []
|
||||
for color in config.get(CONF_COLORS, []):
|
||||
colors.append(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]),
|
||||
))
|
||||
if colors:
|
||||
add(effect.set_colors(colors))
|
||||
yield effect
|
||||
elif key == CONF_FLICKER:
|
||||
rhs = FlickerLightEffect.new(config[CONF_NAME])
|
||||
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
|
||||
if CONF_ALPHA in config:
|
||||
add(effect.set_alpha(config[CONF_ALPHA]))
|
||||
if CONF_INTENSITY in config:
|
||||
add(effect.set_intensity(config[CONF_INTENSITY]))
|
||||
yield effect
|
||||
elif key == CONF_ADDRESSABLE_LAMBDA:
|
||||
args = [(AddressableLightRef, 'it')]
|
||||
lambda_ = yield process_lambda(config[CONF_LAMBDA], args, return_type=void)
|
||||
yield AddressableLambdaLightEffect.new(config[CONF_NAME], lambda_,
|
||||
config[CONF_UPDATE_INTERVAL])
|
||||
elif key == CONF_ADDRESSABLE_RAINBOW:
|
||||
rhs = AddressableRainbowLightEffect.new(config[CONF_NAME])
|
||||
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
|
||||
if CONF_SPEED in config:
|
||||
add(effect.set_speed(config[CONF_SPEED]))
|
||||
if CONF_WIDTH in config:
|
||||
add(effect.set_width(config[CONF_WIDTH]))
|
||||
yield effect
|
||||
elif key == CONF_ADDRESSABLE_COLOR_WIPE:
|
||||
rhs = AddressableColorWipeEffect.new(config[CONF_NAME])
|
||||
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
|
||||
if CONF_ADD_LED_INTERVAL in config:
|
||||
add(effect.set_add_led_interval(config[CONF_ADD_LED_INTERVAL]))
|
||||
if CONF_REVERSE in config:
|
||||
add(effect.set_reverse(config[CONF_REVERSE]))
|
||||
colors = []
|
||||
for color in config.get(CONF_COLORS, []):
|
||||
colors.append(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]),
|
||||
))
|
||||
if colors:
|
||||
add(effect.set_colors(colors))
|
||||
yield effect
|
||||
elif key == CONF_ADDRESSABLE_SCAN:
|
||||
rhs = AddressableScanEffect.new(config[CONF_NAME])
|
||||
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
|
||||
if CONF_MOVE_INTERVAL in config:
|
||||
add(effect.set_move_interval(config[CONF_MOVE_INTERVAL]))
|
||||
yield effect
|
||||
elif key == CONF_ADDRESSABLE_TWINKLE:
|
||||
rhs = AddressableTwinkleEffect.new(config[CONF_NAME])
|
||||
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
|
||||
if CONF_TWINKLE_PROBABILITY in config:
|
||||
add(effect.set_twinkle_probability(config[CONF_TWINKLE_PROBABILITY]))
|
||||
if CONF_PROGRESS_INTERVAL in config:
|
||||
add(effect.set_progress_interval(config[CONF_PROGRESS_INTERVAL]))
|
||||
yield effect
|
||||
elif key == CONF_ADDRESSABLE_RANDOM_TWINKLE:
|
||||
rhs = AddressableRandomTwinkleEffect.new(config[CONF_NAME])
|
||||
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
|
||||
if CONF_TWINKLE_PROBABILITY in config:
|
||||
add(effect.set_twinkle_probability(config[CONF_TWINKLE_PROBABILITY]))
|
||||
if CONF_PROGRESS_INTERVAL in config:
|
||||
add(effect.set_progress_interval(config[CONF_PROGRESS_INTERVAL]))
|
||||
yield effect
|
||||
elif key == CONF_ADDRESSABLE_FIREWORKS:
|
||||
rhs = AddressableFireworksEffect.new(config[CONF_NAME])
|
||||
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
|
||||
if CONF_UPDATE_INTERVAL in config:
|
||||
add(effect.set_update_interval(config[CONF_UPDATE_INTERVAL]))
|
||||
if CONF_SPARK_PROBABILITY in config:
|
||||
add(effect.set_spark_probability(config[CONF_SPARK_PROBABILITY]))
|
||||
if CONF_USE_RANDOM_COLOR in config:
|
||||
add(effect.set_spark_probability(config[CONF_USE_RANDOM_COLOR]))
|
||||
if CONF_FADE_OUT_RATE in config:
|
||||
add(effect.set_spark_probability(config[CONF_FADE_OUT_RATE]))
|
||||
yield effect
|
||||
elif key == CONF_ADDRESSABLE_FLICKER:
|
||||
rhs = AddressableFlickerEffect.new(config[CONF_NAME])
|
||||
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
|
||||
if CONF_UPDATE_INTERVAL in config:
|
||||
add(effect.set_update_interval(config[CONF_UPDATE_INTERVAL]))
|
||||
if CONF_INTENSITY in config:
|
||||
add(effect.set_intensity(config[CONF_INTENSITY]))
|
||||
yield effect
|
||||
else:
|
||||
raise NotImplementedError("Effect {} not implemented".format(next(config.keys())))
|
||||
|
||||
|
||||
@coroutine
|
||||
def setup_light_core_(light_var, output_var, config):
|
||||
if CONF_INTERNAL in config:
|
||||
add(light_var.set_internal(config[CONF_INTERNAL]))
|
||||
cg.add(light_var.set_internal(config[CONF_INTERNAL]))
|
||||
if CONF_DEFAULT_TRANSITION_LENGTH in config:
|
||||
add(light_var.set_default_transition_length(config[CONF_DEFAULT_TRANSITION_LENGTH]))
|
||||
cg.add(light_var.set_default_transition_length(config[CONF_DEFAULT_TRANSITION_LENGTH]))
|
||||
if CONF_GAMMA_CORRECT in config:
|
||||
add(light_var.set_gamma_correct(config[CONF_GAMMA_CORRECT]))
|
||||
effects = []
|
||||
for conf in config.get(CONF_EFFECTS, []):
|
||||
effect = yield build_effect(conf)
|
||||
effects.append(effect)
|
||||
if effects:
|
||||
add(light_var.add_effects(effects))
|
||||
cg.add(light_var.set_gamma_correct(config[CONF_GAMMA_CORRECT]))
|
||||
effects = yield cg.build_registry_list(EFFECTS_REGISTRY, config.get(CONF_EFFECTS, []))
|
||||
cg.add(light_var.add_effects(effects))
|
||||
|
||||
if CONF_COLOR_CORRECT in config:
|
||||
add(output_var.set_correction(*config[CONF_COLOR_CORRECT]))
|
||||
cg.add(output_var.set_correction(*config[CONF_COLOR_CORRECT]))
|
||||
|
||||
setup_mqtt_component(light_var.Pget_mqtt(), config)
|
||||
if CONF_MQTT_ID in config:
|
||||
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], light_var)
|
||||
yield mqtt.register_mqtt_component(mqtt_, config)
|
||||
|
||||
|
||||
def setup_light(light_obj, output_var, config):
|
||||
light_var = Pvariable(config[CONF_ID], light_obj, has_side_effects=False)
|
||||
CORE.add_job(setup_light_core_, light_var, output_var, config)
|
||||
@coroutine
|
||||
def register_light(output_var, config):
|
||||
light_var = cg.new_Pvariable(config[CONF_ID], config[CONF_NAME], output_var)
|
||||
cg.add(cg.App.register_light(light_var))
|
||||
yield cg.register_component(light_var, config)
|
||||
yield setup_light_core_(light_var, output_var, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_LIGHT'
|
||||
|
||||
CONF_LIGHT_TOGGLE = 'light.toggle'
|
||||
LIGHT_TOGGLE_ACTION_SCHEMA = maybe_simple_id({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(LightState),
|
||||
vol.Optional(CONF_TRANSITION_LENGTH): cv.templatable(cv.positive_time_period_milliseconds),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_LIGHT_TOGGLE, LIGHT_TOGGLE_ACTION_SCHEMA)
|
||||
@ACTION_REGISTRY.register('light.toggle', maybe_simple_id({
|
||||
cv.Required(CONF_ID): cv.use_variable_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):
|
||||
var = yield get_variable(config[CONF_ID])
|
||||
var = yield cg.get_variable(config[CONF_ID])
|
||||
type = ToggleAction.template(template_arg)
|
||||
rhs = type.new(var)
|
||||
action = Pvariable(action_id, rhs, type=type)
|
||||
action = cg.Pvariable(action_id, rhs, type=type)
|
||||
if CONF_TRANSITION_LENGTH in config:
|
||||
template_ = yield templatable(config[CONF_TRANSITION_LENGTH], args, uint32)
|
||||
add(action.set_transition_length(template_))
|
||||
template_ = yield cg.templatable(config[CONF_TRANSITION_LENGTH], args, cg.uint32)
|
||||
cg.add(action.set_transition_length(template_))
|
||||
yield action
|
||||
|
||||
|
||||
CONF_LIGHT_TURN_OFF = 'light.turn_off'
|
||||
LIGHT_TURN_OFF_ACTION_SCHEMA = maybe_simple_id({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(LightState),
|
||||
vol.Optional(CONF_TRANSITION_LENGTH): cv.templatable(cv.positive_time_period_milliseconds),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_LIGHT_TURN_OFF, LIGHT_TURN_OFF_ACTION_SCHEMA)
|
||||
@ACTION_REGISTRY.register('light.turn_off', maybe_simple_id({
|
||||
cv.Required(CONF_ID): cv.use_variable_id(LightState),
|
||||
cv.Optional(CONF_TRANSITION_LENGTH): cv.templatable(cv.positive_time_period_milliseconds),
|
||||
}))
|
||||
def light_turn_off_to_code(config, action_id, template_arg, args):
|
||||
var = yield get_variable(config[CONF_ID])
|
||||
var = yield cg.get_variable(config[CONF_ID])
|
||||
type = LightControlAction.template(template_arg)
|
||||
rhs = type.new(var)
|
||||
action = Pvariable(action_id, rhs, type=type)
|
||||
action = cg.Pvariable(action_id, rhs, type=type)
|
||||
if CONF_TRANSITION_LENGTH in config:
|
||||
template_ = yield templatable(config[CONF_TRANSITION_LENGTH], args, uint32)
|
||||
add(action.set_transition_length(template_))
|
||||
template_ = yield cg.templatable(config[CONF_TRANSITION_LENGTH], args, cg.uint32)
|
||||
cg.add(action.set_transition_length(template_))
|
||||
yield action
|
||||
|
||||
|
||||
CONF_LIGHT_CONTROL = 'light.control'
|
||||
LIGHT_CONTROL_ACTION_SCHEMA = cv.Schema({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(LightState),
|
||||
vol.Optional(CONF_STATE): cv.templatable(cv.boolean),
|
||||
vol.Exclusive(CONF_TRANSITION_LENGTH, 'transformer'):
|
||||
cv.Required(CONF_ID): cv.use_variable_id(LightState),
|
||||
cv.Optional(CONF_STATE): cv.templatable(cv.boolean),
|
||||
cv.Exclusive(CONF_TRANSITION_LENGTH, 'transformer'):
|
||||
cv.templatable(cv.positive_time_period_milliseconds),
|
||||
vol.Exclusive(CONF_FLASH_LENGTH, 'transformer'):
|
||||
cv.Exclusive(CONF_FLASH_LENGTH, 'transformer'):
|
||||
cv.templatable(cv.positive_time_period_milliseconds),
|
||||
vol.Exclusive(CONF_EFFECT, 'transformer'): cv.templatable(cv.string),
|
||||
vol.Optional(CONF_BRIGHTNESS): cv.templatable(cv.percentage),
|
||||
vol.Optional(CONF_RED): cv.templatable(cv.percentage),
|
||||
vol.Optional(CONF_GREEN): cv.templatable(cv.percentage),
|
||||
vol.Optional(CONF_BLUE): cv.templatable(cv.percentage),
|
||||
vol.Optional(CONF_WHITE): cv.templatable(cv.percentage),
|
||||
vol.Optional(CONF_COLOR_TEMPERATURE): cv.templatable(cv.color_temperature),
|
||||
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),
|
||||
})
|
||||
CONF_LIGHT_TURN_OFF = 'light.turn_off'
|
||||
LIGHT_TURN_OFF_ACTION_SCHEMA = maybe_simple_id({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(LightState),
|
||||
vol.Optional(CONF_TRANSITION_LENGTH): cv.templatable(cv.positive_time_period_milliseconds),
|
||||
vol.Optional(CONF_STATE, default=False): False,
|
||||
cv.Required(CONF_ID): cv.use_variable_id(LightState),
|
||||
cv.Optional(CONF_TRANSITION_LENGTH): cv.templatable(cv.positive_time_period_milliseconds),
|
||||
cv.Optional(CONF_STATE, default=False): False,
|
||||
})
|
||||
CONF_LIGHT_TURN_ON = 'light.turn_on'
|
||||
LIGHT_TURN_ON_ACTION_SCHEMA = maybe_simple_id(LIGHT_CONTROL_ACTION_SCHEMA.extend({
|
||||
vol.Optional(CONF_STATE, default=True): True,
|
||||
cv.Optional(CONF_STATE, default=True): True,
|
||||
}))
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_LIGHT_TURN_OFF, LIGHT_TURN_OFF_ACTION_SCHEMA)
|
||||
@ACTION_REGISTRY.register(CONF_LIGHT_TURN_ON, LIGHT_TURN_ON_ACTION_SCHEMA)
|
||||
@ACTION_REGISTRY.register(CONF_LIGHT_CONTROL, LIGHT_CONTROL_ACTION_SCHEMA)
|
||||
@ACTION_REGISTRY.register('light.turn_off', LIGHT_TURN_OFF_ACTION_SCHEMA)
|
||||
@ACTION_REGISTRY.register('light.turn_on', LIGHT_TURN_ON_ACTION_SCHEMA)
|
||||
@ACTION_REGISTRY.register('light.control', LIGHT_CONTROL_ACTION_SCHEMA)
|
||||
def light_control_to_code(config, action_id, template_arg, args):
|
||||
var = yield get_variable(config[CONF_ID])
|
||||
var = yield cg.get_variable(config[CONF_ID])
|
||||
type = LightControlAction.template(template_arg)
|
||||
rhs = type.new(var)
|
||||
action = Pvariable(action_id, rhs, type=type)
|
||||
action = cg.Pvariable(action_id, rhs, type=type)
|
||||
if CONF_STATE in config:
|
||||
template_ = yield templatable(config[CONF_STATE], args, bool)
|
||||
add(action.set_state(template_))
|
||||
template_ = yield cg.templatable(config[CONF_STATE], args, bool)
|
||||
cg.add(action.set_state(template_))
|
||||
if CONF_TRANSITION_LENGTH in config:
|
||||
template_ = yield templatable(config[CONF_TRANSITION_LENGTH], args, uint32)
|
||||
add(action.set_transition_length(template_))
|
||||
template_ = yield cg.templatable(config[CONF_TRANSITION_LENGTH], args, cg.uint32)
|
||||
cg.add(action.set_transition_length(template_))
|
||||
if CONF_FLASH_LENGTH in config:
|
||||
template_ = yield templatable(config[CONF_FLASH_LENGTH], args, uint32)
|
||||
add(action.set_flash_length(template_))
|
||||
template_ = yield cg.templatable(config[CONF_FLASH_LENGTH], args, cg.uint32)
|
||||
cg.add(action.set_flash_length(template_))
|
||||
if CONF_BRIGHTNESS in config:
|
||||
template_ = yield templatable(config[CONF_BRIGHTNESS], args, float)
|
||||
add(action.set_brightness(template_))
|
||||
template_ = yield cg.templatable(config[CONF_BRIGHTNESS], args, float)
|
||||
cg.add(action.set_brightness(template_))
|
||||
if CONF_RED in config:
|
||||
template_ = yield templatable(config[CONF_RED], args, float)
|
||||
add(action.set_red(template_))
|
||||
template_ = yield cg.templatable(config[CONF_RED], args, float)
|
||||
cg.add(action.set_red(template_))
|
||||
if CONF_GREEN in config:
|
||||
template_ = yield templatable(config[CONF_GREEN], args, float)
|
||||
add(action.set_green(template_))
|
||||
template_ = yield cg.templatable(config[CONF_GREEN], args, float)
|
||||
cg.add(action.set_green(template_))
|
||||
if CONF_BLUE in config:
|
||||
template_ = yield templatable(config[CONF_BLUE], args, float)
|
||||
add(action.set_blue(template_))
|
||||
template_ = yield cg.templatable(config[CONF_BLUE], args, float)
|
||||
cg.add(action.set_blue(template_))
|
||||
if CONF_WHITE in config:
|
||||
template_ = yield templatable(config[CONF_WHITE], args, float)
|
||||
add(action.set_white(template_))
|
||||
template_ = yield cg.templatable(config[CONF_WHITE], args, float)
|
||||
cg.add(action.set_white(template_))
|
||||
if CONF_COLOR_TEMPERATURE in config:
|
||||
template_ = yield templatable(config[CONF_COLOR_TEMPERATURE], args, float)
|
||||
add(action.set_color_temperature(template_))
|
||||
template_ = yield cg.templatable(config[CONF_COLOR_TEMPERATURE], args, float)
|
||||
cg.add(action.set_color_temperature(template_))
|
||||
if CONF_EFFECT in config:
|
||||
template_ = yield templatable(config[CONF_EFFECT], args, std_string)
|
||||
add(action.set_effect(template_))
|
||||
template_ = yield cg.templatable(config[CONF_EFFECT], args, cg.std_string)
|
||||
cg.add(action.set_effect(template_))
|
||||
yield action
|
||||
|
||||
|
||||
def to_code(config):
|
||||
cg.add_define('USE_LIGHT')
|
||||
cg.add_global(light_ns.using)
|
||||
|
||||
@@ -0,0 +1,449 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "light_output.h"
|
||||
#include "light_state.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace light {
|
||||
|
||||
inline static uint8_t esp_scale8(uint8_t i, uint8_t scale) { return (uint16_t(i) * (1 + uint16_t(scale))) / 256; }
|
||||
|
||||
struct ESPColor {
|
||||
union {
|
||||
struct {
|
||||
union {
|
||||
uint8_t r;
|
||||
uint8_t red;
|
||||
};
|
||||
union {
|
||||
uint8_t g;
|
||||
uint8_t green;
|
||||
};
|
||||
union {
|
||||
uint8_t b;
|
||||
uint8_t blue;
|
||||
};
|
||||
union {
|
||||
uint8_t w;
|
||||
uint8_t white;
|
||||
};
|
||||
};
|
||||
uint8_t raw[4];
|
||||
};
|
||||
inline ESPColor() ALWAYS_INLINE : r(0), g(0), b(0), w(0) {} // NOLINT
|
||||
inline ESPColor(uint8_t red, uint8_t green, uint8_t blue, uint8_t white) ALWAYS_INLINE : r(red),
|
||||
g(green),
|
||||
b(blue),
|
||||
w(white) {}
|
||||
inline ESPColor(uint8_t red, uint8_t green, uint8_t blue) ALWAYS_INLINE : r(red), g(green), b(blue), w(0) {}
|
||||
inline ESPColor(uint32_t colorcode) ALWAYS_INLINE : r((colorcode >> 16) & 0xFF),
|
||||
g((colorcode >> 8) & 0xFF),
|
||||
b((colorcode >> 0) & 0xFF),
|
||||
w((colorcode >> 24) & 0xFF) {}
|
||||
inline ESPColor(const ESPColor &rhs) ALWAYS_INLINE {
|
||||
this->r = rhs.r;
|
||||
this->g = rhs.g;
|
||||
this->b = rhs.b;
|
||||
this->w = rhs.w;
|
||||
}
|
||||
inline bool is_on() ALWAYS_INLINE { return this->r != 0 || this->g != 0 || this->b != 0 || this->w != 0; }
|
||||
inline ESPColor &operator=(const ESPColor &rhs) ALWAYS_INLINE {
|
||||
this->r = rhs.r;
|
||||
this->g = rhs.g;
|
||||
this->b = rhs.b;
|
||||
this->w = rhs.w;
|
||||
return *this;
|
||||
}
|
||||
inline ESPColor &operator=(uint32_t colorcode) ALWAYS_INLINE {
|
||||
this->w = (colorcode >> 24) & 0xFF;
|
||||
this->r = (colorcode >> 16) & 0xFF;
|
||||
this->g = (colorcode >> 8) & 0xFF;
|
||||
this->b = (colorcode >> 0) & 0xFF;
|
||||
return *this;
|
||||
}
|
||||
inline uint8_t &operator[](uint8_t x) ALWAYS_INLINE { return this->raw[x]; }
|
||||
inline ESPColor operator*(uint8_t scale) const ALWAYS_INLINE {
|
||||
return ESPColor(esp_scale8(this->red, scale), esp_scale8(this->green, scale), esp_scale8(this->blue, scale),
|
||||
esp_scale8(this->white, scale));
|
||||
}
|
||||
inline ESPColor &operator*=(uint8_t scale) ALWAYS_INLINE {
|
||||
this->red = esp_scale8(this->red, scale);
|
||||
this->green = esp_scale8(this->green, scale);
|
||||
this->blue = esp_scale8(this->blue, scale);
|
||||
this->white = esp_scale8(this->white, scale);
|
||||
return *this;
|
||||
}
|
||||
inline ESPColor operator*(const ESPColor &scale) const ALWAYS_INLINE {
|
||||
return ESPColor(esp_scale8(this->red, scale.red), esp_scale8(this->green, scale.green),
|
||||
esp_scale8(this->blue, scale.blue), esp_scale8(this->white, scale.white));
|
||||
}
|
||||
inline ESPColor &operator*=(const ESPColor &scale) ALWAYS_INLINE {
|
||||
this->red = esp_scale8(this->red, scale.red);
|
||||
this->green = esp_scale8(this->green, scale.green);
|
||||
this->blue = esp_scale8(this->blue, scale.blue);
|
||||
this->white = esp_scale8(this->white, scale.white);
|
||||
return *this;
|
||||
}
|
||||
inline ESPColor operator+(const ESPColor &add) const ALWAYS_INLINE {
|
||||
ESPColor ret;
|
||||
if (uint8_t(add.r + this->r) < this->r)
|
||||
ret.r = 255;
|
||||
else
|
||||
ret.r = this->r + add.r;
|
||||
if (uint8_t(add.g + this->g) < this->g)
|
||||
ret.g = 255;
|
||||
else
|
||||
ret.g = this->g + add.g;
|
||||
if (uint8_t(add.b + this->b) < this->b)
|
||||
ret.b = 255;
|
||||
else
|
||||
ret.b = this->b + add.b;
|
||||
if (uint8_t(add.w + this->w) < this->w)
|
||||
ret.w = 255;
|
||||
else
|
||||
ret.w = this->w + add.w;
|
||||
return ret;
|
||||
}
|
||||
inline ESPColor &operator+=(const ESPColor &add) ALWAYS_INLINE { return *this = (*this) + add; }
|
||||
inline ESPColor operator+(uint8_t add) const ALWAYS_INLINE { return (*this) + ESPColor(add, add, add, add); }
|
||||
inline ESPColor &operator+=(uint8_t add) ALWAYS_INLINE { return *this = (*this) + add; }
|
||||
inline ESPColor operator-(const ESPColor &subtract) const ALWAYS_INLINE {
|
||||
ESPColor ret;
|
||||
if (subtract.r > this->r)
|
||||
ret.r = 0;
|
||||
else
|
||||
ret.r = this->r - subtract.r;
|
||||
if (subtract.g > this->g)
|
||||
ret.g = 0;
|
||||
else
|
||||
ret.g = this->g - subtract.g;
|
||||
if (subtract.b > this->b)
|
||||
ret.b = 0;
|
||||
else
|
||||
ret.b = this->b - subtract.b;
|
||||
if (subtract.w > this->w)
|
||||
ret.w = 0;
|
||||
else
|
||||
ret.w = this->w - subtract.w;
|
||||
return ret;
|
||||
}
|
||||
inline ESPColor &operator-=(const ESPColor &subtract) ALWAYS_INLINE { return *this = (*this) - subtract; }
|
||||
inline ESPColor operator-(uint8_t subtract) const ALWAYS_INLINE {
|
||||
return (*this) - ESPColor(subtract, subtract, subtract, subtract);
|
||||
}
|
||||
inline ESPColor &operator-=(uint8_t subtract) ALWAYS_INLINE { return *this = (*this) - subtract; }
|
||||
static ESPColor random_color() {
|
||||
uint32_t rand = random_uint32();
|
||||
uint8_t w = rand >> 24;
|
||||
uint8_t r = rand >> 16;
|
||||
uint8_t g = rand >> 8;
|
||||
uint8_t b = rand >> 0;
|
||||
const uint16_t max_rgb = std::max(r, std::max(g, b));
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
struct ESPHSVColor {
|
||||
union {
|
||||
struct {
|
||||
union {
|
||||
uint8_t hue;
|
||||
uint8_t h;
|
||||
};
|
||||
union {
|
||||
uint8_t saturation;
|
||||
uint8_t s;
|
||||
};
|
||||
union {
|
||||
uint8_t value;
|
||||
uint8_t v;
|
||||
};
|
||||
};
|
||||
uint8_t raw[3];
|
||||
};
|
||||
inline ESPHSVColor() ALWAYS_INLINE : h(0), s(0), v(0) { // NOLINT
|
||||
}
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
class ESPColorCorrection {
|
||||
public:
|
||||
ESPColorCorrection() : max_brightness_(255, 255, 255, 255) {}
|
||||
void set_max_brightness(const ESPColor &max_brightness) { this->max_brightness_ = max_brightness; }
|
||||
void set_local_brightness(uint8_t local_brightness) { this->local_brightness_ = local_brightness; }
|
||||
void calculate_gamma_table(float gamma) {
|
||||
for (uint16_t i = 0; i < 256; i++) {
|
||||
// corrected = val ^ gamma
|
||||
auto corrected = static_cast<uint8_t>(roundf(255.0f * gamma_correct(i / 255.0f, gamma)));
|
||||
this->gamma_table_[i] = corrected;
|
||||
}
|
||||
if (gamma == 0.0f) {
|
||||
for (uint16_t i = 0; i < 256; i++)
|
||||
this->gamma_reverse_table_[i] = i;
|
||||
return;
|
||||
}
|
||||
for (uint16_t i = 0; i < 256; i++) {
|
||||
// val = corrected ^ (1/gamma)
|
||||
auto uncorrected = static_cast<uint8_t>(roundf(255.0f * powf(i / 255.0f, 1.0f / gamma)));
|
||||
this->gamma_reverse_table_[i] = uncorrected;
|
||||
}
|
||||
}
|
||||
inline ESPColor color_correct(ESPColor color) const ALWAYS_INLINE {
|
||||
// corrected = (uncorrected * max_brightness * local_brightness) ^ gamma
|
||||
return ESPColor(this->color_correct_red(color.red), this->color_correct_green(color.green),
|
||||
this->color_correct_blue(color.blue), this->color_correct_white(color.white));
|
||||
}
|
||||
inline uint8_t color_correct_red(uint8_t red) const ALWAYS_INLINE {
|
||||
uint8_t res = esp_scale8(esp_scale8(red, this->max_brightness_.red), this->local_brightness_);
|
||||
return this->gamma_table_[res];
|
||||
}
|
||||
inline uint8_t color_correct_green(uint8_t green) const ALWAYS_INLINE {
|
||||
uint8_t res = esp_scale8(esp_scale8(green, this->max_brightness_.green), this->local_brightness_);
|
||||
return this->gamma_table_[res];
|
||||
}
|
||||
inline uint8_t color_correct_blue(uint8_t blue) const ALWAYS_INLINE {
|
||||
uint8_t res = esp_scale8(esp_scale8(blue, this->max_brightness_.blue), this->local_brightness_);
|
||||
return this->gamma_table_[res];
|
||||
}
|
||||
inline uint8_t color_correct_white(uint8_t white) const ALWAYS_INLINE {
|
||||
// do not scale white value with brightness
|
||||
uint8_t res = esp_scale8(white, this->max_brightness_.white);
|
||||
return this->gamma_table_[res];
|
||||
}
|
||||
inline ESPColor color_uncorrect(ESPColor color) const ALWAYS_INLINE {
|
||||
// uncorrected = corrected^(1/gamma) / (max_brightness * local_brightness)
|
||||
return ESPColor(this->color_uncorrect_red(color.red), this->color_uncorrect_green(color.green),
|
||||
this->color_uncorrect_blue(color.blue), this->color_uncorrect_white(color.white));
|
||||
}
|
||||
inline uint8_t color_uncorrect_red(uint8_t red) const ALWAYS_INLINE {
|
||||
if (this->max_brightness_.red == 0 || this->local_brightness_ == 0)
|
||||
return 0;
|
||||
uint16_t uncorrected = this->gamma_reverse_table_[red] * 255UL;
|
||||
uint8_t res = ((uncorrected / this->max_brightness_.red) * 255UL) / this->local_brightness_;
|
||||
return res;
|
||||
}
|
||||
inline uint8_t color_uncorrect_green(uint8_t green) const ALWAYS_INLINE {
|
||||
if (this->max_brightness_.green == 0 || this->local_brightness_ == 0)
|
||||
return 0;
|
||||
uint16_t uncorrected = this->gamma_reverse_table_[green] * 255UL;
|
||||
uint8_t res = ((uncorrected / this->max_brightness_.green) * 255UL) / this->local_brightness_;
|
||||
return res;
|
||||
}
|
||||
inline uint8_t color_uncorrect_blue(uint8_t blue) const ALWAYS_INLINE {
|
||||
if (this->max_brightness_.blue == 0 || this->local_brightness_ == 0)
|
||||
return 0;
|
||||
uint16_t uncorrected = this->gamma_reverse_table_[blue] * 255UL;
|
||||
uint8_t res = ((uncorrected / this->max_brightness_.blue) * 255UL) / this->local_brightness_;
|
||||
return res;
|
||||
}
|
||||
inline uint8_t color_uncorrect_white(uint8_t white) const ALWAYS_INLINE {
|
||||
if (this->max_brightness_.white == 0)
|
||||
return 0;
|
||||
uint16_t uncorrected = this->gamma_reverse_table_[white] * 255UL;
|
||||
uint8_t res = uncorrected / this->max_brightness_.white;
|
||||
return res;
|
||||
}
|
||||
|
||||
protected:
|
||||
uint8_t gamma_table_[256];
|
||||
uint8_t gamma_reverse_table_[256];
|
||||
ESPColor max_brightness_;
|
||||
uint8_t local_brightness_{255};
|
||||
};
|
||||
|
||||
class ESPColorView {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
if (this->white_ == nullptr)
|
||||
return 0;
|
||||
return this->color_correction_->color_uncorrect_white(*this->white_);
|
||||
}
|
||||
inline uint8_t get_effect_data() const ALWAYS_INLINE {
|
||||
if (this->effect_data_ == nullptr)
|
||||
return 0;
|
||||
return *this->effect_data_;
|
||||
}
|
||||
inline void raw_set_color_correction(const ESPColorCorrection *color_correction) ALWAYS_INLINE {
|
||||
this->color_correction_ = color_correction;
|
||||
}
|
||||
|
||||
protected:
|
||||
uint8_t *const red_;
|
||||
uint8_t *const green_;
|
||||
uint8_t *const blue_;
|
||||
uint8_t *const white_;
|
||||
uint8_t *const effect_data_;
|
||||
const ESPColorCorrection *color_correction_;
|
||||
};
|
||||
|
||||
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;
|
||||
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 {
|
||||
auto val = state->current_values;
|
||||
auto max_brightness = static_cast<uint8_t>(roundf(val.get_brightness() * val.get_state() * 255.0f));
|
||||
this->correction_.set_local_brightness(max_brightness);
|
||||
|
||||
if (this->is_effect_active())
|
||||
return;
|
||||
|
||||
// don't use LightState helper, gamma correction+brightness is handled by ESPColorView
|
||||
ESPColor color = ESPColor(uint8_t(roundf(val.get_red() * 255.0f)), uint8_t(roundf(val.get_green() * 255.0f)),
|
||||
uint8_t(roundf(val.get_blue() * 255.0f)),
|
||||
// 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->schedule_show();
|
||||
}
|
||||
void set_correction(float red, float green, float blue, float white = 1.0f) {
|
||||
this->correction_.set_max_brightness(ESPColor(uint8_t(roundf(red * 255.0f)), uint8_t(roundf(green * 255.0f)),
|
||||
uint8_t(roundf(blue * 255.0f)), uint8_t(roundf(white * 255.0f))));
|
||||
}
|
||||
void setup_state(LightState *state) override { this->correction_.calculate_gamma_table(state->get_gamma_correct()); }
|
||||
void schedule_show() { this->next_show_ = true; }
|
||||
|
||||
protected:
|
||||
bool should_show_() const { return this->effect_active_ || this->next_show_; }
|
||||
void mark_shown_() { this->next_show_ = false; }
|
||||
|
||||
bool effect_active_{false};
|
||||
bool next_show_{true};
|
||||
ESPColorCorrection correction_{};
|
||||
};
|
||||
|
||||
} // namespace light
|
||||
} // namespace esphome
|
||||
@@ -0,0 +1,350 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/light/light_state.h"
|
||||
#include "esphome/components/light/addressable_light.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace light {
|
||||
|
||||
inline static int16_t sin16_c(uint16_t theta) {
|
||||
static const uint16_t BASE[] = {0, 6393, 12539, 18204, 23170, 27245, 30273, 32137};
|
||||
static const uint8_t SLOPE[] = {49, 48, 44, 38, 31, 23, 14, 4};
|
||||
uint16_t offset = (theta & 0x3FFF) >> 3; // 0..2047
|
||||
if (theta & 0x4000)
|
||||
offset = 2047 - offset;
|
||||
uint8_t section = offset / 256; // 0..7
|
||||
uint16_t b = BASE[section];
|
||||
uint8_t m = SLOPE[section];
|
||||
uint8_t secoffset8 = uint8_t(offset) / 2;
|
||||
uint16_t mx = m * secoffset8;
|
||||
int16_t y = mx + b;
|
||||
if (theta & 0x8000)
|
||||
return -y;
|
||||
return y;
|
||||
}
|
||||
inline static uint8_t half_sin8(uint8_t v) { return sin16_c(uint16_t(v) * 128u) >> 8; }
|
||||
|
||||
class AddressableLightEffect : public LightEffect {
|
||||
public:
|
||||
explicit AddressableLightEffect(const std::string &name) : LightEffect(name) {}
|
||||
void start_internal() override {
|
||||
this->get_addressable_()->set_effect_active(true);
|
||||
this->get_addressable_()->clear_effect_data();
|
||||
this->high_freq_.start();
|
||||
this->start();
|
||||
}
|
||||
void stop() override {
|
||||
this->get_addressable_()->set_effect_active(false);
|
||||
this->high_freq_.stop();
|
||||
}
|
||||
virtual void apply(AddressableLight &it, const ESPColor ¤t_color) = 0;
|
||||
void apply() override {
|
||||
LightColorValues color = this->state_->remote_values;
|
||||
// not using any color correction etc. that will be handled by the addressable layer
|
||||
ESPColor current_color =
|
||||
ESPColor(static_cast<uint8_t>(color.get_red() * 255), static_cast<uint8_t>(color.get_green() * 255),
|
||||
static_cast<uint8_t>(color.get_blue() * 255), static_cast<uint8_t>(color.get_white() * 255));
|
||||
this->apply(*this->get_addressable_(), current_color);
|
||||
}
|
||||
|
||||
protected:
|
||||
AddressableLight *get_addressable_() const { return (AddressableLight *) this->state_->get_output(); }
|
||||
|
||||
HighFrequencyLoopRequester high_freq_;
|
||||
};
|
||||
|
||||
class AddressableLambdaLightEffect : public AddressableLightEffect {
|
||||
public:
|
||||
AddressableLambdaLightEffect(const std::string &name, const std::function<void(AddressableLight &)> &f,
|
||||
uint32_t update_interval)
|
||||
: AddressableLightEffect(name), f_(f), update_interval_(update_interval) {}
|
||||
void apply(AddressableLight &it, const ESPColor ¤t_color) override {
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_run_ >= this->update_interval_) {
|
||||
this->last_run_ = now;
|
||||
this->f_(it);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
std::function<void(AddressableLight &)> f_;
|
||||
uint32_t update_interval_;
|
||||
uint32_t last_run_{0};
|
||||
};
|
||||
|
||||
class AddressableRainbowLightEffect : public AddressableLightEffect {
|
||||
public:
|
||||
explicit AddressableRainbowLightEffect(const std::string &name) : AddressableLightEffect(name) {}
|
||||
void apply(AddressableLight &it, const ESPColor ¤t_color) override {
|
||||
ESPHSVColor hsv;
|
||||
hsv.value = 255;
|
||||
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++) {
|
||||
hsv.hue = hue >> 8;
|
||||
it[i] = hsv;
|
||||
hue += add;
|
||||
}
|
||||
}
|
||||
void set_speed(uint32_t speed) { this->speed_ = speed; }
|
||||
void set_width(uint16_t width) { this->width_ = width; }
|
||||
|
||||
protected:
|
||||
uint32_t speed_{10};
|
||||
uint16_t width_{50};
|
||||
};
|
||||
|
||||
struct AddressableColorWipeEffectColor {
|
||||
uint8_t r, g, b, w;
|
||||
bool random;
|
||||
size_t num_leds;
|
||||
};
|
||||
|
||||
class AddressableColorWipeEffect : public AddressableLightEffect {
|
||||
public:
|
||||
explicit AddressableColorWipeEffect(const std::string &name) : AddressableLightEffect(name) {}
|
||||
void set_colors(const std::vector<AddressableColorWipeEffectColor> &colors) { this->colors_ = colors; }
|
||||
void set_add_led_interval(uint32_t add_led_interval) { this->add_led_interval_ = add_led_interval; }
|
||||
void set_reverse(bool reverse) { this->reverse_ = reverse; }
|
||||
void apply(AddressableLight &it, const ESPColor ¤t_color) override {
|
||||
const uint32_t now = millis();
|
||||
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();
|
||||
}
|
||||
}
|
||||
const AddressableColorWipeEffectColor color = this->colors_[this->at_color_];
|
||||
const ESPColor esp_color = ESPColor(color.r, color.g, color.b, color.w);
|
||||
if (!this->reverse_) {
|
||||
it[it.size() - 1] = esp_color;
|
||||
} else {
|
||||
it[0] = esp_color;
|
||||
}
|
||||
if (++this->leds_added_ >= color.num_leds) {
|
||||
this->leds_added_ = 0;
|
||||
this->at_color_ = (this->at_color_ + 1) % this->colors_.size();
|
||||
AddressableColorWipeEffectColor &new_color = this->colors_[this->at_color_];
|
||||
if (new_color.random) {
|
||||
ESPColor c = ESPColor::random_color();
|
||||
new_color.r = c.r;
|
||||
new_color.g = c.g;
|
||||
new_color.b = c.b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
std::vector<AddressableColorWipeEffectColor> colors_;
|
||||
size_t at_color_{0};
|
||||
uint32_t last_add_{0};
|
||||
uint32_t add_led_interval_{};
|
||||
size_t leds_added_{0};
|
||||
bool reverse_{};
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
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)
|
||||
this->direction_ = false;
|
||||
} else {
|
||||
this->at_led_--;
|
||||
if (this->at_led_ == 0)
|
||||
this->direction_ = true;
|
||||
}
|
||||
this->last_move_ = now;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
uint32_t move_interval_{};
|
||||
uint32_t last_move_{0};
|
||||
int at_led_{0};
|
||||
bool direction_{true};
|
||||
};
|
||||
|
||||
class AddressableTwinkleEffect : public AddressableLightEffect {
|
||||
public:
|
||||
explicit AddressableTwinkleEffect(const std::string &name) : AddressableLightEffect(name) {}
|
||||
void apply(AddressableLight &addressable, const ESPColor ¤t_color) override {
|
||||
const uint32_t now = millis();
|
||||
uint8_t pos_add = 0;
|
||||
if (now - this->last_progress_ > this->progress_interval_) {
|
||||
const uint32_t pos_add32 = (now - this->last_progress_) / this->progress_interval_;
|
||||
pos_add = pos_add32;
|
||||
this->last_progress_ += pos_add32 * this->progress_interval_;
|
||||
}
|
||||
for (int i = 0; i < addressable.size(); i++) {
|
||||
ESPColorView view = addressable[i];
|
||||
if (view.get_effect_data() != 0) {
|
||||
const uint8_t sine = half_sin8(view.get_effect_data());
|
||||
view = current_color * sine;
|
||||
const uint8_t new_pos = view.get_effect_data() + pos_add;
|
||||
if (new_pos < view.get_effect_data())
|
||||
view.set_effect_data(0);
|
||||
else
|
||||
view.set_effect_data(new_pos);
|
||||
} else {
|
||||
view = ESPColor(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
while (random_float() < this->twinkle_probability_) {
|
||||
const size_t pos = random_uint32() % addressable.size();
|
||||
if (addressable[pos].get_effect_data() != 0)
|
||||
continue;
|
||||
addressable[pos].set_effect_data(1);
|
||||
}
|
||||
}
|
||||
void set_twinkle_probability(float twinkle_probability) { this->twinkle_probability_ = twinkle_probability; }
|
||||
void set_progress_interval(uint32_t progress_interval) { this->progress_interval_ = progress_interval; }
|
||||
|
||||
protected:
|
||||
float twinkle_probability_{0.05f};
|
||||
uint32_t progress_interval_{4};
|
||||
uint32_t last_progress_{0};
|
||||
};
|
||||
|
||||
class AddressableRandomTwinkleEffect : public AddressableLightEffect {
|
||||
public:
|
||||
explicit AddressableRandomTwinkleEffect(const std::string &name) : AddressableLightEffect(name) {}
|
||||
void apply(AddressableLight &it, const ESPColor ¤t_color) override {
|
||||
const uint32_t now = millis();
|
||||
uint8_t pos_add = 0;
|
||||
if (now - this->last_progress_ > this->progress_interval_) {
|
||||
pos_add = (now - this->last_progress_) / this->progress_interval_;
|
||||
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];
|
||||
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;
|
||||
const uint16_t sine = half_sin8((x << 3) | subsine);
|
||||
if (color == 0) {
|
||||
view = current_color * sine;
|
||||
} else {
|
||||
view = ESPColor(((color >> 2) & 1) * sine, ((color >> 1) & 1) * sine, ((color >> 0) & 1) * sine);
|
||||
}
|
||||
const uint8_t new_x = x + pos_add;
|
||||
if (new_x > 0b11111)
|
||||
view.set_effect_data(0);
|
||||
else
|
||||
view.set_effect_data((new_x << 3) | color);
|
||||
} else {
|
||||
view = ESPColor(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
while (random_float() < this->twinkle_probability_) {
|
||||
const size_t pos = random_uint32() % it.size();
|
||||
if (it[pos].get_effect_data() != 0)
|
||||
continue;
|
||||
const uint8_t color = random_uint32() & 0b111;
|
||||
it[pos].set_effect_data(0b1000 | color);
|
||||
}
|
||||
}
|
||||
void set_twinkle_probability(float twinkle_probability) { this->twinkle_probability_ = twinkle_probability; }
|
||||
void set_progress_interval(uint32_t progress_interval) { this->progress_interval_ = progress_interval; }
|
||||
|
||||
protected:
|
||||
float twinkle_probability_{};
|
||||
uint32_t progress_interval_{};
|
||||
uint32_t last_progress_{0};
|
||||
};
|
||||
|
||||
class AddressableFireworksEffect : public AddressableLightEffect {
|
||||
public:
|
||||
explicit AddressableFireworksEffect(const std::string &name) : AddressableLightEffect(name) {}
|
||||
void start() override {
|
||||
const auto &it = *this->get_addressable_();
|
||||
for (int i = 0; i < it.size(); i++)
|
||||
it[i] = ESPColor(0, 0, 0, 0);
|
||||
}
|
||||
void apply(AddressableLight &it, const ESPColor ¤t_color) override {
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_update_ < this->update_interval_)
|
||||
return;
|
||||
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;
|
||||
if (target.r < 64)
|
||||
target *= 170;
|
||||
it[i] = target;
|
||||
}
|
||||
int last = it.size() - 1;
|
||||
it[0].set(it[0].get() + (it[1].get() * 128));
|
||||
for (int i = 1; i < last; i++) {
|
||||
it[i] = (it[i - 1].get() * 64) + it[i].get() + (it[i + 1].get() * 64);
|
||||
}
|
||||
it[last] = it[last].get() + (it[last - 1].get() * 128);
|
||||
if (random_float() < this->spark_probability_) {
|
||||
const size_t pos = random_uint32() % it.size();
|
||||
if (this->use_random_color_) {
|
||||
it[pos] = ESPColor::random_color();
|
||||
} else {
|
||||
it[pos] = current_color;
|
||||
}
|
||||
}
|
||||
}
|
||||
void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }
|
||||
void set_spark_probability(float spark_probability) { this->spark_probability_ = spark_probability; }
|
||||
void set_use_random_color(bool random_color) { this->use_random_color_ = random_color; }
|
||||
void set_fade_out_rate(uint8_t fade_out_rate) { this->fade_out_rate_ = fade_out_rate; }
|
||||
|
||||
protected:
|
||||
uint8_t fade_out_rate_{};
|
||||
uint32_t update_interval_{};
|
||||
uint32_t last_update_{0};
|
||||
float spark_probability_{};
|
||||
bool use_random_color_{};
|
||||
};
|
||||
|
||||
class AddressableFlickerEffect : public AddressableLightEffect {
|
||||
public:
|
||||
explicit AddressableFlickerEffect(const std::string &name) : AddressableLightEffect(name) {}
|
||||
void apply(AddressableLight &it, const ESPColor ¤t_color) override {
|
||||
const uint32_t now = millis();
|
||||
const uint8_t delta_intensity = 255 - this->intensity_;
|
||||
if (now - this->last_update_ < this->update_interval_)
|
||||
return;
|
||||
this->last_update_ = now;
|
||||
fast_random_set_seed(random_uint32());
|
||||
for (int i = 0; i < it.size(); i++) {
|
||||
const uint8_t flicker = fast_random_8() % this->intensity_;
|
||||
it[i] = (it[i].get() * delta_intensity) + (current_color * flicker);
|
||||
}
|
||||
}
|
||||
void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }
|
||||
void set_intensity(float intensity) { this->intensity_ = static_cast<uint8_t>(roundf(intensity * 255.0f)); }
|
||||
|
||||
protected:
|
||||
uint32_t update_interval_{16};
|
||||
uint32_t last_update_{0};
|
||||
uint8_t intensity_{13};
|
||||
};
|
||||
|
||||
} // namespace light
|
||||
} // namespace esphome
|
||||
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "light_state.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace light {
|
||||
|
||||
template<typename... Ts> class ToggleAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit ToggleAction(LightState *state) : state_(state) {}
|
||||
|
||||
TEMPLATABLE_VALUE(uint32_t, transition_length)
|
||||
|
||||
void play(Ts... x) override {
|
||||
auto call = this->state_->toggle();
|
||||
call.set_transition_length(this->transition_length_.optional_value(x...));
|
||||
call.perform();
|
||||
this->play_next(x...);
|
||||
}
|
||||
|
||||
protected:
|
||||
LightState *state_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class LightControlAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit LightControlAction(LightState *parent) : parent_(parent) {}
|
||||
|
||||
TEMPLATABLE_VALUE(bool, state)
|
||||
TEMPLATABLE_VALUE(uint32_t, transition_length)
|
||||
TEMPLATABLE_VALUE(uint32_t, flash_length)
|
||||
TEMPLATABLE_VALUE(float, brightness)
|
||||
TEMPLATABLE_VALUE(float, red)
|
||||
TEMPLATABLE_VALUE(float, green)
|
||||
TEMPLATABLE_VALUE(float, blue)
|
||||
TEMPLATABLE_VALUE(float, white)
|
||||
TEMPLATABLE_VALUE(float, color_temperature)
|
||||
TEMPLATABLE_VALUE(std::string, effect)
|
||||
|
||||
void play(Ts... x) override {
|
||||
auto call = this->parent_->make_call();
|
||||
call.set_state(this->state_.optional_value(x...));
|
||||
call.set_brightness(this->brightness_.optional_value(x...));
|
||||
call.set_red(this->red_.optional_value(x...));
|
||||
call.set_green(this->green_.optional_value(x...));
|
||||
call.set_blue(this->blue_.optional_value(x...));
|
||||
call.set_white(this->white_.optional_value(x...));
|
||||
call.set_color_temperature(this->color_temperature_.optional_value(x...));
|
||||
call.set_effect(this->effect_.optional_value(x...));
|
||||
call.set_flash_length(this->flash_length_.optional_value(x...));
|
||||
call.set_transition_length(this->transition_length_.optional_value(x...));
|
||||
call.perform();
|
||||
this->play_next(x...);
|
||||
}
|
||||
|
||||
protected:
|
||||
LightState *parent_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class LightIsOnCondition : public Condition<Ts...> {
|
||||
public:
|
||||
explicit LightIsOnCondition(LightState *state) : state_(state) {}
|
||||
bool check(Ts... x) override { return this->state_->get_current_values().is_on(); }
|
||||
|
||||
protected:
|
||||
LightState *state_;
|
||||
};
|
||||
template<typename... Ts> class LightIsOffCondition : public Condition<LightState, Ts...> {
|
||||
public:
|
||||
explicit LightIsOffCondition(LightState *state) : state_(state) {}
|
||||
bool check(Ts... x) override { return !this->state_->get_current_values().is_on(); }
|
||||
|
||||
protected:
|
||||
LightState *state_;
|
||||
};
|
||||
|
||||
} // namespace light
|
||||
} // namespace esphome
|
||||
@@ -0,0 +1,143 @@
|
||||
#pragma once
|
||||
|
||||
#include "light_effect.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace light {
|
||||
|
||||
inline static float random_cubic_float() {
|
||||
const float r = random_float() * 2.0f - 1.0f;
|
||||
return r * r * r;
|
||||
}
|
||||
|
||||
/// Random effect. Sets random colors every 10 seconds and slowly transitions between them.
|
||||
class RandomLightEffect : public LightEffect {
|
||||
public:
|
||||
explicit RandomLightEffect(const std::string &name) : LightEffect(name) {}
|
||||
|
||||
void apply() override {
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_color_change_ < this->update_interval_) {
|
||||
return;
|
||||
}
|
||||
auto call = this->state_->turn_on();
|
||||
call.set_red_if_supported(random_float());
|
||||
call.set_green_if_supported(random_float());
|
||||
call.set_blue_if_supported(random_float());
|
||||
call.set_white_if_supported(random_float());
|
||||
call.set_color_temperature_if_supported(random_float());
|
||||
call.set_transition_length_if_supported(this->transition_length_);
|
||||
call.set_publish(true);
|
||||
call.set_save(false);
|
||||
call.perform();
|
||||
|
||||
this->last_color_change_ = now;
|
||||
}
|
||||
|
||||
void set_transition_length(uint32_t transition_length) { this->transition_length_ = transition_length; }
|
||||
|
||||
void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }
|
||||
|
||||
protected:
|
||||
uint32_t last_color_change_{0};
|
||||
uint32_t transition_length_{};
|
||||
uint32_t update_interval_{};
|
||||
};
|
||||
|
||||
class LambdaLightEffect : public LightEffect {
|
||||
public:
|
||||
LambdaLightEffect(const std::string &name, const std::function<void()> &f, uint32_t update_interval)
|
||||
: LightEffect(name), f_(f), update_interval_(update_interval) {}
|
||||
|
||||
void apply() override {
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_run_ >= this->update_interval_) {
|
||||
this->last_run_ = now;
|
||||
this->f_();
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
std::function<void()> f_;
|
||||
uint32_t update_interval_;
|
||||
uint32_t last_run_{0};
|
||||
};
|
||||
|
||||
struct StrobeLightEffectColor {
|
||||
LightColorValues color;
|
||||
uint32_t duration;
|
||||
};
|
||||
|
||||
class StrobeLightEffect : public LightEffect {
|
||||
public:
|
||||
explicit StrobeLightEffect(const std::string &name) : LightEffect(name) {}
|
||||
void apply() override {
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_switch_ < this->colors_[this->at_color_].duration)
|
||||
return;
|
||||
|
||||
// Switch to next color
|
||||
this->at_color_ = (this->at_color_ + 1) % this->colors_.size();
|
||||
auto color = this->colors_[this->at_color_].color;
|
||||
|
||||
auto call = this->state_->turn_on();
|
||||
call.from_light_color_values(this->colors_[this->at_color_].color);
|
||||
|
||||
if (!color.is_on()) {
|
||||
// Don't turn the light off, otherwise the light effect will be stopped
|
||||
call.set_brightness_if_supported(0.0f);
|
||||
call.set_state(true);
|
||||
}
|
||||
call.set_publish(false);
|
||||
call.set_save(false);
|
||||
call.set_transition_length_if_supported(0);
|
||||
call.perform();
|
||||
this->last_switch_ = now;
|
||||
}
|
||||
|
||||
void set_colors(const std::vector<StrobeLightEffectColor> &colors) { this->colors_ = colors; }
|
||||
|
||||
protected:
|
||||
std::vector<StrobeLightEffectColor> colors_;
|
||||
uint32_t last_switch_{0};
|
||||
size_t at_color_{0};
|
||||
};
|
||||
|
||||
class FlickerLightEffect : public LightEffect {
|
||||
public:
|
||||
explicit FlickerLightEffect(const std::string &name) : LightEffect(name) {}
|
||||
|
||||
void apply() override {
|
||||
LightColorValues remote = this->state_->remote_values;
|
||||
LightColorValues current = this->state_->current_values;
|
||||
LightColorValues out;
|
||||
const float alpha = this->alpha_;
|
||||
const float beta = 1.0f - alpha;
|
||||
out.set_state(remote.get_state());
|
||||
out.set_brightness(remote.get_brightness() * beta + current.get_brightness() * alpha +
|
||||
(random_cubic_float() * this->intensity_));
|
||||
out.set_red(remote.get_red() * beta + current.get_red() * alpha + (random_cubic_float() * this->intensity_));
|
||||
out.set_green(remote.get_green() * beta + current.get_green() * alpha + (random_cubic_float() * this->intensity_));
|
||||
out.set_blue(remote.get_blue() * beta + current.get_blue() * alpha + (random_cubic_float() * this->intensity_));
|
||||
out.set_white(remote.get_white() * beta + current.get_white() * alpha + (random_cubic_float() * this->intensity_));
|
||||
|
||||
auto traits = this->state_->get_traits();
|
||||
auto call = this->state_->make_call();
|
||||
call.set_publish(false);
|
||||
call.set_save(false);
|
||||
if (traits.get_supports_brightness())
|
||||
call.set_transition_length(0);
|
||||
call.from_light_color_values(out);
|
||||
call.perform();
|
||||
}
|
||||
|
||||
void set_alpha(float alpha) { this->alpha_ = alpha; }
|
||||
void set_intensity(float intensity) { this->intensity_ = intensity; }
|
||||
|
||||
protected:
|
||||
float intensity_{};
|
||||
float alpha_{};
|
||||
};
|
||||
|
||||
} // namespace light
|
||||
} // namespace esphome
|
||||
@@ -1,21 +0,0 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import light, output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_MAKE_ID, CONF_NAME, CONF_OUTPUT
|
||||
from esphome.cpp_generator import get_variable, variable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(light.PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(light.MakeLight),
|
||||
vol.Required(CONF_OUTPUT): cv.use_variable_id(output.BinaryOutput),
|
||||
}).extend(light.BINARY_LIGHT_SCHEMA.schema).extend(cv.COMPONENT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
output_ = yield get_variable(config[CONF_OUTPUT])
|
||||
rhs = App.make_binary_light(config[CONF_NAME], output_)
|
||||
light_struct = variable(config[CONF_MAKE_ID], rhs)
|
||||
light.setup_light(light_struct.Pstate, light_struct.Poutput, config)
|
||||
setup_component(light_struct.Pstate, config)
|
||||
@@ -1,30 +0,0 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import light, output
|
||||
from esphome.components.light.rgbww import validate_cold_white_colder
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_COLD_WHITE, CONF_COLD_WHITE_COLOR_TEMPERATURE, CONF_MAKE_ID, \
|
||||
CONF_NAME, CONF_WARM_WHITE, CONF_WARM_WHITE_COLOR_TEMPERATURE
|
||||
from esphome.cpp_generator import get_variable, variable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(light.PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(light.MakeLight),
|
||||
vol.Required(CONF_COLD_WHITE): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Required(CONF_WARM_WHITE): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature,
|
||||
vol.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature,
|
||||
}).extend(light.BRIGHTNESS_ONLY_LIGHT_SCHEMA.schema).extend(cv.COMPONENT_SCHEMA.schema),
|
||||
validate_cold_white_colder)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
cold_white = yield get_variable(config[CONF_COLD_WHITE])
|
||||
warm_white = yield get_variable(config[CONF_WARM_WHITE])
|
||||
rhs = App.make_cwww_light(config[CONF_NAME], config[CONF_COLD_WHITE_COLOR_TEMPERATURE],
|
||||
config[CONF_WARM_WHITE_COLOR_TEMPERATURE],
|
||||
cold_white, warm_white)
|
||||
light_struct = variable(config[CONF_MAKE_ID], rhs)
|
||||
light.setup_light(light_struct.Pstate, light_struct.Poutput, config)
|
||||
setup_component(light_struct.Pstate, config)
|
||||
@@ -1,97 +0,0 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import light
|
||||
from esphome.components.power_supply import PowerSupplyComponent
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CHIPSET, CONF_MAKE_ID, CONF_MAX_REFRESH_RATE, CONF_NAME, \
|
||||
CONF_NUM_LEDS, CONF_PIN, CONF_POWER_SUPPLY, CONF_RGB_ORDER
|
||||
from esphome.cpp_generator import RawExpression, TemplateArguments, add, get_variable, variable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, Application
|
||||
|
||||
TYPES = [
|
||||
'NEOPIXEL',
|
||||
'TM1829',
|
||||
'TM1809',
|
||||
'TM1804',
|
||||
'TM1803',
|
||||
'UCS1903',
|
||||
'UCS1903B',
|
||||
'UCS1904',
|
||||
'UCS2903',
|
||||
'WS2812',
|
||||
'WS2852',
|
||||
'WS2812B',
|
||||
'SK6812',
|
||||
'SK6822',
|
||||
'APA106',
|
||||
'PL9823',
|
||||
'WS2811',
|
||||
'WS2813',
|
||||
'APA104',
|
||||
'WS2811_400',
|
||||
'GW6205',
|
||||
'GW6205_400',
|
||||
'LPD1886',
|
||||
'LPD1886_8BIT',
|
||||
]
|
||||
|
||||
RGB_ORDERS = [
|
||||
'RGB',
|
||||
'RBG',
|
||||
'GRB',
|
||||
'GBR',
|
||||
'BRG',
|
||||
'BGR',
|
||||
]
|
||||
|
||||
|
||||
def validate(value):
|
||||
if value[CONF_CHIPSET] == 'NEOPIXEL' and CONF_RGB_ORDER in value:
|
||||
raise vol.Invalid("NEOPIXEL doesn't support RGB order")
|
||||
return value
|
||||
|
||||
|
||||
MakeFastLEDLight = Application.struct('MakeFastLEDLight')
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(light.PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(MakeFastLEDLight),
|
||||
|
||||
vol.Required(CONF_CHIPSET): cv.one_of(*TYPES, upper=True),
|
||||
vol.Required(CONF_PIN): pins.output_pin,
|
||||
|
||||
vol.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
|
||||
vol.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds,
|
||||
vol.Optional(CONF_RGB_ORDER): cv.one_of(*RGB_ORDERS, upper=True),
|
||||
|
||||
vol.Optional(CONF_POWER_SUPPLY): cv.use_variable_id(PowerSupplyComponent),
|
||||
}).extend(light.ADDRESSABLE_LIGHT_SCHEMA.schema).extend(cv.COMPONENT_SCHEMA.schema), validate)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_fast_led_light(config[CONF_NAME])
|
||||
make = variable(config[CONF_MAKE_ID], rhs)
|
||||
fast_led = make.Pfast_led
|
||||
|
||||
rgb_order = None
|
||||
if CONF_RGB_ORDER in config:
|
||||
rgb_order = RawExpression(config[CONF_RGB_ORDER])
|
||||
template_args = TemplateArguments(RawExpression(config[CONF_CHIPSET]),
|
||||
config[CONF_PIN], rgb_order)
|
||||
add(fast_led.add_leds(template_args, config[CONF_NUM_LEDS]))
|
||||
|
||||
if CONF_MAX_REFRESH_RATE in config:
|
||||
add(fast_led.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE]))
|
||||
|
||||
if CONF_POWER_SUPPLY in config:
|
||||
power_supply = yield get_variable(config[CONF_POWER_SUPPLY])
|
||||
add(fast_led.set_power_supply(power_supply))
|
||||
|
||||
light.setup_light(make.Pstate, make.Pfast_led, config)
|
||||
setup_component(fast_led, config)
|
||||
|
||||
|
||||
REQUIRED_BUILD_FLAGS = '-DUSE_FAST_LED_LIGHT'
|
||||
|
||||
LIB_DEPS = 'FastLED@3.2.0'
|
||||
@@ -1,80 +0,0 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import light
|
||||
from esphome.components.power_supply import PowerSupplyComponent
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CHIPSET, CONF_CLOCK_PIN, CONF_DATA_PIN, CONF_EFFECTS, CONF_MAKE_ID, \
|
||||
CONF_MAX_REFRESH_RATE, CONF_NAME, CONF_NUM_LEDS, CONF_POWER_SUPPLY, CONF_RGB_ORDER
|
||||
from esphome.cpp_generator import RawExpression, TemplateArguments, add, get_variable, variable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, Application
|
||||
|
||||
CHIPSETS = [
|
||||
'LPD8806',
|
||||
'WS2801',
|
||||
'WS2803',
|
||||
'SM16716',
|
||||
'P9813',
|
||||
'APA102',
|
||||
'SK9822',
|
||||
'DOTSTAR',
|
||||
]
|
||||
|
||||
RGB_ORDERS = [
|
||||
'RGB',
|
||||
'RBG',
|
||||
'GRB',
|
||||
'GBR',
|
||||
'BRG',
|
||||
'BGR',
|
||||
]
|
||||
|
||||
MakeFastLEDLight = Application.struct('MakeFastLEDLight')
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(light.PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(MakeFastLEDLight),
|
||||
|
||||
vol.Required(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True),
|
||||
vol.Required(CONF_DATA_PIN): pins.output_pin,
|
||||
vol.Required(CONF_CLOCK_PIN): pins.output_pin,
|
||||
|
||||
vol.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
|
||||
vol.Optional(CONF_RGB_ORDER): cv.one_of(*RGB_ORDERS, upper=True),
|
||||
vol.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds,
|
||||
|
||||
vol.Optional(CONF_POWER_SUPPLY): cv.use_variable_id(PowerSupplyComponent),
|
||||
vol.Optional(CONF_EFFECTS): light.validate_effects(
|
||||
light.ADDRESSABLE_EFFECTS),
|
||||
}).extend(light.ADDRESSABLE_LIGHT_SCHEMA.schema).extend(
|
||||
cv.COMPONENT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_fast_led_light(config[CONF_NAME])
|
||||
make = variable(config[CONF_MAKE_ID], rhs)
|
||||
fast_led = make.Pfast_led
|
||||
|
||||
rgb_order = None
|
||||
if CONF_RGB_ORDER in config:
|
||||
rgb_order = RawExpression(config[CONF_RGB_ORDER])
|
||||
template_args = TemplateArguments(RawExpression(config[CONF_CHIPSET]),
|
||||
config[CONF_DATA_PIN],
|
||||
config[CONF_CLOCK_PIN],
|
||||
rgb_order)
|
||||
add(fast_led.add_leds(template_args, config[CONF_NUM_LEDS]))
|
||||
|
||||
if CONF_MAX_REFRESH_RATE in config:
|
||||
add(fast_led.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE]))
|
||||
|
||||
if CONF_POWER_SUPPLY in config:
|
||||
power_supply = yield get_variable(config[CONF_POWER_SUPPLY])
|
||||
add(fast_led.set_power_supply(power_supply))
|
||||
|
||||
light.setup_light(make.Pstate, make.Pfast_led, config)
|
||||
setup_component(fast_led, config)
|
||||
|
||||
|
||||
REQUIRED_BUILD_FLAGS = '-DUSE_FAST_LED_LIGHT'
|
||||
|
||||
LIB_DEPS = 'FastLED@3.2.0'
|
||||
@@ -0,0 +1,272 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "light_traits.h"
|
||||
|
||||
#ifdef USE_JSON
|
||||
#include "esphome/components/json/json_util.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace light {
|
||||
|
||||
/** This class represents the color state for a light object.
|
||||
*
|
||||
* All values in this class are represented using floats in the range from 0.0 (off) to 1.0 (on).
|
||||
* Not all values have to be populated though, for example a simple monochromatic light only needs
|
||||
* to access the state and brightness attributes.
|
||||
*
|
||||
* PLease note all float values are automatically clamped.
|
||||
*
|
||||
* state - Whether the light should be on/off. Represented as a float for transitions.
|
||||
* brightness - The brightness of the light.
|
||||
* red, green, blue - RGB values.
|
||||
* white - The white value for RGBW lights.
|
||||
*/
|
||||
class LightColorValues {
|
||||
public:
|
||||
/// Construct the LightColorValues with all attributes enabled, but state set to 0.0
|
||||
LightColorValues()
|
||||
: state_(0.0f),
|
||||
brightness_(1.0f),
|
||||
red_(1.0f),
|
||||
green_(1.0f),
|
||||
blue_(1.0f),
|
||||
white_(1.0f),
|
||||
color_temperature_{1.0f} {}
|
||||
|
||||
LightColorValues(float state, float brightness, float red, float green, float blue, float white,
|
||||
float color_temperature = 1.0f) {
|
||||
this->set_state(state);
|
||||
this->set_brightness(brightness);
|
||||
this->set_red(red);
|
||||
this->set_green(green);
|
||||
this->set_blue(blue);
|
||||
this->set_white(white);
|
||||
this->set_color_temperature(color_temperature);
|
||||
}
|
||||
|
||||
LightColorValues(bool state, float brightness, float red, float green, float blue, float white,
|
||||
float color_temperature = 1.0f)
|
||||
: LightColorValues(state ? 1.0f : 0.0f, brightness, red, green, blue, white, color_temperature) {}
|
||||
|
||||
/// Create light color values from a binary true/false state.
|
||||
static LightColorValues from_binary(bool state) { return {state, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; }
|
||||
|
||||
/// Create light color values from a monochromatic brightness state.
|
||||
static LightColorValues from_monochromatic(float brightness) {
|
||||
if (brightness == 0.0f)
|
||||
return {0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f};
|
||||
else
|
||||
return {1.0f, brightness, 1.0f, 1.0f, 1.0f, 1.0f};
|
||||
}
|
||||
|
||||
/// Create light color values from an RGB state.
|
||||
static LightColorValues from_rgb(float r, float g, float b) {
|
||||
float brightness = std::max(r, std::max(g, b));
|
||||
if (brightness == 0.0f) {
|
||||
return {0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f};
|
||||
} else {
|
||||
return {1.0f, brightness, r / brightness, g / brightness, b / brightness, 1.0f};
|
||||
}
|
||||
}
|
||||
|
||||
/// Create light color values from an RGBW state.
|
||||
static LightColorValues from_rgbw(float r, float g, float b, float w) {
|
||||
float brightness = std::max(r, std::max(g, std::max(b, w)));
|
||||
if (brightness == 0.0f) {
|
||||
return {0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f};
|
||||
} else {
|
||||
return {1.0f, brightness, r / brightness, g / brightness, b / brightness, w / brightness};
|
||||
}
|
||||
}
|
||||
|
||||
/** Linearly interpolate between the values in start to the values in end.
|
||||
*
|
||||
* This function linearly interpolates the color value by just interpolating every attribute
|
||||
* independently.
|
||||
*
|
||||
* @param start The interpolation start values.
|
||||
* @param end The interpolation end values.
|
||||
* @param completion The completion value. 0 -> start, 1 -> end.
|
||||
* @return The linearly interpolated LightColorValues.
|
||||
*/
|
||||
static LightColorValues lerp(const LightColorValues &start, const LightColorValues &end, float completion) {
|
||||
LightColorValues v;
|
||||
v.set_state(esphome::lerp(completion, start.get_state(), end.get_state()));
|
||||
v.set_brightness(esphome::lerp(completion, start.get_brightness(), end.get_brightness()));
|
||||
v.set_red(esphome::lerp(completion, start.get_red(), end.get_red()));
|
||||
v.set_green(esphome::lerp(completion, start.get_green(), end.get_green()));
|
||||
v.set_blue(esphome::lerp(completion, start.get_blue(), end.get_blue()));
|
||||
v.set_white(esphome::lerp(completion, start.get_white(), end.get_white()));
|
||||
v.set_color_temperature(esphome::lerp(completion, start.get_color_temperature(), end.get_color_temperature()));
|
||||
return v;
|
||||
}
|
||||
|
||||
#ifdef USE_JSON
|
||||
/** Dump this color into a JsonObject. Only dumps values if the corresponding traits are marked supported by traits.
|
||||
*
|
||||
* @param root The json root object.
|
||||
* @param traits The traits object used for determining whether to include certain attributes.
|
||||
*/
|
||||
void dump_json(JsonObject &root, const LightTraits &traits) const {
|
||||
root["state"] = (this->get_state() != 0.0f) ? "ON" : "OFF";
|
||||
if (traits.get_supports_brightness())
|
||||
root["brightness"] = uint8_t(this->get_brightness() * 255);
|
||||
if (traits.get_supports_rgb()) {
|
||||
JsonObject &color = root.createNestedObject("color");
|
||||
color["r"] = uint8_t(this->get_red() * 255);
|
||||
color["g"] = uint8_t(this->get_green() * 255);
|
||||
color["b"] = uint8_t(this->get_blue() * 255);
|
||||
}
|
||||
if (traits.get_supports_rgb_white_value())
|
||||
root["white_value"] = uint8_t(this->get_white() * 255);
|
||||
if (traits.get_supports_color_temperature())
|
||||
root["color_temp"] = uint32_t(this->get_color_temperature());
|
||||
}
|
||||
#endif
|
||||
|
||||
/** Normalize the color (RGB/W) component.
|
||||
*
|
||||
* Divides all color attributes by the maximum attribute, so effectively set at least one attribute to 1.
|
||||
* For example: r=0.3, g=0.5, b=0.4 => r=0.6, g=1.0, b=0.8
|
||||
*
|
||||
* @param traits Used for determining which attributes to consider.
|
||||
*/
|
||||
void normalize_color(const LightTraits &traits) {
|
||||
if (traits.get_supports_rgb()) {
|
||||
float max_value = fmaxf(this->get_red(), fmaxf(this->get_green(), this->get_blue()));
|
||||
if (traits.get_supports_rgb_white_value()) {
|
||||
max_value = fmaxf(max_value, this->get_white());
|
||||
if (max_value == 0.0f) {
|
||||
this->set_white(1.0f);
|
||||
} else {
|
||||
this->set_white(this->get_white() / max_value);
|
||||
}
|
||||
}
|
||||
if (max_value == 0.0f) {
|
||||
this->set_red(1.0f);
|
||||
this->set_green(1.0f);
|
||||
this->set_blue(1.0f);
|
||||
} else {
|
||||
this->set_red(this->get_red() / max_value);
|
||||
this->set_green(this->get_green() / max_value);
|
||||
this->set_blue(this->get_blue() / max_value);
|
||||
}
|
||||
}
|
||||
|
||||
if (traits.get_supports_brightness() && this->get_brightness() == 0.0f) {
|
||||
if (traits.get_supports_rgb_white_value()) {
|
||||
// 0% brightness for RGBW[W] means no RGB channel, but white channel on.
|
||||
// do nothing
|
||||
} else {
|
||||
// 0% brightness means off
|
||||
this->set_state(false);
|
||||
// reset brightness to 100%
|
||||
this->set_brightness(1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert these light color values to a binary representation and write them to binary.
|
||||
void as_binary(bool *binary) const { *binary = this->state_ == 1.0f; }
|
||||
|
||||
/// Convert these light color values to a brightness-only representation and write them to brightness.
|
||||
void as_brightness(float *brightness) const { *brightness = this->state_ * this->brightness_; }
|
||||
|
||||
/// Convert these light color values to an RGB representation and write them to red, green, blue.
|
||||
void as_rgb(float *red, float *green, float *blue) const {
|
||||
*red = this->state_ * this->brightness_ * this->red_;
|
||||
*green = this->state_ * this->brightness_ * this->green_;
|
||||
*blue = this->state_ * this->brightness_ * this->blue_;
|
||||
}
|
||||
|
||||
/// Convert these light color values to an RGBW representation and write them to red, green, blue, white.
|
||||
void as_rgbw(float *red, float *green, float *blue, float *white) const {
|
||||
this->as_rgb(red, green, blue);
|
||||
*white = this->state_ * this->white_;
|
||||
}
|
||||
|
||||
/// Convert these light color values to an RGBWW representation with the given parameters.
|
||||
void as_rgbww(float color_temperature_cw, float color_temperature_ww, float *red, float *green, float *blue,
|
||||
float *cold_white, float *warm_white) const {
|
||||
this->as_rgb(red, green, blue);
|
||||
const float color_temp = clamp(this->color_temperature_, color_temperature_cw, color_temperature_ww);
|
||||
const float ww_fraction = (color_temp - color_temperature_cw) / (color_temperature_ww - color_temperature_cw);
|
||||
const float cw_fraction = 1.0f - ww_fraction;
|
||||
const float max_cw_ww = std::max(ww_fraction, cw_fraction);
|
||||
*cold_white = this->state_ * this->white_ * (cw_fraction / max_cw_ww);
|
||||
*warm_white = this->state_ * this->white_ * (ww_fraction / max_cw_ww);
|
||||
}
|
||||
|
||||
/// Convert these light color values to an CWWW representation with the given parameters.
|
||||
void as_cwww(float color_temperature_cw, float color_temperature_ww, float *cold_white, float *warm_white) const {
|
||||
const float color_temp = clamp(this->color_temperature_, color_temperature_cw, color_temperature_ww);
|
||||
const float ww_fraction = (color_temp - color_temperature_cw) / (color_temperature_ww - color_temperature_cw);
|
||||
const float cw_fraction = 1.0f - ww_fraction;
|
||||
const float max_cw_ww = std::max(ww_fraction, cw_fraction);
|
||||
*cold_white = this->state_ * this->brightness_ * (cw_fraction / max_cw_ww);
|
||||
*warm_white = this->state_ * this->brightness_ * (ww_fraction / max_cw_ww);
|
||||
}
|
||||
|
||||
/// Compare this LightColorValues to rhs, return true if and only if all attributes match.
|
||||
bool operator==(const LightColorValues &rhs) const {
|
||||
return state_ == rhs.state_ && brightness_ == rhs.brightness_ && red_ == rhs.red_ && green_ == rhs.green_ &&
|
||||
blue_ == rhs.blue_ && white_ == rhs.white_ && color_temperature_ == rhs.color_temperature_;
|
||||
}
|
||||
bool operator!=(const LightColorValues &rhs) const { return !(rhs == *this); }
|
||||
|
||||
/// Get the state of these light color values. In range from 0.0 (off) to 1.0 (on)
|
||||
float get_state() const { return this->state_; }
|
||||
/// Get the binary true/false state of these light color values.
|
||||
bool is_on() const { return this->get_state() != 0.0f; }
|
||||
/// Set the state of these light color values. In range from 0.0 (off) to 1.0 (on)
|
||||
void set_state(float state) { this->state_ = clamp(state, 0.0f, 1.0f); }
|
||||
/// Set the state of these light color values as a binary true/false.
|
||||
void set_state(bool state) { this->state_ = state ? 1.0f : 0.0f; }
|
||||
|
||||
/// Get the brightness property of these light color values. In range 0.0 to 1.0
|
||||
float get_brightness() const { return this->brightness_; }
|
||||
/// Set the brightness property of these light color values. In range 0.0 to 1.0
|
||||
void set_brightness(float brightness) { this->brightness_ = clamp(brightness, 0.0f, 1.0f); }
|
||||
|
||||
/// Get the red property of these light color values. In range 0.0 to 1.0
|
||||
float get_red() const { return this->red_; }
|
||||
/// Set the red property of these light color values. In range 0.0 to 1.0
|
||||
void set_red(float red) { this->red_ = clamp(red, 0.0f, 1.0f); }
|
||||
|
||||
/// Get the green property of these light color values. In range 0.0 to 1.0
|
||||
float get_green() const { return this->green_; }
|
||||
/// Set the green property of these light color values. In range 0.0 to 1.0
|
||||
void set_green(float green) { this->green_ = clamp(green, 0.0f, 1.0f); }
|
||||
|
||||
/// Get the blue property of these light color values. In range 0.0 to 1.0
|
||||
float get_blue() const { return this->blue_; }
|
||||
/// Set the blue property of these light color values. In range 0.0 to 1.0
|
||||
void set_blue(float blue) { this->blue_ = clamp(blue, 0.0f, 1.0f); }
|
||||
|
||||
/// Get the white property of these light color values. In range 0.0 to 1.0
|
||||
float get_white() const { return white_; }
|
||||
/// Set the white property of these light color values. In range 0.0 to 1.0
|
||||
void set_white(float white) { this->white_ = clamp(white, 0.0f, 1.0f); }
|
||||
|
||||
/// Get the color temperature property of these light color values in mired.
|
||||
float get_color_temperature() const { return this->color_temperature_; }
|
||||
/// Set the color temperature property of these light color values in mired.
|
||||
void set_color_temperature(float color_temperature) {
|
||||
this->color_temperature_ = std::max(0.000001f, color_temperature);
|
||||
}
|
||||
|
||||
protected:
|
||||
float state_; ///< ON / OFF, float for transition
|
||||
float brightness_;
|
||||
float red_;
|
||||
float green_;
|
||||
float blue_;
|
||||
float white_;
|
||||
float color_temperature_; ///< Color Temperature in Mired
|
||||
};
|
||||
|
||||
} // namespace light
|
||||
} // namespace esphome
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "light_color_values.h"
|
||||
#include "light_state.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace light {
|
||||
|
||||
class LightState;
|
||||
|
||||
class LightEffect {
|
||||
public:
|
||||
explicit LightEffect(const std::string &name) : name_(name) {}
|
||||
|
||||
/// Initialize this LightEffect. Will be called once after creation.
|
||||
virtual void start() {}
|
||||
|
||||
virtual void start_internal() { this->start(); }
|
||||
|
||||
/// Called when this effect is about to be removed
|
||||
virtual void stop() {}
|
||||
|
||||
/// Apply this effect. Use the provided state for starting transitions, ...
|
||||
virtual void apply() = 0;
|
||||
|
||||
const std::string &get_name() { return this->name_; }
|
||||
|
||||
/// Internal method called by the LightState when this light effect is registered in it.
|
||||
virtual void init() {}
|
||||
|
||||
void init_internal(LightState *state) {
|
||||
this->state_ = state;
|
||||
this->init();
|
||||
}
|
||||
|
||||
protected:
|
||||
LightState *state_{nullptr};
|
||||
std::string name_;
|
||||
};
|
||||
|
||||
} // namespace light
|
||||
} // namespace esphome
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "light_traits.h"
|
||||
#include "light_state.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace light {
|
||||
|
||||
class LightState;
|
||||
|
||||
/// Interface to write LightStates to hardware.
|
||||
class LightOutput {
|
||||
public:
|
||||
/// Return the LightTraits of this LightOutput.
|
||||
virtual LightTraits get_traits() = 0;
|
||||
|
||||
virtual void setup_state(LightState *state) {}
|
||||
|
||||
virtual void write_state(LightState *state) = 0;
|
||||
};
|
||||
|
||||
} // namespace light
|
||||
} // namespace esphome
|
||||
@@ -0,0 +1,703 @@
|
||||
#include "light_state.h"
|
||||
#include "light_output.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace light {
|
||||
|
||||
static const char *TAG = "light";
|
||||
|
||||
void LightState::start_transition_(const LightColorValues &target, uint32_t length) {
|
||||
this->transformer_ = make_unique<LightTransitionTransformer>(millis(), length, this->current_values, target);
|
||||
this->remote_values = this->transformer_->get_remote_values();
|
||||
}
|
||||
|
||||
void LightState::start_flash_(const LightColorValues &target, uint32_t length) {
|
||||
LightColorValues end_colors = this->current_values;
|
||||
// If starting a flash if one is already happening, set end values to end values of current flash
|
||||
// Hacky but works
|
||||
if (this->transformer_ != nullptr)
|
||||
end_colors = this->transformer_->get_end_values();
|
||||
this->transformer_ = make_unique<LightFlashTransformer>(millis(), length, end_colors, target);
|
||||
this->remote_values = this->transformer_->get_remote_values();
|
||||
}
|
||||
|
||||
LightState::LightState(const std::string &name, LightOutput *output) : Nameable(name), output_(output) {}
|
||||
|
||||
void LightState::set_immediately_(const LightColorValues &target) {
|
||||
this->transformer_ = nullptr;
|
||||
this->current_values = this->remote_values = target;
|
||||
this->next_write_ = true;
|
||||
}
|
||||
|
||||
LightColorValues LightState::get_current_values() { return this->current_values; }
|
||||
|
||||
void LightState::publish_state() {
|
||||
this->remote_values_callback_.call();
|
||||
this->next_write_ = true;
|
||||
}
|
||||
|
||||
LightColorValues LightState::get_remote_values() { return this->remote_values; }
|
||||
|
||||
std::string LightState::get_effect_name() {
|
||||
if (this->active_effect_index_ > 0)
|
||||
return this->effects_[this->active_effect_index_ - 1]->get_name();
|
||||
else
|
||||
return "None";
|
||||
}
|
||||
|
||||
void LightState::start_effect_(uint32_t effect_index) {
|
||||
this->stop_effect_();
|
||||
if (effect_index == 0)
|
||||
return;
|
||||
|
||||
this->active_effect_index_ = effect_index;
|
||||
auto *effect = this->get_active_effect_();
|
||||
effect->start_internal();
|
||||
}
|
||||
|
||||
bool LightState::supports_effects() { return !this->effects_.empty(); }
|
||||
void LightState::set_transformer_(std::unique_ptr<LightTransformer> transformer) {
|
||||
this->transformer_ = std::move(transformer);
|
||||
}
|
||||
void LightState::stop_effect_() {
|
||||
auto *effect = this->get_active_effect_();
|
||||
if (effect != nullptr) {
|
||||
effect->stop();
|
||||
}
|
||||
this->active_effect_index_ = 0;
|
||||
}
|
||||
|
||||
void LightState::set_default_transition_length(uint32_t default_transition_length) {
|
||||
this->default_transition_length_ = default_transition_length;
|
||||
}
|
||||
#ifdef USE_JSON
|
||||
void LightState::dump_json(JsonObject &root) {
|
||||
if (this->supports_effects())
|
||||
root["effect"] = this->get_effect_name();
|
||||
this->remote_values.dump_json(root, this->output_->get_traits());
|
||||
}
|
||||
#endif
|
||||
|
||||
struct LightStateRTCState {
|
||||
bool state{false};
|
||||
float brightness{1.0f};
|
||||
float red{1.0f};
|
||||
float green{1.0f};
|
||||
float blue{1.0f};
|
||||
float white{1.0f};
|
||||
float color_temp{1.0f};
|
||||
uint32_t effect{0};
|
||||
};
|
||||
|
||||
void LightState::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up light '%s'...", this->get_name().c_str());
|
||||
|
||||
this->output_->setup_state(this);
|
||||
for (auto *effect : this->effects_) {
|
||||
effect->init_internal(this);
|
||||
}
|
||||
|
||||
this->rtc_ = global_preferences.make_preference<LightStateRTCState>(this->get_object_id_hash());
|
||||
LightStateRTCState recovered{};
|
||||
// Attempt to load from preferences, else fall back to default values from struct
|
||||
this->rtc_.load(&recovered);
|
||||
|
||||
auto call = this->make_call();
|
||||
call.set_state(recovered.state);
|
||||
call.set_brightness_if_supported(recovered.brightness);
|
||||
call.set_red_if_supported(recovered.red);
|
||||
call.set_green_if_supported(recovered.green);
|
||||
call.set_blue_if_supported(recovered.blue);
|
||||
call.set_white_if_supported(recovered.white);
|
||||
call.set_color_temperature_if_supported(recovered.color_temp);
|
||||
if (recovered.effect != 0) {
|
||||
call.set_effect(recovered.effect);
|
||||
} else {
|
||||
call.set_transition_length_if_supported(0);
|
||||
}
|
||||
call.perform();
|
||||
}
|
||||
void LightState::loop() {
|
||||
// Apply effect (if any)
|
||||
auto *effect = this->get_active_effect_();
|
||||
if (effect != nullptr) {
|
||||
effect->apply();
|
||||
}
|
||||
|
||||
// Apply transformer (if any)
|
||||
if (this->transformer_ != nullptr) {
|
||||
if (this->transformer_->is_finished()) {
|
||||
this->remote_values = this->current_values = this->transformer_->get_end_values();
|
||||
if (this->transformer_->publish_at_end())
|
||||
this->publish_state();
|
||||
this->transformer_ = nullptr;
|
||||
} else {
|
||||
this->current_values = this->transformer_->get_values();
|
||||
this->remote_values = this->transformer_->get_remote_values();
|
||||
}
|
||||
this->next_write_ = true;
|
||||
}
|
||||
|
||||
if (this->next_write_) {
|
||||
this->output_->write_state(this);
|
||||
this->next_write_ = false;
|
||||
}
|
||||
}
|
||||
LightTraits LightState::get_traits() { return this->output_->get_traits(); }
|
||||
const std::vector<LightEffect *> &LightState::get_effects() const { return this->effects_; }
|
||||
void LightState::add_effects(const std::vector<LightEffect *> effects) {
|
||||
this->effects_.reserve(this->effects_.size() + effects.size());
|
||||
for (auto *effect : effects) {
|
||||
this->effects_.push_back(effect);
|
||||
}
|
||||
}
|
||||
LightCall LightState::turn_on() { return this->make_call().set_state(true); }
|
||||
LightCall LightState::turn_off() { return this->make_call().set_state(false); }
|
||||
LightCall LightState::toggle() { return this->make_call().set_state(!this->remote_values.is_on()); }
|
||||
LightCall LightState::make_call() { return LightCall(this); }
|
||||
uint32_t LightState::hash_base() { return 1114400283; }
|
||||
void LightState::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Light '%s'", this->get_name().c_str());
|
||||
if (this->get_traits().get_supports_brightness()) {
|
||||
ESP_LOGCONFIG(TAG, " Default Transition Length: %.1fs", this->default_transition_length_ / 1e3f);
|
||||
ESP_LOGCONFIG(TAG, " Gamma Correct: %.2f", this->gamma_correct_);
|
||||
}
|
||||
if (this->get_traits().get_supports_color_temperature()) {
|
||||
ESP_LOGCONFIG(TAG, " Min Mireds: %.1f", this->get_traits().get_min_mireds());
|
||||
ESP_LOGCONFIG(TAG, " Max Mireds: %.1f", this->get_traits().get_max_mireds());
|
||||
}
|
||||
}
|
||||
#ifdef USE_MQTT_LIGHT
|
||||
MQTTJSONLightComponent *LightState::get_mqtt() const { return this->mqtt_; }
|
||||
void LightState::set_mqtt(MQTTJSONLightComponent *mqtt) { this->mqtt_ = mqtt; }
|
||||
#endif
|
||||
|
||||
#ifdef USE_JSON
|
||||
LightCall &LightCall::parse_color_json(JsonObject &root) {
|
||||
if (root.containsKey("state")) {
|
||||
auto val = parse_on_off(root["state"]);
|
||||
switch (val) {
|
||||
case PARSE_ON:
|
||||
this->set_state(true);
|
||||
break;
|
||||
case PARSE_OFF:
|
||||
this->set_state(false);
|
||||
break;
|
||||
case PARSE_TOGGLE:
|
||||
this->set_state(!this->parent_->remote_values.is_on());
|
||||
break;
|
||||
case PARSE_NONE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (root.containsKey("brightness")) {
|
||||
this->set_brightness(float(root["brightness"]) / 255.0f);
|
||||
}
|
||||
|
||||
if (root.containsKey("color")) {
|
||||
JsonObject &color = root["color"];
|
||||
if (color.containsKey("r")) {
|
||||
this->set_red(float(color["r"]) / 255.0f);
|
||||
}
|
||||
if (color.containsKey("g")) {
|
||||
this->set_green(float(color["g"]) / 255.0f);
|
||||
}
|
||||
if (color.containsKey("b")) {
|
||||
this->set_blue(float(color["b"]) / 255.0f);
|
||||
}
|
||||
}
|
||||
|
||||
if (root.containsKey("white_value")) {
|
||||
this->set_white(float(root["white_value"]) / 255.0f);
|
||||
}
|
||||
|
||||
if (root.containsKey("color_temp")) {
|
||||
this->set_color_temperature(float(root["color_temp"]));
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::parse_json(JsonObject &root) {
|
||||
this->parse_color_json(root);
|
||||
|
||||
if (root.containsKey("flash")) {
|
||||
auto length = uint32_t(float(root["flash"]) * 1000);
|
||||
this->set_flash_length(length);
|
||||
}
|
||||
|
||||
if (root.containsKey("transition")) {
|
||||
auto length = uint32_t(float(root["transition"]) * 1000);
|
||||
this->set_transition_length(length);
|
||||
}
|
||||
|
||||
if (root.containsKey("effect")) {
|
||||
const char *effect = root["effect"];
|
||||
this->set_effect(effect);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
void LightCall::perform() {
|
||||
// use remote values for fallback
|
||||
const char *name = this->parent_->get_name().c_str();
|
||||
if (this->publish_) {
|
||||
ESP_LOGD(TAG, "'%s' Setting:", name);
|
||||
}
|
||||
|
||||
LightColorValues v = this->validate_();
|
||||
|
||||
if (this->publish_) {
|
||||
// Only print state when it's being changed
|
||||
bool current_state = this->parent_->remote_values.is_on();
|
||||
if (this->state_.value_or(current_state) != current_state) {
|
||||
ESP_LOGD(TAG, " State: %s", ONOFF(v.is_on()));
|
||||
}
|
||||
|
||||
if (this->brightness_.has_value()) {
|
||||
ESP_LOGD(TAG, " Brightness: %.0f%%", v.get_brightness() * 100.0f);
|
||||
}
|
||||
|
||||
if (this->color_temperature_.has_value()) {
|
||||
ESP_LOGD(TAG, " Color Temperature: %.1f mireds", v.get_color_temperature());
|
||||
}
|
||||
|
||||
if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
|
||||
ESP_LOGD(TAG, " Red=%.0f%%, Green=%.0f%%, Blue=%.0f%%", v.get_red() * 100.0f, v.get_green() * 100.0f,
|
||||
v.get_blue() * 100.0f);
|
||||
}
|
||||
if (this->white_.has_value()) {
|
||||
ESP_LOGD(TAG, " White Value: %.0f%%", v.get_white() * 100.0f);
|
||||
}
|
||||
}
|
||||
|
||||
if (this->has_flash_()) {
|
||||
// FLASH
|
||||
if (this->publish_) {
|
||||
ESP_LOGD(TAG, " Flash Length: %.1fs", *this->flash_length_ / 1e3f);
|
||||
}
|
||||
|
||||
this->parent_->start_flash_(v, *this->flash_length_);
|
||||
} else if (this->has_transition_()) {
|
||||
// TRANSITION
|
||||
if (this->publish_) {
|
||||
ESP_LOGD(TAG, " Transition Length: %.1fs", *this->transition_length_ / 1e3f);
|
||||
}
|
||||
|
||||
// Special case: Transition and effect can be set when turning off
|
||||
if (this->has_effect_()) {
|
||||
if (this->publish_) {
|
||||
ESP_LOGD(TAG, " Effect: 'None'");
|
||||
}
|
||||
this->parent_->stop_effect_();
|
||||
}
|
||||
|
||||
this->parent_->start_transition_(v, *this->transition_length_);
|
||||
|
||||
} else if (this->has_effect_()) {
|
||||
// EFFECT
|
||||
auto effect = this->effect_;
|
||||
const char *effect_s;
|
||||
if (effect == 0)
|
||||
effect_s = "None";
|
||||
else
|
||||
effect_s = this->parent_->effects_[*this->effect_ - 1]->get_name().c_str();
|
||||
|
||||
if (this->publish_) {
|
||||
ESP_LOGD(TAG, " Effect: '%s'", effect_s);
|
||||
}
|
||||
|
||||
this->parent_->start_effect_(*this->effect_);
|
||||
|
||||
// Also set light color values when starting an effect
|
||||
// For example to turn off the light
|
||||
this->parent_->set_immediately_(v);
|
||||
} else {
|
||||
// INSTANT CHANGE
|
||||
this->parent_->set_immediately_(v);
|
||||
}
|
||||
|
||||
if (this->publish_) {
|
||||
this->parent_->publish_state();
|
||||
}
|
||||
|
||||
if (this->save_) {
|
||||
LightStateRTCState saved;
|
||||
saved.state = v.is_on();
|
||||
saved.brightness = v.get_brightness();
|
||||
saved.red = v.get_red();
|
||||
saved.green = v.get_green();
|
||||
saved.blue = v.get_blue();
|
||||
saved.white = v.get_white();
|
||||
saved.color_temp = v.get_color_temperature();
|
||||
saved.effect = this->parent_->active_effect_index_;
|
||||
this->parent_->rtc_.save(&saved);
|
||||
}
|
||||
}
|
||||
|
||||
LightColorValues LightCall::validate_() {
|
||||
// use remote values for fallback
|
||||
auto *name = this->parent_->get_name().c_str();
|
||||
auto traits = this->parent_->get_traits();
|
||||
|
||||
// Brightness exists check
|
||||
if (this->brightness_.has_value() && !traits.get_supports_brightness()) {
|
||||
ESP_LOGW(TAG, "'%s' - This light does not support setting brightness!", name);
|
||||
this->brightness_.reset();
|
||||
}
|
||||
|
||||
// Transition length possible check
|
||||
if (this->transition_length_.has_value() && *this->transition_length_ != 0 && !traits.get_supports_brightness()) {
|
||||
ESP_LOGW(TAG, "'%s' - This light does not support transitions!", name);
|
||||
this->transition_length_.reset();
|
||||
}
|
||||
|
||||
// RGB exists check
|
||||
if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
|
||||
if (!traits.get_supports_rgb()) {
|
||||
ESP_LOGW(TAG, "'%s' - This light does not support setting RGB color!", name);
|
||||
this->red_.reset();
|
||||
this->green_.reset();
|
||||
this->blue_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// White value exists check
|
||||
if (this->white_.has_value() && !traits.get_supports_rgb_white_value()) {
|
||||
ESP_LOGW(TAG, "'%s' - This light does not support setting white value!", name);
|
||||
this->white_.reset();
|
||||
}
|
||||
|
||||
// Color temperature exists check
|
||||
if (this->color_temperature_.has_value() && !traits.get_supports_color_temperature()) {
|
||||
ESP_LOGW(TAG, "'%s' - This light does not support setting color temperature!", name);
|
||||
this->color_temperature_.reset();
|
||||
}
|
||||
|
||||
#define VALIDATE_RANGE_(name_, upper_name) \
|
||||
if (name_##_.has_value()) { \
|
||||
auto val = *name_##_; \
|
||||
if (val < 0.0f || val > 1.0f) { \
|
||||
ESP_LOGW(TAG, "'%s' - %s value %.2f is out of range [0.0 - 1.0]!", name, upper_name, val); \
|
||||
name_##_ = clamp(val, 0.0f, 1.0f); \
|
||||
} \
|
||||
}
|
||||
#define VALIDATE_RANGE(name, upper_name) VALIDATE_RANGE_(name, upper_name)
|
||||
|
||||
// Range checks
|
||||
VALIDATE_RANGE(brightness, "Brightness")
|
||||
VALIDATE_RANGE(red, "Red")
|
||||
VALIDATE_RANGE(green, "Green")
|
||||
VALIDATE_RANGE(blue, "Blue")
|
||||
VALIDATE_RANGE(white, "White")
|
||||
|
||||
auto v = this->parent_->remote_values;
|
||||
if (this->state_.has_value())
|
||||
v.set_state(*this->state_);
|
||||
if (this->brightness_.has_value())
|
||||
v.set_brightness(*this->brightness_);
|
||||
|
||||
if (this->brightness_.has_value())
|
||||
v.set_brightness(*this->brightness_);
|
||||
if (this->red_.has_value())
|
||||
v.set_red(*this->red_);
|
||||
if (this->green_.has_value())
|
||||
v.set_green(*this->green_);
|
||||
if (this->blue_.has_value())
|
||||
v.set_blue(*this->blue_);
|
||||
if (this->white_.has_value())
|
||||
v.set_white(*this->white_);
|
||||
|
||||
if (this->color_temperature_.has_value())
|
||||
v.set_color_temperature(*this->color_temperature_);
|
||||
|
||||
v.normalize_color(traits);
|
||||
|
||||
// Flash length check
|
||||
if (this->has_flash_() && *this->flash_length_ == 0) {
|
||||
ESP_LOGW(TAG, "'%s' - Flash length must be greater than zero!", name);
|
||||
this->flash_length_.reset();
|
||||
}
|
||||
|
||||
// validate transition length/flash length/effect not used at the same time
|
||||
bool supports_transition = traits.get_supports_brightness();
|
||||
|
||||
// If effect is already active, remove effect start
|
||||
if (this->has_effect_() && *this->effect_ == this->parent_->active_effect_index_) {
|
||||
this->effect_.reset();
|
||||
}
|
||||
|
||||
// validate effect index
|
||||
if (this->has_effect_() && *this->effect_ > this->parent_->effects_.size()) {
|
||||
ESP_LOGW(TAG, "'%s' Invalid effect index %u", name, *this->effect_);
|
||||
this->effect_.reset();
|
||||
}
|
||||
|
||||
if (this->has_effect_() && (this->has_transition_() || this->has_flash_())) {
|
||||
ESP_LOGW(TAG, "'%s' - Effect cannot be used together with transition/flash!", name);
|
||||
this->transition_length_.reset();
|
||||
this->flash_length_.reset();
|
||||
}
|
||||
|
||||
if (this->has_flash_() && this->has_transition_()) {
|
||||
ESP_LOGW(TAG, "'%s' - Flash cannot be used together with transition!", name);
|
||||
this->transition_length_.reset();
|
||||
}
|
||||
|
||||
if (!this->has_transition_() && !this->has_flash_() && !this->has_effect_() && supports_transition) {
|
||||
// nothing specified and light supports transitions, set default transition length
|
||||
this->transition_length_ = this->parent_->default_transition_length_;
|
||||
}
|
||||
|
||||
if (this->transition_length_.value_or(0) == 0) {
|
||||
// 0 transition is interpreted as no transition (instant change)
|
||||
this->transition_length_.reset();
|
||||
}
|
||||
|
||||
if (this->has_transition_() && !supports_transition) {
|
||||
ESP_LOGW(TAG, "'%s' - Light does not support transitions!", name);
|
||||
this->transition_length_.reset();
|
||||
}
|
||||
|
||||
// If not a flash and turning the light off, then disable the light
|
||||
// Do not use light color values directly, so that effects can set 0% brightness
|
||||
// Reason: When user turns off the light in frontend, the effect should also stop
|
||||
if (!this->has_flash_() && !this->state_.value_or(v.is_on())) {
|
||||
if (this->has_effect_()) {
|
||||
ESP_LOGW(TAG, "'%s' - Cannot start an effect when turning off!", name);
|
||||
this->effect_.reset();
|
||||
} else if (this->parent_->active_effect_index_ != 0) {
|
||||
// Auto turn off effect
|
||||
this->effect_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Disable saving for flashes
|
||||
if (this->has_flash_())
|
||||
this->save_ = false;
|
||||
|
||||
return v;
|
||||
}
|
||||
LightCall &LightCall::set_effect(const std::string &effect) {
|
||||
if (strcasecmp(effect.c_str(), "none") == 0) {
|
||||
this->set_effect(0);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
for (uint32_t i = 0; i < this->parent_->effects_.size(); i++) {
|
||||
LightEffect *e = this->parent_->effects_[i];
|
||||
|
||||
if (strcasecmp(effect.c_str(), e->get_name().c_str()) == 0) {
|
||||
this->set_effect(i + 1);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
ESP_LOGW(TAG, "'%s' - No such effect '%s'", this->parent_->get_name().c_str(), effect.c_str());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::from_light_color_values(const LightColorValues &values) {
|
||||
this->set_state(values.is_on());
|
||||
this->set_brightness_if_supported(values.get_brightness());
|
||||
this->set_red_if_supported(values.get_red());
|
||||
this->set_green_if_supported(values.get_green());
|
||||
this->set_blue_if_supported(values.get_blue());
|
||||
this->set_white_if_supported(values.get_white());
|
||||
this->set_color_temperature_if_supported(values.get_color_temperature());
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_transition_length_if_supported(uint32_t transition_length) {
|
||||
if (this->parent_->get_traits().get_supports_brightness())
|
||||
this->set_transition_length(transition_length);
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_brightness_if_supported(float brightness) {
|
||||
if (this->parent_->get_traits().get_supports_brightness())
|
||||
this->set_brightness(brightness);
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_red_if_supported(float red) {
|
||||
if (this->parent_->get_traits().get_supports_rgb())
|
||||
this->set_red(red);
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_green_if_supported(float green) {
|
||||
if (this->parent_->get_traits().get_supports_rgb())
|
||||
this->set_green(green);
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_blue_if_supported(float blue) {
|
||||
if (this->parent_->get_traits().get_supports_rgb())
|
||||
this->set_blue(blue);
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_white_if_supported(float white) {
|
||||
if (this->parent_->get_traits().get_supports_rgb_white_value())
|
||||
this->set_white(white);
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_color_temperature_if_supported(float color_temperature) {
|
||||
if (this->parent_->get_traits().get_supports_color_temperature())
|
||||
this->set_color_temperature(color_temperature);
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_state(optional<bool> state) {
|
||||
this->state_ = state;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_state(bool state) {
|
||||
this->state_ = state;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_transition_length(optional<uint32_t> transition_length) {
|
||||
this->transition_length_ = transition_length;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_transition_length(uint32_t transition_length) {
|
||||
this->transition_length_ = transition_length;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_flash_length(optional<uint32_t> flash_length) {
|
||||
this->flash_length_ = flash_length;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_flash_length(uint32_t flash_length) {
|
||||
this->flash_length_ = flash_length;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_brightness(optional<float> brightness) {
|
||||
this->brightness_ = brightness;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_brightness(float brightness) {
|
||||
this->brightness_ = brightness;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_red(optional<float> red) {
|
||||
this->red_ = red;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_red(float red) {
|
||||
this->red_ = red;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_green(optional<float> green) {
|
||||
this->green_ = green;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_green(float green) {
|
||||
this->green_ = green;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_blue(optional<float> blue) {
|
||||
this->blue_ = blue;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_blue(float blue) {
|
||||
this->blue_ = blue;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_white(optional<float> white) {
|
||||
this->white_ = white;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_white(float white) {
|
||||
this->white_ = white;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_color_temperature(optional<float> color_temperature) {
|
||||
this->color_temperature_ = color_temperature;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_color_temperature(float color_temperature) {
|
||||
this->color_temperature_ = color_temperature;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_effect(optional<std::string> effect) {
|
||||
if (effect.has_value())
|
||||
this->set_effect(*effect);
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_effect(uint32_t effect_number) {
|
||||
this->effect_ = effect_number;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_effect(optional<uint32_t> effect_number) {
|
||||
this->effect_ = effect_number;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_publish(bool publish) {
|
||||
this->publish_ = publish;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_save(bool save) {
|
||||
this->save_ = save;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_rgb(float red, float green, float blue) {
|
||||
this->set_red(red);
|
||||
this->set_green(green);
|
||||
this->set_blue(blue);
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_rgbw(float red, float green, float blue, float white) {
|
||||
this->set_rgb(red, green, blue);
|
||||
this->set_white(white);
|
||||
return *this;
|
||||
}
|
||||
|
||||
float LightState::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; }
|
||||
LightOutput *LightState::get_output() const { return this->output_; }
|
||||
void LightState::set_gamma_correct(float gamma_correct) { this->gamma_correct_ = gamma_correct; }
|
||||
void LightState::current_values_as_binary(bool *binary) { this->current_values.as_binary(binary); }
|
||||
void LightState::current_values_as_brightness(float *brightness) {
|
||||
this->current_values.as_brightness(brightness);
|
||||
*brightness = gamma_correct(*brightness, this->gamma_correct_);
|
||||
}
|
||||
void LightState::current_values_as_rgb(float *red, float *green, float *blue) {
|
||||
this->current_values.as_rgb(red, green, blue);
|
||||
*red = gamma_correct(*red, this->gamma_correct_);
|
||||
*green = gamma_correct(*green, this->gamma_correct_);
|
||||
*blue = gamma_correct(*blue, this->gamma_correct_);
|
||||
}
|
||||
void LightState::current_values_as_rgbw(float *red, float *green, float *blue, float *white) {
|
||||
this->current_values.as_rgbw(red, green, blue, white);
|
||||
*red = gamma_correct(*red, this->gamma_correct_);
|
||||
*green = gamma_correct(*green, this->gamma_correct_);
|
||||
*blue = gamma_correct(*blue, this->gamma_correct_);
|
||||
*white = gamma_correct(*white, this->gamma_correct_);
|
||||
}
|
||||
void LightState::current_values_as_rgbww(float *red, float *green, float *blue, float *cold_white, float *warm_white) {
|
||||
auto traits = this->get_traits();
|
||||
this->current_values.as_rgbww(traits.get_min_mireds(), traits.get_max_mireds(), red, green, blue, cold_white,
|
||||
warm_white);
|
||||
*red = gamma_correct(*red, this->gamma_correct_);
|
||||
*green = gamma_correct(*green, this->gamma_correct_);
|
||||
*blue = gamma_correct(*blue, this->gamma_correct_);
|
||||
*cold_white = gamma_correct(*cold_white, this->gamma_correct_);
|
||||
*warm_white = gamma_correct(*warm_white, this->gamma_correct_);
|
||||
}
|
||||
void LightState::current_values_as_cwww(float *cold_white, float *warm_white) {
|
||||
auto traits = this->get_traits();
|
||||
this->current_values.as_cwww(traits.get_min_mireds(), traits.get_max_mireds(), cold_white, warm_white);
|
||||
*cold_white = gamma_correct(*cold_white, this->gamma_correct_);
|
||||
*warm_white = gamma_correct(*warm_white, this->gamma_correct_);
|
||||
}
|
||||
void LightState::add_new_remote_values_callback(std::function<void()> &&send_callback) {
|
||||
this->remote_values_callback_.add(std::move(send_callback));
|
||||
}
|
||||
LightEffect *LightState::get_active_effect_() {
|
||||
if (this->active_effect_index_ == 0)
|
||||
return nullptr;
|
||||
else
|
||||
return this->effects_[this->active_effect_index_ - 1];
|
||||
}
|
||||
|
||||
} // namespace light
|
||||
} // namespace esphome
|
||||
@@ -0,0 +1,319 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/optional.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "light_effect.h"
|
||||
#include "light_color_values.h"
|
||||
#include "light_traits.h"
|
||||
#include "light_transformer.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace light {
|
||||
|
||||
class LightState;
|
||||
class LightOutput;
|
||||
|
||||
class LightCall {
|
||||
public:
|
||||
explicit LightCall(LightState *parent) : parent_(parent) {}
|
||||
|
||||
/// Set the binary ON/OFF state of the light.
|
||||
LightCall &set_state(optional<bool> state);
|
||||
/// Set the binary ON/OFF state of the light.
|
||||
LightCall &set_state(bool state);
|
||||
/** Set the transition length of this call in milliseconds.
|
||||
*
|
||||
* This argument is ignored for starting flashes and effects.
|
||||
*
|
||||
* Defaults to the default transition length defined in the light configuration.
|
||||
*/
|
||||
LightCall &set_transition_length(optional<uint32_t> transition_length);
|
||||
/** Set the transition length of this call in milliseconds.
|
||||
*
|
||||
* This argument is ignored for starting flashes and effects.
|
||||
*
|
||||
* Defaults to the default transition length defined in the light configuration.
|
||||
*/
|
||||
LightCall &set_transition_length(uint32_t transition_length);
|
||||
/// Set the transition length property if the light supports transitions.
|
||||
LightCall &set_transition_length_if_supported(uint32_t transition_length);
|
||||
/// Start and set the flash length of this call in milliseconds.
|
||||
LightCall &set_flash_length(optional<uint32_t> flash_length);
|
||||
/// Start and set the flash length of this call in milliseconds.
|
||||
LightCall &set_flash_length(uint32_t flash_length);
|
||||
/// Set the target brightness of the light from 0.0 (fully off) to 1.0 (fully on)
|
||||
LightCall &set_brightness(optional<float> brightness);
|
||||
/// Set the target brightness of the light from 0.0 (fully off) to 1.0 (fully on)
|
||||
LightCall &set_brightness(float brightness);
|
||||
/// Set the brightness property if the light supports brightness.
|
||||
LightCall &set_brightness_if_supported(float brightness);
|
||||
/** Set the red RGB value of the light from 0.0 to 1.0.
|
||||
*
|
||||
* Note that this only controls the color of the light, not its brightness.
|
||||
*/
|
||||
LightCall &set_red(optional<float> red);
|
||||
/** Set the red RGB value of the light from 0.0 to 1.0.
|
||||
*
|
||||
* Note that this only controls the color of the light, not its brightness.
|
||||
*/
|
||||
LightCall &set_red(float red);
|
||||
/// Set the red property if the light supports RGB.
|
||||
LightCall &set_red_if_supported(float red);
|
||||
/** Set the green RGB value of the light from 0.0 to 1.0.
|
||||
*
|
||||
* Note that this only controls the color of the light, not its brightness.
|
||||
*/
|
||||
LightCall &set_green(optional<float> green);
|
||||
/** Set the green RGB value of the light from 0.0 to 1.0.
|
||||
*
|
||||
* Note that this only controls the color of the light, not its brightness.
|
||||
*/
|
||||
LightCall &set_green(float green);
|
||||
/// Set the green property if the light supports RGB.
|
||||
LightCall &set_green_if_supported(float green);
|
||||
/** Set the blue RGB value of the light from 0.0 to 1.0.
|
||||
*
|
||||
* Note that this only controls the color of the light, not its brightness.
|
||||
*/
|
||||
LightCall &set_blue(optional<float> blue);
|
||||
/** Set the blue RGB value of the light from 0.0 to 1.0.
|
||||
*
|
||||
* Note that this only controls the color of the light, not its brightness.
|
||||
*/
|
||||
LightCall &set_blue(float blue);
|
||||
/// Set the blue property if the light supports RGB.
|
||||
LightCall &set_blue_if_supported(float blue);
|
||||
/// Set the white value value of the light from 0.0 to 1.0 for RGBW[W] lights.
|
||||
LightCall &set_white(optional<float> white);
|
||||
/// Set the white value value of the light from 0.0 to 1.0 for RGBW[W] lights.
|
||||
LightCall &set_white(float white);
|
||||
/// Set the white property if the light supports RGB.
|
||||
LightCall &set_white_if_supported(float white);
|
||||
/// Set the color temperature of the light in mireds for CWWW or RGBWW lights.
|
||||
LightCall &set_color_temperature(optional<float> color_temperature);
|
||||
/// Set the color temperature of the light in mireds for CWWW or RGBWW lights.
|
||||
LightCall &set_color_temperature(float color_temperature);
|
||||
/// Set the color_temperature property if the light supports color temperature.
|
||||
LightCall &set_color_temperature_if_supported(float color_temperature);
|
||||
/// Set the effect of the light by its name.
|
||||
LightCall &set_effect(optional<std::string> effect);
|
||||
/// Set the effect of the light by its name.
|
||||
LightCall &set_effect(const std::string &effect);
|
||||
/// Set the effect of the light by its internal index number (only for internal use).
|
||||
LightCall &set_effect(uint32_t effect_number);
|
||||
LightCall &set_effect(optional<uint32_t> effect_number);
|
||||
/// Set whether this light call should trigger a publish state.
|
||||
LightCall &set_publish(bool publish);
|
||||
/// Set whether this light call should trigger a save state to recover them at startup..
|
||||
LightCall &set_save(bool save);
|
||||
|
||||
/** Set the RGB color of the light by RGB values.
|
||||
*
|
||||
* Please note that this only changes the color of the light, not the brightness.
|
||||
*
|
||||
* @param red The red color value from 0.0 to 1.0.
|
||||
* @param green The green color value from 0.0 to 1.0.
|
||||
* @param blue The blue color value from 0.0 to 1.0.
|
||||
* @return The light call for chaining setters.
|
||||
*/
|
||||
LightCall &set_rgb(float red, float green, float blue);
|
||||
/** Set the RGBW color of the light by RGB values.
|
||||
*
|
||||
* Please note that this only changes the color of the light, not the brightness.
|
||||
*
|
||||
* @param red The red color value from 0.0 to 1.0.
|
||||
* @param green The green color value from 0.0 to 1.0.
|
||||
* @param blue The blue color value from 0.0 to 1.0.
|
||||
* @param white The white color value from 0.0 to 1.0.
|
||||
* @return The light call for chaining setters.
|
||||
*/
|
||||
LightCall &set_rgbw(float red, float green, float blue, float white);
|
||||
#ifdef USE_JSON
|
||||
LightCall &parse_color_json(JsonObject &root);
|
||||
LightCall &parse_json(JsonObject &root);
|
||||
#endif
|
||||
LightCall &from_light_color_values(const LightColorValues &values);
|
||||
|
||||
void perform();
|
||||
|
||||
protected:
|
||||
/// Validate all properties and return the target light color values.
|
||||
LightColorValues validate_();
|
||||
|
||||
bool has_transition_() { return this->transition_length_.has_value(); }
|
||||
bool has_flash_() { return this->flash_length_.has_value(); }
|
||||
bool has_effect_() { return this->effect_.has_value(); }
|
||||
|
||||
LightState *parent_;
|
||||
optional<bool> state_;
|
||||
optional<uint32_t> transition_length_;
|
||||
optional<uint32_t> flash_length_;
|
||||
optional<float> brightness_;
|
||||
optional<float> red_;
|
||||
optional<float> green_;
|
||||
optional<float> blue_;
|
||||
optional<float> white_;
|
||||
optional<float> color_temperature_;
|
||||
optional<uint32_t> effect_;
|
||||
bool publish_{true};
|
||||
bool save_{true};
|
||||
};
|
||||
|
||||
/** This class represents the communication layer between the front-end MQTT layer and the
|
||||
* hardware output layer.
|
||||
*/
|
||||
class LightState : public Nameable, public Component {
|
||||
public:
|
||||
/// Construct this LightState using the provided traits and name.
|
||||
LightState(const std::string &name, LightOutput *output);
|
||||
|
||||
LightTraits get_traits();
|
||||
|
||||
/// Make a light state call
|
||||
LightCall turn_on();
|
||||
LightCall turn_off();
|
||||
LightCall toggle();
|
||||
LightCall make_call();
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
/// Load state from preferences
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
/// Shortly after HARDWARE.
|
||||
float get_setup_priority() const override;
|
||||
|
||||
/** The current values of the light as outputted to the light.
|
||||
*
|
||||
* These values represent the "real" state of the light - During transitions this
|
||||
* property will be changed continuously (in contrast to .remote_values, where they
|
||||
* are constant during transitions).
|
||||
*
|
||||
* This property is read-only for users. Any changes to it will be ignored.
|
||||
*/
|
||||
LightColorValues current_values;
|
||||
|
||||
/// Deprecated method to access current_values.
|
||||
ESPDEPRECATED("get_current_values() is deprecated, please use .current_values instead.")
|
||||
LightColorValues get_current_values();
|
||||
|
||||
/// Deprecated method to access remote_values.
|
||||
ESPDEPRECATED("get_remote_values() is deprecated, please use .remote_values instead.")
|
||||
LightColorValues get_remote_values();
|
||||
|
||||
/** The remote color values reported to the frontend.
|
||||
*
|
||||
* These are different from the "current" values: For example transitions will
|
||||
* continuously change the "current" values. But the remote values will immediately
|
||||
* switch to the target value for a transition, reducing the number of packets sent.
|
||||
*
|
||||
* This property is read-only for users. Any changes to it will be ignored.
|
||||
*/
|
||||
LightColorValues remote_values;
|
||||
|
||||
/// Publish the currently active state to the frontend.
|
||||
void publish_state();
|
||||
|
||||
/// Get the light output associated with this object.
|
||||
LightOutput *get_output() const;
|
||||
|
||||
/// Return the name of the current effect, or if no effect is active "None".
|
||||
std::string get_effect_name();
|
||||
|
||||
/** This lets front-end components subscribe to light change events.
|
||||
*
|
||||
* This is different from add_new_current_values_callback in that it only sends events for start
|
||||
* and end values. For example, with transitions it will only send a single callback whereas
|
||||
* the callback passed in add_new_current_values_callback will be called every loop() cycle when
|
||||
* a transition is active
|
||||
*
|
||||
* Note the callback should get the output values through get_remote_values().
|
||||
*
|
||||
* @param send_callback The callback.
|
||||
*/
|
||||
void add_new_remote_values_callback(std::function<void()> &&send_callback);
|
||||
|
||||
/// Return whether the light has any effects that meet the trait requirements.
|
||||
bool supports_effects();
|
||||
|
||||
#ifdef USE_JSON
|
||||
/// Dump the state of this light as JSON.
|
||||
void dump_json(JsonObject &root);
|
||||
#endif
|
||||
|
||||
/// Set the default transition length, i.e. the transition length when no transition is provided.
|
||||
void set_default_transition_length(uint32_t default_transition_length);
|
||||
|
||||
/// Set the gamma correction factor
|
||||
void set_gamma_correct(float gamma_correct);
|
||||
float get_gamma_correct() const { return this->gamma_correct_; }
|
||||
|
||||
const std::vector<LightEffect *> &get_effects() const;
|
||||
|
||||
void add_effects(std::vector<LightEffect *> effects);
|
||||
|
||||
void current_values_as_binary(bool *binary);
|
||||
|
||||
void current_values_as_brightness(float *brightness);
|
||||
|
||||
void current_values_as_rgb(float *red, float *green, float *blue);
|
||||
|
||||
void current_values_as_rgbw(float *red, float *green, float *blue, float *white);
|
||||
|
||||
void current_values_as_rgbww(float *red, float *green, float *blue, float *cold_white, float *warm_white);
|
||||
|
||||
void current_values_as_cwww(float *cold_white, float *warm_white);
|
||||
|
||||
protected:
|
||||
friend LightOutput;
|
||||
friend LightCall;
|
||||
|
||||
uint32_t hash_base() override;
|
||||
|
||||
/// Internal method to start an effect with the given index
|
||||
void start_effect_(uint32_t effect_index);
|
||||
/// Internal method to stop the current effect (if one is active).
|
||||
void stop_effect_();
|
||||
/// Internal method to start a transition to the target color with the given length.
|
||||
void start_transition_(const LightColorValues &target, uint32_t length);
|
||||
|
||||
/// Internal method to start a flash for the specified amount of time.
|
||||
void start_flash_(const LightColorValues &target, uint32_t length);
|
||||
|
||||
/// Internal method to set the color values to target immediately (with no transition).
|
||||
void set_immediately_(const LightColorValues &target);
|
||||
|
||||
/// Internal method to start a transformer.
|
||||
void set_transformer_(std::unique_ptr<LightTransformer> transformer);
|
||||
|
||||
LightEffect *get_active_effect_();
|
||||
|
||||
/// Object used to store the persisted values of the light.
|
||||
ESPPreferenceObject rtc_;
|
||||
/// Default transition length for all transitions in ms.
|
||||
uint32_t default_transition_length_{};
|
||||
/// Value for storing the index of the currently active effect. 0 if no effect is active
|
||||
uint32_t active_effect_index_{};
|
||||
/// The currently active transformer for this light (transition/flash).
|
||||
std::unique_ptr<LightTransformer> transformer_{nullptr};
|
||||
/** Callback to call when new values for the frontend are available.
|
||||
*
|
||||
* "Remote values" are light color values that are reported to the frontend and have a lower
|
||||
* publish frequency than the "real" color values. For example, during transitions the current
|
||||
* color value may change continuously, but the remote values will be reported as the target values
|
||||
* starting with the beginning of the transition.
|
||||
*/
|
||||
CallbackManager<void()> remote_values_callback_{};
|
||||
LightOutput *output_; ///< Store the output to allow effects to have more access.
|
||||
/// Whether the light value should be written in the next cycle.
|
||||
bool next_write_{true};
|
||||
/// Gamma correction factor for the light.
|
||||
float gamma_correct_{};
|
||||
/// List of effects for this light.
|
||||
std::vector<LightEffect *> effects_;
|
||||
};
|
||||
|
||||
} // namespace light
|
||||
} // namespace esphome
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
namespace esphome {
|
||||
namespace light {
|
||||
|
||||
/// This class is used to represent the capabilities of a light.
|
||||
class LightTraits {
|
||||
public:
|
||||
LightTraits() = default;
|
||||
|
||||
bool get_supports_brightness() const { return this->supports_brightness_; }
|
||||
void set_supports_brightness(bool supports_brightness) { this->supports_brightness_ = supports_brightness; }
|
||||
bool get_supports_rgb() const { return this->supports_rgb_; }
|
||||
void set_supports_rgb(bool supports_rgb) { this->supports_rgb_ = supports_rgb; }
|
||||
bool get_supports_rgb_white_value() const { return this->supports_rgb_white_value_; }
|
||||
void set_supports_rgb_white_value(bool supports_rgb_white_value) {
|
||||
this->supports_rgb_white_value_ = supports_rgb_white_value;
|
||||
}
|
||||
bool get_supports_color_temperature() const { return this->supports_color_temperature_; }
|
||||
void set_supports_color_temperature(bool supports_color_temperature) {
|
||||
this->supports_color_temperature_ = supports_color_temperature;
|
||||
}
|
||||
float get_min_mireds() const { return this->min_mireds_; }
|
||||
void set_min_mireds(float min_mireds) { this->min_mireds_ = min_mireds; }
|
||||
float get_max_mireds() const { return this->max_mireds_; }
|
||||
void set_max_mireds(float max_mireds) { this->max_mireds_ = max_mireds; }
|
||||
|
||||
protected:
|
||||
bool supports_brightness_{false};
|
||||
bool supports_rgb_{false};
|
||||
bool supports_rgb_white_value_{false};
|
||||
bool supports_color_temperature_{false};
|
||||
float min_mireds_{0};
|
||||
float max_mireds_{0};
|
||||
};
|
||||
|
||||
} // namespace light
|
||||
} // namespace esphome
|
||||
@@ -0,0 +1,86 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "light_color_values.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace light {
|
||||
|
||||
/// Base-class for all light color transformers, such as transitions or flashes.
|
||||
class LightTransformer {
|
||||
public:
|
||||
LightTransformer(uint32_t start_time, uint32_t length, const LightColorValues &start_values,
|
||||
const LightColorValues &target_values)
|
||||
: start_time_(start_time), length_(length), start_values_(start_values), target_values_(target_values) {}
|
||||
|
||||
LightTransformer() = delete;
|
||||
|
||||
/// Whether this transformation is finished
|
||||
virtual bool is_finished() { return this->get_progress_() >= 1.0f; }
|
||||
|
||||
/// This will be called to get the current values for output.
|
||||
virtual LightColorValues get_values() = 0;
|
||||
|
||||
/// The values that should be reported to the front-end.
|
||||
virtual LightColorValues get_remote_values() { return this->get_target_values_(); }
|
||||
|
||||
/// The values that should be set after this transformation is complete.
|
||||
virtual LightColorValues get_end_values() { return this->get_target_values_(); }
|
||||
|
||||
virtual bool publish_at_end() = 0;
|
||||
|
||||
protected:
|
||||
/// Get the completion of this transformer, 0 to 1.
|
||||
float get_progress_() { return clamp((millis() - this->start_time_) / float(this->length_), 0.0f, 1.0f); }
|
||||
|
||||
const LightColorValues &get_start_values_() const { return this->start_values_; }
|
||||
|
||||
const LightColorValues &get_target_values_() const { return this->target_values_; }
|
||||
|
||||
uint32_t start_time_;
|
||||
uint32_t length_;
|
||||
LightColorValues start_values_;
|
||||
LightColorValues target_values_;
|
||||
};
|
||||
|
||||
class LightTransitionTransformer : public LightTransformer {
|
||||
public:
|
||||
LightTransitionTransformer(uint32_t start_time, uint32_t length, const LightColorValues &start_values,
|
||||
const LightColorValues &target_values)
|
||||
: LightTransformer(start_time, length, start_values, target_values) {
|
||||
// When turning light on from off state, use colors from new.
|
||||
if (!this->start_values_.is_on() && this->target_values_.is_on()) {
|
||||
this->start_values_.set_brightness(0.0f);
|
||||
this->start_values_.set_red(target_values.get_red());
|
||||
this->start_values_.set_green(target_values.get_green());
|
||||
this->start_values_.set_blue(target_values.get_blue());
|
||||
this->start_values_.set_white(target_values.get_white());
|
||||
this->start_values_.set_color_temperature(target_values.get_color_temperature());
|
||||
}
|
||||
}
|
||||
|
||||
LightColorValues get_values() override {
|
||||
float x = this->get_progress_();
|
||||
float v = x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f);
|
||||
return LightColorValues::lerp(this->get_start_values_(), this->get_target_values_(), v);
|
||||
}
|
||||
|
||||
bool publish_at_end() override { return false; }
|
||||
};
|
||||
|
||||
class LightFlashTransformer : public LightTransformer {
|
||||
public:
|
||||
LightFlashTransformer(uint32_t start_time, uint32_t length, const LightColorValues &start_values,
|
||||
const LightColorValues &target_values)
|
||||
: LightTransformer(start_time, length, start_values, target_values) {}
|
||||
|
||||
LightColorValues get_values() override { return this->get_target_values_(); }
|
||||
|
||||
LightColorValues get_end_values() override { return this->get_start_values_(); }
|
||||
|
||||
bool publish_at_end() override { return true; }
|
||||
};
|
||||
|
||||
} // namespace light
|
||||
} // namespace esphome
|
||||
@@ -1,21 +0,0 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import light, output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_MAKE_ID, CONF_NAME, CONF_OUTPUT
|
||||
from esphome.cpp_generator import get_variable, variable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(light.PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(light.MakeLight),
|
||||
vol.Required(CONF_OUTPUT): cv.use_variable_id(output.FloatOutput),
|
||||
}).extend(light.BRIGHTNESS_ONLY_LIGHT_SCHEMA.schema).extend(cv.COMPONENT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
output_ = yield get_variable(config[CONF_OUTPUT])
|
||||
rhs = App.make_monochromatic_light(config[CONF_NAME], output_)
|
||||
light_struct = variable(config[CONF_MAKE_ID], rhs)
|
||||
light.setup_light(light_struct.Pstate, light_struct.Poutput, config)
|
||||
setup_component(light_struct.Pstate, config)
|
||||
@@ -1,181 +0,0 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import light
|
||||
from esphome.components.light import AddressableLight
|
||||
from esphome.components.power_supply import PowerSupplyComponent
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CLOCK_PIN, CONF_DATA_PIN, CONF_MAKE_ID, CONF_METHOD, CONF_NAME, \
|
||||
CONF_NUM_LEDS, CONF_PIN, CONF_POWER_SUPPLY, CONF_TYPE, CONF_VARIANT
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_generator import TemplateArguments, add, get_variable, variable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, Application, Component, global_ns
|
||||
|
||||
NeoPixelBusLightOutputBase = light.light_ns.class_('NeoPixelBusLightOutputBase', Component,
|
||||
AddressableLight)
|
||||
ESPNeoPixelOrder = light.light_ns.namespace('ESPNeoPixelOrder')
|
||||
|
||||
|
||||
def validate_type(value):
|
||||
value = cv.string(value).upper()
|
||||
if 'R' not in value:
|
||||
raise vol.Invalid("Must have R in type")
|
||||
if 'G' not in value:
|
||||
raise vol.Invalid("Must have G in type")
|
||||
if 'B' not in value:
|
||||
raise vol.Invalid("Must have B in type")
|
||||
rest = set(value) - set('RGBW')
|
||||
if rest:
|
||||
raise vol.Invalid("Type has invalid color: {}".format(', '.join(rest)))
|
||||
if len(set(value)) != len(value):
|
||||
raise vol.Invalid("Type has duplicate color!")
|
||||
return value
|
||||
|
||||
|
||||
def validate_variant(value):
|
||||
value = cv.string(value).upper()
|
||||
if value == 'WS2813':
|
||||
value = 'WS2812X'
|
||||
if value == 'WS2812':
|
||||
value = '800KBPS'
|
||||
if value == 'LC8812':
|
||||
value = 'SK6812'
|
||||
return cv.one_of(*VARIANTS)(value)
|
||||
|
||||
|
||||
def validate_method(value):
|
||||
if value is None:
|
||||
if CORE.is_esp32:
|
||||
return 'ESP32_I2S_1'
|
||||
if CORE.is_esp8266:
|
||||
return 'ESP8266_DMA'
|
||||
raise NotImplementedError
|
||||
|
||||
if CORE.is_esp32:
|
||||
return cv.one_of(*ESP32_METHODS, upper=True, space='_')(value)
|
||||
if CORE.is_esp8266:
|
||||
return cv.one_of(*ESP8266_METHODS, upper=True, space='_')(value)
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def validate_method_pin(value):
|
||||
method = value[CONF_METHOD]
|
||||
method_pins = {
|
||||
'ESP8266_DMA': [3],
|
||||
'ESP8266_UART0': [1],
|
||||
'ESP8266_ASYNC_UART0': [1],
|
||||
'ESP8266_UART1': [2],
|
||||
'ESP8266_ASYNC_UART1': [2],
|
||||
'ESP32_I2S_0': list(range(0, 32)),
|
||||
'ESP32_I2S_1': list(range(0, 32)),
|
||||
}
|
||||
if CORE.is_esp8266:
|
||||
method_pins['BIT_BANG'] = list(range(0, 16))
|
||||
elif CORE.is_esp32:
|
||||
method_pins['BIT_BANG'] = list(range(0, 32))
|
||||
pins_ = method_pins[method]
|
||||
for opt in (CONF_PIN, CONF_CLOCK_PIN, CONF_DATA_PIN):
|
||||
if opt in value and value[opt] not in pins_:
|
||||
raise vol.Invalid("Method {} only supports pin(s) {}".format(
|
||||
method, ', '.join('GPIO{}'.format(x) for x in pins_)
|
||||
), path=[CONF_METHOD])
|
||||
return value
|
||||
|
||||
|
||||
VARIANTS = {
|
||||
'WS2812X': 'Ws2812x',
|
||||
'SK6812': 'Sk6812',
|
||||
'800KBPS': '800Kbps',
|
||||
'400KBPS': '400Kbps',
|
||||
}
|
||||
|
||||
ESP8266_METHODS = {
|
||||
'ESP8266_DMA': 'NeoEsp8266Dma{}Method',
|
||||
'ESP8266_UART0': 'NeoEsp8266Uart0{}Method',
|
||||
'ESP8266_UART1': 'NeoEsp8266Uart1{}Method',
|
||||
'ESP8266_ASYNC_UART0': 'NeoEsp8266AsyncUart0{}Method',
|
||||
'ESP8266_ASYNC_UART1': 'NeoEsp8266AsyncUart1{}Method',
|
||||
'BIT_BANG': 'NeoEsp8266BitBang{}Method',
|
||||
}
|
||||
ESP32_METHODS = {
|
||||
'ESP32_I2S_0': 'NeoEsp32I2s0{}Method',
|
||||
'ESP32_I2S_1': 'NeoEsp32I2s1{}Method',
|
||||
'BIT_BANG': 'NeoEsp32BitBang{}Method',
|
||||
}
|
||||
|
||||
|
||||
def format_method(config):
|
||||
variant = VARIANTS[config[CONF_VARIANT]]
|
||||
method = config[CONF_METHOD]
|
||||
if CORE.is_esp8266:
|
||||
return ESP8266_METHODS[method].format(variant)
|
||||
if CORE.is_esp32:
|
||||
return ESP32_METHODS[method].format(variant)
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def validate(config):
|
||||
if CONF_PIN in config:
|
||||
if CONF_CLOCK_PIN in config or CONF_DATA_PIN in config:
|
||||
raise vol.Invalid("Cannot specify both 'pin' and 'clock_pin'+'data_pin'")
|
||||
return config
|
||||
if CONF_CLOCK_PIN in config:
|
||||
if CONF_DATA_PIN not in config:
|
||||
raise vol.Invalid("If you give clock_pin, you must also specify data_pin")
|
||||
return config
|
||||
raise vol.Invalid("Must specify at least one of 'pin' or 'clock_pin'+'data_pin'")
|
||||
|
||||
|
||||
MakeNeoPixelBusLight = Application.struct('MakeNeoPixelBusLight')
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(light.PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(MakeNeoPixelBusLight),
|
||||
|
||||
vol.Optional(CONF_TYPE, default='GRB'): validate_type,
|
||||
vol.Optional(CONF_VARIANT, default='800KBPS'): validate_variant,
|
||||
vol.Optional(CONF_METHOD, default=None): validate_method,
|
||||
vol.Optional(CONF_PIN): pins.output_pin,
|
||||
vol.Optional(CONF_CLOCK_PIN): pins.output_pin,
|
||||
vol.Optional(CONF_DATA_PIN): pins.output_pin,
|
||||
|
||||
vol.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
|
||||
|
||||
vol.Optional(CONF_POWER_SUPPLY): cv.use_variable_id(PowerSupplyComponent),
|
||||
}).extend(light.ADDRESSABLE_LIGHT_SCHEMA.schema).extend(cv.COMPONENT_SCHEMA.schema),
|
||||
validate, validate_method_pin)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
type_ = config[CONF_TYPE]
|
||||
has_white = 'W' in type_
|
||||
if has_white:
|
||||
func = App.make_neo_pixel_bus_rgbw_light
|
||||
color_feat = global_ns.NeoRgbwFeature
|
||||
else:
|
||||
func = App.make_neo_pixel_bus_rgb_light
|
||||
color_feat = global_ns.NeoRgbFeature
|
||||
|
||||
template = TemplateArguments(getattr(global_ns, format_method(config)), color_feat)
|
||||
rhs = func(template, config[CONF_NAME])
|
||||
make = variable(config[CONF_MAKE_ID], rhs, type=MakeNeoPixelBusLight.template(template))
|
||||
output = make.Poutput
|
||||
|
||||
if CONF_PIN in config:
|
||||
add(output.add_leds(config[CONF_NUM_LEDS], config[CONF_PIN]))
|
||||
else:
|
||||
add(output.add_leds(config[CONF_NUM_LEDS], config[CONF_CLOCK_PIN], config[CONF_DATA_PIN]))
|
||||
|
||||
add(output.set_pixel_order(getattr(ESPNeoPixelOrder, type_)))
|
||||
|
||||
if CONF_POWER_SUPPLY in config:
|
||||
power_supply = yield get_variable(config[CONF_POWER_SUPPLY])
|
||||
add(output.set_power_supply(power_supply))
|
||||
|
||||
light.setup_light(make.Pstate, output, config)
|
||||
setup_component(output, config)
|
||||
|
||||
|
||||
REQUIRED_BUILD_FLAGS = '-DUSE_NEO_PIXEL_BUS_LIGHT'
|
||||
|
||||
LIB_DEPS = 'NeoPixelBus@2.4.1'
|
||||
@@ -1,43 +0,0 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import light
|
||||
from esphome.components.light import AddressableLight
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_FROM, CONF_ID, CONF_MAKE_ID, CONF_NAME, CONF_SEGMENTS, CONF_TO
|
||||
from esphome.cpp_generator import get_variable, variable
|
||||
from esphome.cpp_types import App, Application
|
||||
|
||||
AddressableSegment = light.light_ns.class_('AddressableSegment')
|
||||
PartitionLightOutput = light.light_ns.class_('PartitionLightOutput', AddressableLight)
|
||||
MakePartitionLight = Application.struct('MakePartitionLight')
|
||||
|
||||
|
||||
def validate_from_to(value):
|
||||
if value[CONF_FROM] > value[CONF_TO]:
|
||||
raise vol.Invalid(u"From ({}) must not be larger than to ({})"
|
||||
u"".format(value[CONF_FROM], value[CONF_TO]))
|
||||
return value
|
||||
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(light.PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(light.AddressableLightState),
|
||||
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(MakePartitionLight),
|
||||
|
||||
vol.Required(CONF_SEGMENTS): vol.All(cv.ensure_list({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(light.AddressableLightState),
|
||||
vol.Required(CONF_FROM): cv.positive_int,
|
||||
vol.Required(CONF_TO): cv.positive_int,
|
||||
}, validate_from_to), vol.Length(min=1)),
|
||||
}).extend(light.ADDRESSABLE_LIGHT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
segments = []
|
||||
for conf in config[CONF_SEGMENTS]:
|
||||
var = yield get_variable(conf[CONF_ID])
|
||||
segments.append(AddressableSegment(var, conf[CONF_FROM],
|
||||
conf[CONF_TO] - conf[CONF_FROM] + 1))
|
||||
|
||||
rhs = App.make_partition_light(config[CONF_NAME], segments)
|
||||
make = variable(config[CONF_MAKE_ID], rhs)
|
||||
light.setup_light(make.Pstate, make.Ppartition, config)
|
||||
@@ -1,25 +0,0 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import light, output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_BLUE, CONF_GREEN, CONF_MAKE_ID, CONF_NAME, CONF_RED
|
||||
from esphome.cpp_generator import get_variable, variable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(light.PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(light.MakeLight),
|
||||
vol.Required(CONF_RED): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Required(CONF_GREEN): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Required(CONF_BLUE): cv.use_variable_id(output.FloatOutput),
|
||||
}).extend(light.RGB_LIGHT_SCHEMA.schema).extend(cv.COMPONENT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
red = yield get_variable(config[CONF_RED])
|
||||
green = yield get_variable(config[CONF_GREEN])
|
||||
blue = yield get_variable(config[CONF_BLUE])
|
||||
rhs = App.make_rgb_light(config[CONF_NAME], red, green, blue)
|
||||
light_struct = variable(config[CONF_MAKE_ID], rhs)
|
||||
light.setup_light(light_struct.Pstate, light_struct.Poutput, config)
|
||||
setup_component(light_struct.Pstate, config)
|
||||
@@ -1,27 +0,0 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import light, output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_BLUE, CONF_GREEN, CONF_MAKE_ID, CONF_NAME, CONF_RED, CONF_WHITE
|
||||
from esphome.cpp_generator import get_variable, variable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(light.PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(light.MakeLight),
|
||||
vol.Required(CONF_RED): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Required(CONF_GREEN): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Required(CONF_BLUE): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Required(CONF_WHITE): cv.use_variable_id(output.FloatOutput),
|
||||
}).extend(light.RGB_LIGHT_SCHEMA.schema).extend(cv.COMPONENT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
red = yield get_variable(config[CONF_RED])
|
||||
green = yield get_variable(config[CONF_GREEN])
|
||||
blue = yield get_variable(config[CONF_BLUE])
|
||||
white = yield get_variable(config[CONF_WHITE])
|
||||
rhs = App.make_rgbw_light(config[CONF_NAME], red, green, blue, white)
|
||||
light_struct = variable(config[CONF_MAKE_ID], rhs)
|
||||
light.setup_light(light_struct.Pstate, light_struct.Poutput, config)
|
||||
setup_component(light_struct.Pstate, config)
|
||||
@@ -1,47 +0,0 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import light, output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_BLUE, CONF_COLD_WHITE, CONF_COLD_WHITE_COLOR_TEMPERATURE, \
|
||||
CONF_GREEN, CONF_MAKE_ID, CONF_NAME, CONF_RED, CONF_WARM_WHITE, \
|
||||
CONF_WARM_WHITE_COLOR_TEMPERATURE
|
||||
from esphome.cpp_generator import get_variable, variable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App
|
||||
|
||||
|
||||
def validate_cold_white_colder(value):
|
||||
cw = value[CONF_COLD_WHITE_COLOR_TEMPERATURE]
|
||||
ww = value[CONF_WARM_WHITE_COLOR_TEMPERATURE]
|
||||
if cw > ww:
|
||||
raise vol.Invalid("Cold white color temperature cannot be higher than warm white")
|
||||
if cw == ww:
|
||||
raise vol.Invalid("Cold white color temperature cannot be the same as warm white")
|
||||
return value
|
||||
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(light.PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(light.MakeLight),
|
||||
vol.Required(CONF_RED): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Required(CONF_GREEN): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Required(CONF_BLUE): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Required(CONF_COLD_WHITE): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Required(CONF_WARM_WHITE): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature,
|
||||
vol.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature,
|
||||
}).extend(light.RGB_LIGHT_SCHEMA.schema).extend(cv.COMPONENT_SCHEMA.schema),
|
||||
validate_cold_white_colder)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
red = yield get_variable(config[CONF_RED])
|
||||
green = yield get_variable(config[CONF_GREEN])
|
||||
blue = yield get_variable(config[CONF_BLUE])
|
||||
cold_white = yield get_variable(config[CONF_COLD_WHITE])
|
||||
warm_white = yield get_variable(config[CONF_WARM_WHITE])
|
||||
rhs = App.make_rgbww_light(config[CONF_NAME], config[CONF_COLD_WHITE_COLOR_TEMPERATURE],
|
||||
config[CONF_WARM_WHITE_COLOR_TEMPERATURE],
|
||||
red, green, blue, cold_white, warm_white)
|
||||
light_struct = variable(config[CONF_MAKE_ID], rhs)
|
||||
light.setup_light(light_struct.Pstate, light_struct.Poutput, config)
|
||||
setup_component(light_struct.Pstate, config)
|
||||
Reference in New Issue
Block a user