mirror of
https://github.com/esphome/esphome.git
synced 2025-09-05 04:42:21 +01:00
289 lines
9.5 KiB
Python
289 lines
9.5 KiB
Python
import enum
|
|
|
|
import esphome.automation as auto
|
|
import esphome.codegen as cg
|
|
from esphome.components import mqtt, power_supply, web_server
|
|
import esphome.config_validation as cv
|
|
from esphome.const import (
|
|
CONF_BLUE,
|
|
CONF_BRIGHTNESS,
|
|
CONF_COLD_WHITE,
|
|
CONF_COLD_WHITE_COLOR_TEMPERATURE,
|
|
CONF_COLOR_BRIGHTNESS,
|
|
CONF_COLOR_CORRECT,
|
|
CONF_COLOR_MODE,
|
|
CONF_COLOR_TEMPERATURE,
|
|
CONF_DEFAULT_TRANSITION_LENGTH,
|
|
CONF_EFFECTS,
|
|
CONF_ENTITY_CATEGORY,
|
|
CONF_FLASH_TRANSITION_LENGTH,
|
|
CONF_GAMMA_CORRECT,
|
|
CONF_GREEN,
|
|
CONF_ICON,
|
|
CONF_ID,
|
|
CONF_INITIAL_STATE,
|
|
CONF_MQTT_ID,
|
|
CONF_ON_STATE,
|
|
CONF_ON_TURN_OFF,
|
|
CONF_ON_TURN_ON,
|
|
CONF_OUTPUT_ID,
|
|
CONF_POWER_SUPPLY,
|
|
CONF_RED,
|
|
CONF_RESTORE_MODE,
|
|
CONF_STATE,
|
|
CONF_TRIGGER_ID,
|
|
CONF_WARM_WHITE,
|
|
CONF_WARM_WHITE_COLOR_TEMPERATURE,
|
|
CONF_WEB_SERVER,
|
|
CONF_WHITE,
|
|
)
|
|
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
|
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
|
from esphome.cpp_generator import MockObjClass
|
|
|
|
from .automation import LIGHT_STATE_SCHEMA
|
|
from .effects import (
|
|
ADDRESSABLE_EFFECTS,
|
|
BINARY_EFFECTS,
|
|
EFFECTS_REGISTRY,
|
|
MONOCHROMATIC_EFFECTS,
|
|
RGB_EFFECTS,
|
|
validate_effects,
|
|
)
|
|
from .types import ( # noqa
|
|
AddressableLight,
|
|
AddressableLightState,
|
|
ColorMode,
|
|
LightOutput,
|
|
LightState,
|
|
LightStateRTCState,
|
|
LightStateTrigger,
|
|
LightTurnOffTrigger,
|
|
LightTurnOnTrigger,
|
|
light_ns,
|
|
)
|
|
|
|
CODEOWNERS = ["@esphome/core"]
|
|
IS_PLATFORM_COMPONENT = True
|
|
|
|
LightRestoreMode = light_ns.enum("LightRestoreMode")
|
|
RESTORE_MODES = {
|
|
"RESTORE_DEFAULT_OFF": LightRestoreMode.LIGHT_RESTORE_DEFAULT_OFF,
|
|
"RESTORE_DEFAULT_ON": LightRestoreMode.LIGHT_RESTORE_DEFAULT_ON,
|
|
"ALWAYS_OFF": LightRestoreMode.LIGHT_ALWAYS_OFF,
|
|
"ALWAYS_ON": LightRestoreMode.LIGHT_ALWAYS_ON,
|
|
"RESTORE_INVERTED_DEFAULT_OFF": LightRestoreMode.LIGHT_RESTORE_INVERTED_DEFAULT_OFF,
|
|
"RESTORE_INVERTED_DEFAULT_ON": LightRestoreMode.LIGHT_RESTORE_INVERTED_DEFAULT_ON,
|
|
"RESTORE_AND_OFF": LightRestoreMode.LIGHT_RESTORE_AND_OFF,
|
|
"RESTORE_AND_ON": LightRestoreMode.LIGHT_RESTORE_AND_ON,
|
|
}
|
|
|
|
LIGHT_SCHEMA = (
|
|
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
|
|
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
|
|
.extend(
|
|
{
|
|
cv.GenerateID(): cv.declare_id(LightState),
|
|
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
|
mqtt.MQTTJSONLightComponent
|
|
),
|
|
cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum(
|
|
RESTORE_MODES, upper=True, space="_"
|
|
),
|
|
cv.Optional(CONF_ON_TURN_ON): auto.validate_automation(
|
|
{
|
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightTurnOnTrigger),
|
|
}
|
|
),
|
|
cv.Optional(CONF_ON_TURN_OFF): auto.validate_automation(
|
|
{
|
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightTurnOffTrigger),
|
|
}
|
|
),
|
|
cv.Optional(CONF_ON_STATE): auto.validate_automation(
|
|
{
|
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightStateTrigger),
|
|
}
|
|
),
|
|
cv.Optional(CONF_INITIAL_STATE): LIGHT_STATE_SCHEMA,
|
|
}
|
|
)
|
|
)
|
|
|
|
LIGHT_SCHEMA.add_extra(entity_duplicate_validator("light"))
|
|
|
|
BINARY_LIGHT_SCHEMA = LIGHT_SCHEMA.extend(
|
|
{
|
|
cv.Optional(CONF_EFFECTS): validate_effects(BINARY_EFFECTS),
|
|
}
|
|
)
|
|
|
|
BRIGHTNESS_ONLY_LIGHT_SCHEMA = LIGHT_SCHEMA.extend(
|
|
{
|
|
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_FLASH_TRANSITION_LENGTH, default="0s"
|
|
): cv.positive_time_period_milliseconds,
|
|
cv.Optional(CONF_EFFECTS): validate_effects(MONOCHROMATIC_EFFECTS),
|
|
}
|
|
)
|
|
|
|
RGB_LIGHT_SCHEMA = BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend(
|
|
{
|
|
cv.Optional(CONF_EFFECTS): validate_effects(RGB_EFFECTS),
|
|
}
|
|
)
|
|
|
|
ADDRESSABLE_LIGHT_SCHEMA = RGB_LIGHT_SCHEMA.extend(
|
|
{
|
|
cv.GenerateID(): cv.declare_id(AddressableLightState),
|
|
cv.Optional(CONF_EFFECTS): validate_effects(ADDRESSABLE_EFFECTS),
|
|
cv.Optional(CONF_COLOR_CORRECT): cv.All(
|
|
[cv.percentage], cv.Length(min=3, max=4)
|
|
),
|
|
cv.Optional(CONF_POWER_SUPPLY): cv.use_id(power_supply.PowerSupply),
|
|
}
|
|
)
|
|
|
|
|
|
class LightType(enum.IntEnum):
|
|
"""Light type enum."""
|
|
|
|
BINARY = 0
|
|
BRIGHTNESS_ONLY = 1
|
|
RGB = 2
|
|
ADDRESSABLE = 3
|
|
|
|
|
|
def light_schema(
|
|
class_: MockObjClass,
|
|
type_: LightType,
|
|
*,
|
|
entity_category: str = cv.UNDEFINED,
|
|
icon: str = cv.UNDEFINED,
|
|
default_restore_mode: str = cv.UNDEFINED,
|
|
) -> cv.Schema:
|
|
schema = {
|
|
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(class_),
|
|
}
|
|
|
|
for key, default, validator in [
|
|
(CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
|
|
(CONF_ICON, icon, cv.icon),
|
|
(
|
|
CONF_RESTORE_MODE,
|
|
default_restore_mode,
|
|
cv.enum(RESTORE_MODES, upper=True, space="_"),
|
|
),
|
|
]:
|
|
if default is not cv.UNDEFINED:
|
|
schema[cv.Optional(key, default=default)] = validator
|
|
|
|
if type_ == LightType.BINARY:
|
|
return BINARY_LIGHT_SCHEMA.extend(schema)
|
|
if type_ == LightType.BRIGHTNESS_ONLY:
|
|
return BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend(schema)
|
|
if type_ == LightType.RGB:
|
|
return RGB_LIGHT_SCHEMA.extend(schema)
|
|
if type_ == LightType.ADDRESSABLE:
|
|
return ADDRESSABLE_LIGHT_SCHEMA.extend(schema)
|
|
|
|
raise ValueError(f"Invalid light type: {type_}")
|
|
|
|
|
|
def validate_color_temperature_channels(value):
|
|
if (
|
|
CONF_COLD_WHITE_COLOR_TEMPERATURE in value
|
|
and CONF_WARM_WHITE_COLOR_TEMPERATURE in value
|
|
and value[CONF_COLD_WHITE_COLOR_TEMPERATURE]
|
|
>= value[CONF_WARM_WHITE_COLOR_TEMPERATURE]
|
|
):
|
|
raise cv.Invalid(
|
|
"Color temperature of the cold white channel must be colder than that of the warm white channel.",
|
|
path=[CONF_COLD_WHITE_COLOR_TEMPERATURE],
|
|
)
|
|
return value
|
|
|
|
|
|
async def setup_light_core_(light_var, output_var, config):
|
|
await setup_entity(light_var, config, "light")
|
|
|
|
cg.add(light_var.set_restore_mode(config[CONF_RESTORE_MODE]))
|
|
|
|
if (initial_state_config := config.get(CONF_INITIAL_STATE)) is not None:
|
|
initial_state = LightStateRTCState(
|
|
initial_state_config.get(CONF_COLOR_MODE, ColorMode.UNKNOWN),
|
|
initial_state_config.get(CONF_STATE, False),
|
|
initial_state_config.get(CONF_BRIGHTNESS, 1.0),
|
|
initial_state_config.get(CONF_COLOR_BRIGHTNESS, 1.0),
|
|
initial_state_config.get(CONF_RED, 1.0),
|
|
initial_state_config.get(CONF_GREEN, 1.0),
|
|
initial_state_config.get(CONF_BLUE, 1.0),
|
|
initial_state_config.get(CONF_WHITE, 1.0),
|
|
initial_state_config.get(CONF_COLOR_TEMPERATURE, 1.0),
|
|
initial_state_config.get(CONF_COLD_WHITE, 1.0),
|
|
initial_state_config.get(CONF_WARM_WHITE, 1.0),
|
|
)
|
|
cg.add(light_var.set_initial_state(initial_state))
|
|
|
|
if (
|
|
default_transition_length := config.get(CONF_DEFAULT_TRANSITION_LENGTH)
|
|
) is not None:
|
|
cg.add(light_var.set_default_transition_length(default_transition_length))
|
|
if (
|
|
flash_transition_length := config.get(CONF_FLASH_TRANSITION_LENGTH)
|
|
) is not None:
|
|
cg.add(light_var.set_flash_transition_length(flash_transition_length))
|
|
if (gamma_correct := config.get(CONF_GAMMA_CORRECT)) is not None:
|
|
cg.add(light_var.set_gamma_correct(gamma_correct))
|
|
effects = await cg.build_registry_list(
|
|
EFFECTS_REGISTRY, config.get(CONF_EFFECTS, [])
|
|
)
|
|
cg.add(light_var.add_effects(effects))
|
|
|
|
for conf in config.get(CONF_ON_TURN_ON, []):
|
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], light_var)
|
|
await auto.build_automation(trigger, [], conf)
|
|
for conf in config.get(CONF_ON_TURN_OFF, []):
|
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], light_var)
|
|
await auto.build_automation(trigger, [], conf)
|
|
for conf in config.get(CONF_ON_STATE, []):
|
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], light_var)
|
|
await auto.build_automation(trigger, [], conf)
|
|
|
|
if (color_correct := config.get(CONF_COLOR_CORRECT)) is not None:
|
|
cg.add(output_var.set_correction(*color_correct))
|
|
|
|
if (power_supply_id := config.get(CONF_POWER_SUPPLY)) is not None:
|
|
var_ = await cg.get_variable(power_supply_id)
|
|
cg.add(output_var.set_power_supply(var_))
|
|
|
|
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
|
|
mqtt_ = cg.new_Pvariable(mqtt_id, light_var)
|
|
await mqtt.register_mqtt_component(mqtt_, config)
|
|
|
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
|
await web_server.add_entity_config(light_var, web_server_config)
|
|
|
|
|
|
async def register_light(output_var, config):
|
|
light_var = cg.new_Pvariable(config[CONF_ID], output_var)
|
|
cg.add(cg.App.register_light(light_var))
|
|
CORE.register_platform_component("light", light_var)
|
|
await cg.register_component(light_var, config)
|
|
await setup_light_core_(light_var, output_var, config)
|
|
|
|
|
|
async def new_light(config, *args):
|
|
output_var = cg.new_Pvariable(config[CONF_OUTPUT_ID], *args)
|
|
await register_light(output_var, config)
|
|
return output_var
|
|
|
|
|
|
@coroutine_with_priority(CoroPriority.CORE)
|
|
async def to_code(config):
|
|
cg.add_global(light_ns.using)
|