🏗 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:
Otto Winter
2019-04-17 12:06:00 +02:00
committed by GitHub
parent 049807e3ab
commit 6682c43dfa
817 changed files with 54156 additions and 10830 deletions
+292 -366
View File
@@ -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 &current_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 &current_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 &current_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 &current_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 &current_color) override {
for (int i = 0; i < addressable.size(); i++) {
if (i == this->at_led_)
addressable[i] = current_color;
else
addressable[i] = ESPColor(0, 0, 0, 0);
}
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 &current_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 &current_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 &current_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 &current_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
+79
View File
@@ -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
-21
View File
@@ -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)
-30
View File
@@ -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'
-80
View File
@@ -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
+43
View File
@@ -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
+24
View File
@@ -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
+703
View File
@@ -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
+319
View File
@@ -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
+38
View File
@@ -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
-21
View File
@@ -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)
-181
View File
@@ -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'
-43
View File
@@ -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)
-25
View File
@@ -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)
-27
View File
@@ -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)
-47
View File
@@ -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)