From 7fd5634a51a4e6e90fbf7d8d2e3b5184b59e6d65 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 8 Apr 2019 18:08:58 +0200 Subject: [PATCH] Add cover position/tilt support (#496) * Add climate support * Fixes * Updates * Updates * Add ota * Remove climate changes * Lint --- esphome/components/cover/__init__.py | 81 +++++++++++++++-- esphome/components/cover/endstop.py | 57 ++++++++++++ esphome/components/cover/template.py | 47 +++++++--- esphome/components/cover/time_based.py | 44 ++++++++++ esphome/components/light/__init__.py | 87 +++++++++++++------ esphome/components/light/binary.py | 9 +- esphome/components/light/cwww.py | 21 ++--- esphome/components/light/fastled_clockless.py | 18 +--- esphome/components/light/fastled_spi.py | 21 ++--- esphome/components/light/monochromatic.py | 12 +-- esphome/components/light/neopixelbus.py | 20 ++--- esphome/components/light/partition.py | 13 +-- esphome/components/light/rgb.py | 12 +-- esphome/components/light/rgbw.py | 12 +-- esphome/components/light/rgbww.py | 29 ++----- esphome/components/ota.py | 1 - esphome/components/sensor/__init__.py | 7 +- esphome/config_validation.py | 66 ++++++++++++-- esphome/const.py | 9 ++ esphome/cpp_generator.py | 41 ++++++--- tests/test1.yaml | 10 +-- 21 files changed, 425 insertions(+), 192 deletions(-) create mode 100644 esphome/components/cover/endstop.py create mode 100644 esphome/components/cover/time_based.py diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index 9a3c153a03..a2590564b9 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -1,41 +1,58 @@ import voluptuous as vol -from esphome.automation import ACTION_REGISTRY, maybe_simple_id +from esphome.automation import ACTION_REGISTRY, maybe_simple_id, Condition from esphome.components import mqtt from esphome.components.mqtt import setup_mqtt_component import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_INTERNAL, CONF_MQTT_ID +from esphome.const import CONF_ID, CONF_INTERNAL, CONF_MQTT_ID, CONF_DEVICE_CLASS, CONF_STATE, \ + CONF_POSITION, CONF_TILT, CONF_STOP from esphome.core import CORE -from esphome.cpp_generator import Pvariable, add, get_variable -from esphome.cpp_types import Action, Nameable, esphome_ns +from esphome.cpp_generator import Pvariable, add, get_variable, templatable +from esphome.cpp_types import Action, Nameable, esphome_ns, App PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ }) +DEVICE_CLASSES = [ + '', 'awning', 'blind', 'curtain', 'damper', 'door', 'garage', + 'shade', 'shutter', 'window' +] + cover_ns = esphome_ns.namespace('cover') Cover = cover_ns.class_('Cover', Nameable) MQTTCoverComponent = cover_ns.class_('MQTTCoverComponent', mqtt.MQTTComponent) -CoverState = cover_ns.class_('CoverState') COVER_OPEN = cover_ns.COVER_OPEN COVER_CLOSED = cover_ns.COVER_CLOSED -validate_cover_state = cv.one_of('OPEN', 'CLOSED', upper=True) COVER_STATES = { 'OPEN': COVER_OPEN, 'CLOSED': COVER_CLOSED, } +validate_cover_state = cv.one_of(*COVER_STATES, upper=True) + +CoverOperation = cover_ns.enum('CoverOperation') +COVER_OPERATIONS = { + 'IDLE': CoverOperation.COVER_OPERATION_IDLE, + 'OPENING': CoverOperation.COVER_OPERATION_OPENING, + 'CLOSING': CoverOperation.COVER_OPERATION_CLOSING, +} +validate_cover_operation = cv.one_of(*COVER_OPERATIONS, upper=True) # Actions OpenAction = cover_ns.class_('OpenAction', Action) CloseAction = cover_ns.class_('CloseAction', Action) StopAction = cover_ns.class_('StopAction', Action) +ControlAction = cover_ns.class_('ControlAction', Action) +CoverIsOpenCondition = cover_ns.class_('CoverIsOpenCondition', Condition) +CoverIsClosedCondition = cover_ns.class_('CoverIsClosedCondition', Condition) COVER_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({ cv.GenerateID(): cv.declare_variable_id(Cover), cv.GenerateID(CONF_MQTT_ID): cv.declare_variable_id(MQTTCoverComponent), + vol.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True), }) COVER_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(COVER_SCHEMA.schema) @@ -44,6 +61,8 @@ COVER_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(COVER_SCHEMA.schema) def setup_cover_core_(cover_var, config): if CONF_INTERNAL in config: add(cover_var.set_internal(config[CONF_INTERNAL])) + if CONF_DEVICE_CLASS in config: + add(cover_var.set_device_class(config[CONF_DEVICE_CLASS])) setup_mqtt_component(cover_var.Pget_mqtt(), config) @@ -51,6 +70,13 @@ def setup_cover(cover_obj, config): CORE.add_job(setup_cover_core_, cover_obj, config) +def register_cover(var, config): + if not CORE.has_id(config[CONF_ID]): + var = Pvariable(config[CONF_ID], var, has_side_effects=True) + add(App.register_cover(var)) + CORE.add_job(setup_cover_core_, var, config) + + BUILD_FLAGS = '-DUSE_COVER' CONF_COVER_OPEN = 'cover.open' @@ -63,8 +89,8 @@ COVER_OPEN_ACTION_SCHEMA = maybe_simple_id({ def cover_open_to_code(config, action_id, template_arg, args): for var in get_variable(config[CONF_ID]): yield None - rhs = var.make_open_action(template_arg) type = OpenAction.template(template_arg) + rhs = type.new(var) yield Pvariable(action_id, rhs, type=type) @@ -78,8 +104,8 @@ COVER_CLOSE_ACTION_SCHEMA = maybe_simple_id({ def cover_close_to_code(config, action_id, template_arg, args): for var in get_variable(config[CONF_ID]): yield None - rhs = var.make_close_action(template_arg) type = CloseAction.template(template_arg) + rhs = type.new(var) yield Pvariable(action_id, rhs, type=type) @@ -93,6 +119,43 @@ COVER_STOP_ACTION_SCHEMA = maybe_simple_id({ def cover_stop_to_code(config, action_id, template_arg, args): for var in get_variable(config[CONF_ID]): yield None - rhs = var.make_stop_action(template_arg) type = StopAction.template(template_arg) + rhs = type.new(var) yield Pvariable(action_id, rhs, type=type) + + +CONF_COVER_CONTROL = 'cover.control' +COVER_CONTROL_ACTION_SCHEMA = cv.Schema({ + vol.Required(CONF_ID): cv.use_variable_id(Cover), + vol.Optional(CONF_STOP): cv.templatable(cv.boolean), + vol.Exclusive(CONF_STATE, 'pos'): cv.templatable(cv.one_of(*COVER_STATES)), + vol.Exclusive(CONF_POSITION, 'pos'): cv.templatable(cv.percentage), + vol.Optional(CONF_TILT): cv.templatable(cv.percentage), +}) + + +@ACTION_REGISTRY.register(CONF_COVER_CONTROL, COVER_CONTROL_ACTION_SCHEMA) +def cover_control_to_code(config, action_id, template_arg, args): + for var in get_variable(config[CONF_ID]): + yield None + type = StopAction.template(template_arg) + rhs = type.new(var) + action = Pvariable(action_id, rhs, type=type) + if CONF_STOP in config: + for template_ in templatable(config[CONF_STOP], args, bool): + yield None + add(action.set_stop(template_)) + if CONF_STATE in config: + for template_ in templatable(config[CONF_STATE], args, float, + to_exp=COVER_STATES): + yield None + add(action.set_position(template_)) + if CONF_POSITION in config: + for template_ in templatable(config[CONF_POSITION], args, float): + yield None + add(action.set_position(template_)) + if CONF_TILT in config: + for template_ in templatable(config[CONF_TILT], args, float): + yield None + add(action.set_tilt(template_)) + yield action diff --git a/esphome/components/cover/endstop.py b/esphome/components/cover/endstop.py new file mode 100644 index 0000000000..a8e8b3e2ae --- /dev/null +++ b/esphome/components/cover/endstop.py @@ -0,0 +1,57 @@ +import voluptuous as vol + +from esphome import automation +from esphome.components import binary_sensor, cover +import esphome.config_validation as cv +from esphome.const import CONF_CLOSE_ACTION, CONF_CLOSE_DURATION, \ + CONF_CLOSE_ENDSTOP, CONF_ID, CONF_NAME, CONF_OPEN_ACTION, CONF_OPEN_DURATION, \ + CONF_OPEN_ENDSTOP, CONF_STOP_ACTION, CONF_MAX_DURATION +from esphome.cpp_generator import Pvariable, add, get_variable +from esphome.cpp_helpers import setup_component +from esphome.cpp_types import App, Component + +EndstopCover = cover.cover_ns.class_('EndstopCover', cover.Cover, Component) + +PLATFORM_SCHEMA = cv.nameable(cover.COVER_PLATFORM_SCHEMA.extend({ + cv.GenerateID(): cv.declare_variable_id(EndstopCover), + vol.Required(CONF_STOP_ACTION): automation.validate_automation(single=True), + + vol.Required(CONF_OPEN_ENDSTOP): cv.use_variable_id(binary_sensor.BinarySensor), + vol.Required(CONF_OPEN_ACTION): automation.validate_automation(single=True), + vol.Required(CONF_OPEN_DURATION): cv.positive_time_period_milliseconds, + + vol.Required(CONF_CLOSE_ACTION): automation.validate_automation(single=True), + vol.Required(CONF_CLOSE_ENDSTOP): cv.use_variable_id(binary_sensor.BinarySensor), + vol.Required(CONF_CLOSE_DURATION): cv.positive_time_period_milliseconds, + vol.Optional(CONF_MAX_DURATION): cv.positive_time_period_milliseconds, +}).extend(cv.COMPONENT_SCHEMA.schema)) + + +def to_code(config): + rhs = App.register_component(EndstopCover.new(config[CONF_NAME])) + var = Pvariable(config[CONF_ID], rhs) + cover.register_cover(var, config) + setup_component(var, config) + + automation.build_automations(var.get_stop_trigger(), [], + config[CONF_STOP_ACTION]) + + for bin in get_variable(config[CONF_OPEN_ENDSTOP]): + yield + add(var.set_open_endstop(bin)) + add(var.set_open_duration(config[CONF_OPEN_DURATION])) + automation.build_automations(var.get_open_trigger(), [], + config[CONF_OPEN_ACTION]) + + for bin in get_variable(config[CONF_CLOSE_ENDSTOP]): + yield + add(var.set_close_endstop(bin)) + add(var.set_close_duration(config[CONF_CLOSE_DURATION])) + automation.build_automations(var.get_close_trigger(), [], + config[CONF_CLOSE_ACTION]) + + if CONF_MAX_DURATION in config: + add(var.set_max_duration(config[CONF_MAX_DURATION])) + + +BUILD_FLAGS = '-DUSE_ENDSTOP_COVER' diff --git a/esphome/components/cover/template.py b/esphome/components/cover/template.py index a7e2c19d39..7d194d7d04 100644 --- a/esphome/components/cover/template.py +++ b/esphome/components/cover/template.py @@ -5,15 +5,22 @@ from esphome.automation import ACTION_REGISTRY from esphome.components import cover import esphome.config_validation as cv from esphome.const import CONF_ASSUMED_STATE, CONF_CLOSE_ACTION, CONF_ID, CONF_LAMBDA, CONF_NAME, \ - CONF_OPEN_ACTION, CONF_OPTIMISTIC, CONF_STATE, CONF_STOP_ACTION + CONF_OPEN_ACTION, CONF_OPTIMISTIC, CONF_STATE, CONF_STOP_ACTION, CONF_POSITION, \ + CONF_CURRENT_OPERATION, CONF_RESTORE_MODE from esphome.cpp_generator import Pvariable, add, get_variable, process_lambda, templatable from esphome.cpp_helpers import setup_component from esphome.cpp_types import Action, App, optional -from esphome.py_compat import string_types TemplateCover = cover.cover_ns.class_('TemplateCover', cover.Cover) CoverPublishAction = cover.cover_ns.class_('CoverPublishAction', Action) +TemplateCoverRestoreMode = cover.cover_ns.enum('TemplateCoverRestoreMode') +RESTORE_MODES = { + 'NO_RESTORE': TemplateCoverRestoreMode.NO_RESTORE, + 'RESTORE': TemplateCoverRestoreMode.RESTORE, + 'RESTORE_AND_CALL': TemplateCoverRestoreMode.RESTORE_AND_CALL, +} + PLATFORM_SCHEMA = cv.nameable(cover.COVER_PLATFORM_SCHEMA.extend({ cv.GenerateID(): cv.declare_variable_id(TemplateCover), vol.Optional(CONF_LAMBDA): cv.lambda_, @@ -22,19 +29,19 @@ PLATFORM_SCHEMA = cv.nameable(cover.COVER_PLATFORM_SCHEMA.extend({ vol.Optional(CONF_OPEN_ACTION): automation.validate_automation(single=True), vol.Optional(CONF_CLOSE_ACTION): automation.validate_automation(single=True), vol.Optional(CONF_STOP_ACTION): automation.validate_automation(single=True), + vol.Optional(CONF_RESTORE_MODE): cv.one_of(*RESTORE_MODES, upper=True), }).extend(cv.COMPONENT_SCHEMA.schema)) def to_code(config): - rhs = App.make_template_cover(config[CONF_NAME]) + rhs = App.register_component(TemplateCover.new(config[CONF_NAME])) var = Pvariable(config[CONF_ID], rhs) - - cover.setup_cover(var, config) + cover.register_cover(var, config) setup_component(var, config) if CONF_LAMBDA in config: for template_ in process_lambda(config[CONF_LAMBDA], [], - return_type=optional.template(cover.CoverState)): + return_type=optional.template(float)): yield add(var.set_state_lambda(template_)) if CONF_OPEN_ACTION in config: @@ -50,6 +57,8 @@ def to_code(config): add(var.set_optimistic(config[CONF_OPTIMISTIC])) if CONF_ASSUMED_STATE in config: add(var.set_assumed_state(config[CONF_ASSUMED_STATE])) + if CONF_RESTORE_MODE in config: + add(var.set_restore_mode(RESTORE_MODES[config[CONF_RESTORE_MODE]])) BUILD_FLAGS = '-DUSE_TEMPLATE_COVER' @@ -57,7 +66,9 @@ BUILD_FLAGS = '-DUSE_TEMPLATE_COVER' CONF_COVER_TEMPLATE_PUBLISH = 'cover.template.publish' COVER_TEMPLATE_PUBLISH_ACTION_SCHEMA = cv.Schema({ vol.Required(CONF_ID): cv.use_variable_id(cover.Cover), - vol.Required(CONF_STATE): cv.templatable(cover.validate_cover_state), + vol.Exclusive(CONF_STATE, 'pos'): cv.templatable(cover.validate_cover_state), + vol.Exclusive(CONF_POSITION, 'pos'): cv.templatable(cv.zero_to_one_float), + vol.Optional(CONF_CURRENT_OPERATION): cv.templatable(cover.validate_cover_operation), }) @@ -66,14 +77,22 @@ COVER_TEMPLATE_PUBLISH_ACTION_SCHEMA = cv.Schema({ def cover_template_publish_to_code(config, action_id, template_arg, args): for var in get_variable(config[CONF_ID]): yield None - rhs = var.make_cover_publish_action(template_arg) type = CoverPublishAction.template(template_arg) + rhs = type.new(var) action = Pvariable(action_id, rhs, type=type) - state = config[CONF_STATE] - if isinstance(state, string_types): - template_ = cover.COVER_STATES[state] - else: - for template_ in templatable(state, args, cover.CoverState): + if CONF_STATE in config: + for template_ in templatable(config[CONF_STATE], args, float, + to_exp=cover.COVER_STATES): yield None - add(action.set_state(template_)) + add(action.set_position(template_)) + if CONF_POSITION in config: + for template_ in templatable(config[CONF_POSITION], args, float, + to_exp=cover.COVER_STATES): + yield None + add(action.set_position(template_)) + if CONF_CURRENT_OPERATION in config: + for template_ in templatable(config[CONF_CURRENT_OPERATION], args, cover.CoverOperation, + to_exp=cover.COVER_OPERATIONS): + yield None + add(action.set_current_operation(template_)) yield action diff --git a/esphome/components/cover/time_based.py b/esphome/components/cover/time_based.py new file mode 100644 index 0000000000..80411973f2 --- /dev/null +++ b/esphome/components/cover/time_based.py @@ -0,0 +1,44 @@ +import voluptuous as vol + +from esphome import automation +from esphome.components import cover +import esphome.config_validation as cv +from esphome.const import CONF_CLOSE_ACTION, CONF_CLOSE_DURATION, CONF_ID, CONF_NAME, \ + CONF_OPEN_ACTION, CONF_OPEN_DURATION, CONF_STOP_ACTION +from esphome.cpp_generator import Pvariable, add +from esphome.cpp_helpers import setup_component +from esphome.cpp_types import App, Component + +TimeBasedCover = cover.cover_ns.class_('TimeBasedCover', cover.Cover, Component) + +PLATFORM_SCHEMA = cv.nameable(cover.COVER_PLATFORM_SCHEMA.extend({ + cv.GenerateID(): cv.declare_variable_id(TimeBasedCover), + vol.Required(CONF_STOP_ACTION): automation.validate_automation(single=True), + + vol.Required(CONF_OPEN_ACTION): automation.validate_automation(single=True), + vol.Required(CONF_OPEN_DURATION): cv.positive_time_period_milliseconds, + + vol.Required(CONF_CLOSE_ACTION): automation.validate_automation(single=True), + vol.Required(CONF_CLOSE_DURATION): cv.positive_time_period_milliseconds, +}).extend(cv.COMPONENT_SCHEMA.schema)) + + +def to_code(config): + rhs = App.register_component(TimeBasedCover.new(config[CONF_NAME])) + var = Pvariable(config[CONF_ID], rhs) + cover.register_cover(var, config) + setup_component(var, config) + + automation.build_automations(var.get_stop_trigger(), [], + config[CONF_STOP_ACTION]) + + add(var.set_open_duration(config[CONF_OPEN_DURATION])) + automation.build_automations(var.get_open_trigger(), [], + config[CONF_OPEN_ACTION]) + + add(var.set_close_duration(config[CONF_CLOSE_DURATION])) + automation.build_automations(var.get_close_trigger(), [], + config[CONF_CLOSE_ACTION]) + + +BUILD_FLAGS = '-DUSE_TIME_BASED_COVER' diff --git a/esphome/components/light/__init__.py b/esphome/components/light/__init__.py index 540964980a..85592ad315 100644 --- a/esphome/components/light/__init__.py +++ b/esphome/components/light/__init__.py @@ -4,7 +4,7 @@ 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, \ +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, \ @@ -12,8 +12,8 @@ from esphome.const import CONF_ALPHA, CONF_BLUE, CONF_BRIGHTNESS, CONF_COLORS, \ from esphome.core import CORE from esphome.cpp_generator import Pvariable, StructInitializer, add, get_variable, process_lambda, \ templatable -from esphome.cpp_types import Action, Application, Component, Nameable, esphome_ns, float_, \ - std_string, uint32, void +from esphome.cpp_types import Action, Application, Component, Nameable, esphome_ns, std_string, \ + uint32, void PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ @@ -31,8 +31,7 @@ AddressableLightRef = AddressableLight.operator('ref') # Actions ToggleAction = light_ns.class_('ToggleAction', Action) -TurnOffAction = light_ns.class_('TurnOffAction', Action) -TurnOnAction = light_ns.class_('TurnOnAction', Action) +LightControlAction = light_ns.class_('LightControlAction', Action) LightColorValues = light_ns.class_('LightColorValues') @@ -249,7 +248,25 @@ LIGHT_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({ cv.GenerateID(CONF_MQTT_ID): cv.declare_variable_id(MQTTJSONLightComponent), }) -LIGHT_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(LIGHT_SCHEMA.schema) +BINARY_LIGHT_SCHEMA = LIGHT_SCHEMA.extend({ + vol.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), +}) + +RGB_LIGHT_SCHEMA = BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend({ + vol.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)), +}) def build_effect(full_config): @@ -371,7 +388,7 @@ def build_effect(full_config): raise NotImplementedError("Effect {} not implemented".format(next(config.keys()))) -def setup_light_core_(light_var, config): +def setup_light_core_(light_var, output_var, config): if CONF_INTERNAL in config: add(light_var.set_internal(config[CONF_INTERNAL])) if CONF_DEFAULT_TRANSITION_LENGTH in config: @@ -386,12 +403,15 @@ def setup_light_core_(light_var, config): if effects: add(light_var.add_effects(effects)) + if CONF_COLOR_CORRECT in config: + add(output_var.set_correction(*config[CONF_COLOR_CORRECT])) + setup_mqtt_component(light_var.Pget_mqtt(), config) -def setup_light(light_obj, 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, config) + CORE.add_job(setup_light_core_, light_var, output_var, config) BUILD_FLAGS = '-DUSE_LIGHT' @@ -407,8 +427,8 @@ LIGHT_TOGGLE_ACTION_SCHEMA = maybe_simple_id({ def light_toggle_to_code(config, action_id, template_arg, args): for var in get_variable(config[CONF_ID]): yield None - rhs = var.make_toggle_action(template_arg) type = ToggleAction.template(template_arg) + rhs = type.new(var) action = Pvariable(action_id, rhs, type=type) if CONF_TRANSITION_LENGTH in config: for template_ in templatable(config[CONF_TRANSITION_LENGTH], args, uint32): @@ -428,8 +448,8 @@ LIGHT_TURN_OFF_ACTION_SCHEMA = maybe_simple_id({ def light_turn_off_to_code(config, action_id, template_arg, args): for var in get_variable(config[CONF_ID]): yield None - rhs = var.make_turn_off_action(template_arg) - type = TurnOffAction.template(template_arg) + type = LightControlAction.template(template_arg) + rhs = type.new(var) action = Pvariable(action_id, rhs, type=type) if CONF_TRANSITION_LENGTH in config: for template_ in templatable(config[CONF_TRANSITION_LENGTH], args, uint32): @@ -438,30 +458,47 @@ def light_turn_off_to_code(config, action_id, template_arg, args): yield action -CONF_LIGHT_TURN_ON = 'light.turn_on' -LIGHT_TURN_ON_ACTION_SCHEMA = maybe_simple_id({ +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.templatable(cv.positive_time_period_milliseconds), vol.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.positive_float), - vol.Optional(CONF_EFFECT): cv.templatable(cv.string), + vol.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, +}) +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, +})) +@ACTION_REGISTRY.register(CONF_LIGHT_TURN_OFF, LIGHT_TURN_OFF_ACTION_SCHEMA) @ACTION_REGISTRY.register(CONF_LIGHT_TURN_ON, LIGHT_TURN_ON_ACTION_SCHEMA) -def light_turn_on_to_code(config, action_id, template_arg, args): +@ACTION_REGISTRY.register(CONF_LIGHT_CONTROL, LIGHT_CONTROL_ACTION_SCHEMA) +def light_control_to_code(config, action_id, template_arg, args): for var in get_variable(config[CONF_ID]): yield None - rhs = var.make_turn_on_action(template_arg) - type = TurnOnAction.template(template_arg) + type = LightControlAction.template(template_arg) + rhs = type.new(var) action = Pvariable(action_id, rhs, type=type) + if CONF_STATE in config: + for template_ in templatable(config[CONF_STATE], args, bool): + yield None + add(action.set_state(template_)) if CONF_TRANSITION_LENGTH in config: for template_ in templatable(config[CONF_TRANSITION_LENGTH], args, uint32): yield None @@ -471,27 +508,27 @@ def light_turn_on_to_code(config, action_id, template_arg, args): yield None add(action.set_flash_length(template_)) if CONF_BRIGHTNESS in config: - for template_ in templatable(config[CONF_BRIGHTNESS], args, float_): + for template_ in templatable(config[CONF_BRIGHTNESS], args, float): yield None add(action.set_brightness(template_)) if CONF_RED in config: - for template_ in templatable(config[CONF_RED], args, float_): + for template_ in templatable(config[CONF_RED], args, float): yield None add(action.set_red(template_)) if CONF_GREEN in config: - for template_ in templatable(config[CONF_GREEN], args, float_): + for template_ in templatable(config[CONF_GREEN], args, float): yield None add(action.set_green(template_)) if CONF_BLUE in config: - for template_ in templatable(config[CONF_BLUE], args, float_): + for template_ in templatable(config[CONF_BLUE], args, float): yield None add(action.set_blue(template_)) if CONF_WHITE in config: - for template_ in templatable(config[CONF_WHITE], args, float_): + for template_ in templatable(config[CONF_WHITE], args, float): yield None add(action.set_white(template_)) if CONF_COLOR_TEMPERATURE in config: - for template_ in templatable(config[CONF_COLOR_TEMPERATURE], args, float_): + for template_ in templatable(config[CONF_COLOR_TEMPERATURE], args, float): yield None add(action.set_color_temperature(template_)) if CONF_EFFECT in config: diff --git a/esphome/components/light/binary.py b/esphome/components/light/binary.py index 522a4f082c..42d42112c9 100644 --- a/esphome/components/light/binary.py +++ b/esphome/components/light/binary.py @@ -2,16 +2,15 @@ import voluptuous as vol from esphome.components import light, output import esphome.config_validation as cv -from esphome.const import CONF_EFFECTS, CONF_MAKE_ID, CONF_NAME, CONF_OUTPUT +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.LIGHT_PLATFORM_SCHEMA.extend({ +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), - vol.Optional(CONF_EFFECTS): light.validate_effects(light.BINARY_EFFECTS), -}).extend(cv.COMPONENT_SCHEMA.schema)) +}).extend(light.BINARY_LIGHT_SCHEMA.schema).extend(cv.COMPONENT_SCHEMA.schema)) def to_code(config): @@ -19,5 +18,5 @@ def to_code(config): yield rhs = App.make_binary_light(config[CONF_NAME], output_) light_struct = variable(config[CONF_MAKE_ID], rhs) - light.setup_light(light_struct.Pstate, config) + light.setup_light(light_struct.Pstate, light_struct.Poutput, config) setup_component(light_struct.Pstate, config) diff --git a/esphome/components/light/cwww.py b/esphome/components/light/cwww.py index 19447fdcab..e8b7dad3e2 100644 --- a/esphome/components/light/cwww.py +++ b/esphome/components/light/cwww.py @@ -1,27 +1,22 @@ import voluptuous as vol from esphome.components import light, output -from esphome.components.light.rgbww import validate_cold_white_colder, \ - validate_color_temperature +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_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, CONF_GAMMA_CORRECT, CONF_MAKE_ID, \ +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.LIGHT_PLATFORM_SCHEMA.extend({ +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): validate_color_temperature, - vol.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): validate_color_temperature, - - vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float, - vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds, - vol.Optional(CONF_EFFECTS): light.validate_effects(light.MONOCHROMATIC_EFFECTS), -}).extend(cv.COMPONENT_SCHEMA.schema), validate_cold_white_colder) + 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): @@ -33,5 +28,5 @@ def to_code(config): config[CONF_WARM_WHITE_COLOR_TEMPERATURE], cold_white, warm_white) light_struct = variable(config[CONF_MAKE_ID], rhs) - light.setup_light(light_struct.Pstate, config) + light.setup_light(light_struct.Pstate, light_struct.Poutput, config) setup_component(light_struct.Pstate, config) diff --git a/esphome/components/light/fastled_clockless.py b/esphome/components/light/fastled_clockless.py index df872a6428..f50498a681 100644 --- a/esphome/components/light/fastled_clockless.py +++ b/esphome/components/light/fastled_clockless.py @@ -4,8 +4,7 @@ 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_COLOR_CORRECT, CONF_DEFAULT_TRANSITION_LENGTH, \ - CONF_EFFECTS, CONF_GAMMA_CORRECT, CONF_MAKE_ID, CONF_MAX_REFRESH_RATE, CONF_NAME, \ +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 @@ -56,8 +55,7 @@ def validate(value): MakeFastLEDLight = Application.struct('MakeFastLEDLight') -PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({ - cv.GenerateID(): cv.declare_variable_id(light.AddressableLightState), +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), @@ -67,12 +65,8 @@ PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({ 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_GAMMA_CORRECT): cv.positive_float, - vol.Optional(CONF_COLOR_CORRECT): vol.All([cv.percentage], vol.Length(min=3, max=3)), - vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds, vol.Optional(CONF_POWER_SUPPLY): cv.use_variable_id(PowerSupplyComponent), - vol.Optional(CONF_EFFECTS): light.validate_effects(light.ADDRESSABLE_EFFECTS), -}).extend(cv.COMPONENT_SCHEMA.schema), validate) +}).extend(light.ADDRESSABLE_LIGHT_SCHEMA.schema).extend(cv.COMPONENT_SCHEMA.schema), validate) def to_code(config): @@ -95,11 +89,7 @@ def to_code(config): yield add(fast_led.set_power_supply(power_supply)) - if CONF_COLOR_CORRECT in config: - r, g, b = config[CONF_COLOR_CORRECT] - add(fast_led.set_correction(r, g, b)) - - light.setup_light(make.Pstate, config) + light.setup_light(make.Pstate, make.Pfast_led, config) setup_component(fast_led, config) diff --git a/esphome/components/light/fastled_spi.py b/esphome/components/light/fastled_spi.py index 5abfce21cd..c5e68d5c6f 100644 --- a/esphome/components/light/fastled_spi.py +++ b/esphome/components/light/fastled_spi.py @@ -4,8 +4,7 @@ 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_COLOR_CORRECT, CONF_DATA_PIN, \ - CONF_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, CONF_GAMMA_CORRECT, CONF_MAKE_ID, \ +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 @@ -33,8 +32,7 @@ RGB_ORDERS = [ MakeFastLEDLight = Application.struct('MakeFastLEDLight') -PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({ - cv.GenerateID(): cv.declare_variable_id(light.AddressableLightState), +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), @@ -45,12 +43,11 @@ PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({ 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_GAMMA_CORRECT): cv.positive_float, - vol.Optional(CONF_COLOR_CORRECT): vol.All([cv.percentage], vol.Length(min=3, max=3)), - vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds, vol.Optional(CONF_POWER_SUPPLY): cv.use_variable_id(PowerSupplyComponent), - vol.Optional(CONF_EFFECTS): light.validate_effects(light.ADDRESSABLE_EFFECTS), -}).extend(cv.COMPONENT_SCHEMA.schema)) + 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): @@ -75,11 +72,7 @@ def to_code(config): yield add(fast_led.set_power_supply(power_supply)) - if CONF_COLOR_CORRECT in config: - r, g, b = config[CONF_COLOR_CORRECT] - add(fast_led.set_correction(r, g, b)) - - light.setup_light(make.Pstate, config) + light.setup_light(make.Pstate, make.Pfast_led, config) setup_component(fast_led, config) diff --git a/esphome/components/light/monochromatic.py b/esphome/components/light/monochromatic.py index ca819dd2c8..1db1120cfd 100644 --- a/esphome/components/light/monochromatic.py +++ b/esphome/components/light/monochromatic.py @@ -2,19 +2,15 @@ import voluptuous as vol from esphome.components import light, output import esphome.config_validation as cv -from esphome.const import CONF_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, CONF_GAMMA_CORRECT, \ - CONF_MAKE_ID, CONF_NAME, CONF_OUTPUT +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.LIGHT_PLATFORM_SCHEMA.extend({ +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), - vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float, - vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds, - vol.Optional(CONF_EFFECTS): light.validate_effects(light.MONOCHROMATIC_EFFECTS), -}).extend(cv.COMPONENT_SCHEMA.schema)) +}).extend(light.BRIGHTNESS_ONLY_LIGHT_SCHEMA.schema).extend(cv.COMPONENT_SCHEMA.schema)) def to_code(config): @@ -22,5 +18,5 @@ def to_code(config): yield rhs = App.make_monochromatic_light(config[CONF_NAME], output_) light_struct = variable(config[CONF_MAKE_ID], rhs) - light.setup_light(light_struct.Pstate, config) + light.setup_light(light_struct.Pstate, light_struct.Poutput, config) setup_component(light_struct.Pstate, config) diff --git a/esphome/components/light/neopixelbus.py b/esphome/components/light/neopixelbus.py index be8bc9052a..5ba0fd889a 100644 --- a/esphome/components/light/neopixelbus.py +++ b/esphome/components/light/neopixelbus.py @@ -5,9 +5,8 @@ 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_COLOR_CORRECT, CONF_DATA_PIN, \ - CONF_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, CONF_GAMMA_CORRECT, CONF_MAKE_ID, CONF_METHOD, \ - CONF_NAME, CONF_NUM_LEDS, CONF_PIN, CONF_POWER_SUPPLY, CONF_TYPE, CONF_VARIANT +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 @@ -130,8 +129,7 @@ def validate(config): MakeNeoPixelBusLight = Application.struct('MakeNeoPixelBusLight') -PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({ - cv.GenerateID(): cv.declare_variable_id(light.AddressableLightState), +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, @@ -143,12 +141,9 @@ PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({ vol.Required(CONF_NUM_LEDS): cv.positive_not_null_int, - vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float, - vol.Optional(CONF_COLOR_CORRECT): vol.All([cv.percentage], vol.Length(min=3, max=4)), - vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds, vol.Optional(CONF_POWER_SUPPLY): cv.use_variable_id(PowerSupplyComponent), - vol.Optional(CONF_EFFECTS): light.validate_effects(light.ADDRESSABLE_EFFECTS), -}).extend(cv.COMPONENT_SCHEMA.schema), validate, validate_method_pin) +}).extend(light.ADDRESSABLE_LIGHT_SCHEMA.schema).extend(cv.COMPONENT_SCHEMA.schema), + validate, validate_method_pin) def to_code(config): @@ -178,10 +173,7 @@ def to_code(config): yield add(output.set_power_supply(power_supply)) - if CONF_COLOR_CORRECT in config: - add(output.set_correction(*config[CONF_COLOR_CORRECT])) - - light.setup_light(make.Pstate, config) + light.setup_light(make.Pstate, output, config) setup_component(output, config) diff --git a/esphome/components/light/partition.py b/esphome/components/light/partition.py index 61587492d8..122f3d2fc6 100644 --- a/esphome/components/light/partition.py +++ b/esphome/components/light/partition.py @@ -3,8 +3,7 @@ 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_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, CONF_FROM, \ - CONF_GAMMA_CORRECT, CONF_ID, CONF_MAKE_ID, CONF_NAME, CONF_SEGMENTS, CONF_TO +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 @@ -20,7 +19,7 @@ def validate_from_to(value): return value -PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({ +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), @@ -29,11 +28,7 @@ PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({ vol.Required(CONF_FROM): cv.positive_int, vol.Required(CONF_TO): cv.positive_int, }, validate_from_to), vol.Length(min=1)), - - vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float, - vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds, - vol.Optional(CONF_EFFECTS): light.validate_effects(light.ADDRESSABLE_EFFECTS), -})) +}).extend(light.ADDRESSABLE_LIGHT_SCHEMA.schema)) def to_code(config): @@ -46,4 +41,4 @@ def to_code(config): rhs = App.make_partition_light(config[CONF_NAME], segments) make = variable(config[CONF_MAKE_ID], rhs) - light.setup_light(make.Pstate, config) + light.setup_light(make.Pstate, make.Ppartition, config) diff --git a/esphome/components/light/rgb.py b/esphome/components/light/rgb.py index 77791fdefd..14ada47a74 100644 --- a/esphome/components/light/rgb.py +++ b/esphome/components/light/rgb.py @@ -2,21 +2,17 @@ import voluptuous as vol from esphome.components import light, output import esphome.config_validation as cv -from esphome.const import CONF_BLUE, CONF_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, \ - CONF_GAMMA_CORRECT, CONF_GREEN, CONF_MAKE_ID, CONF_NAME, CONF_RED +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.LIGHT_PLATFORM_SCHEMA.extend({ +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.Optional(CONF_GAMMA_CORRECT): cv.positive_float, - vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds, - vol.Optional(CONF_EFFECTS): light.validate_effects(light.RGB_EFFECTS), -}).extend(cv.COMPONENT_SCHEMA.schema)) +}).extend(light.RGB_LIGHT_SCHEMA.schema).extend(cv.COMPONENT_SCHEMA.schema)) def to_code(config): @@ -28,5 +24,5 @@ def to_code(config): yield 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, config) + light.setup_light(light_struct.Pstate, light_struct.Poutput, config) setup_component(light_struct.Pstate, config) diff --git a/esphome/components/light/rgbw.py b/esphome/components/light/rgbw.py index d421322eb1..eed78dc56d 100644 --- a/esphome/components/light/rgbw.py +++ b/esphome/components/light/rgbw.py @@ -2,22 +2,18 @@ import voluptuous as vol from esphome.components import light, output import esphome.config_validation as cv -from esphome.const import CONF_BLUE, CONF_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, \ - CONF_GAMMA_CORRECT, CONF_GREEN, CONF_MAKE_ID, CONF_NAME, CONF_RED, CONF_WHITE +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.LIGHT_PLATFORM_SCHEMA.extend({ +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), - vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float, - vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds, - vol.Optional(CONF_EFFECTS): light.validate_effects(light.RGB_EFFECTS), -}).extend(cv.COMPONENT_SCHEMA.schema)) +}).extend(light.RGB_LIGHT_SCHEMA.schema).extend(cv.COMPONENT_SCHEMA.schema)) def to_code(config): @@ -31,5 +27,5 @@ def to_code(config): yield 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, config) + light.setup_light(light_struct.Pstate, light_struct.Poutput, config) setup_component(light_struct.Pstate, config) diff --git a/esphome/components/light/rgbww.py b/esphome/components/light/rgbww.py index 5af68cb74d..0a4691f156 100644 --- a/esphome/components/light/rgbww.py +++ b/esphome/components/light/rgbww.py @@ -3,23 +3,13 @@ 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_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, CONF_GAMMA_CORRECT, CONF_GREEN, CONF_MAKE_ID, \ - CONF_NAME, CONF_RED, CONF_WARM_WHITE, CONF_WARM_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_color_temperature(value): - try: - val = cv.float_with_unit('Color Temperature', 'mireds')(value) - except vol.Invalid: - val = 1000000.0 / cv.float_with_unit('Color Temperature', 'K')(value) - if val < 0: - raise vol.Invalid("Color temperature cannot be negative") - return val - - def validate_cold_white_colder(value): cw = value[CONF_COLD_WHITE_COLOR_TEMPERATURE] ww = value[CONF_WARM_WHITE_COLOR_TEMPERATURE] @@ -30,20 +20,17 @@ def validate_cold_white_colder(value): return value -PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({ +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): validate_color_temperature, - vol.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): validate_color_temperature, - - vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float, - vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds, - vol.Optional(CONF_EFFECTS): light.validate_effects(light.RGB_EFFECTS), -}).extend(cv.COMPONENT_SCHEMA.schema), validate_cold_white_colder) + 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): @@ -61,5 +48,5 @@ def to_code(config): 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, config) + light.setup_light(light_struct.Pstate, light_struct.Poutput, config) setup_component(light_struct.Pstate, config) diff --git a/esphome/components/ota.py b/esphome/components/ota.py index 18dd5cbd85..3805226aa3 100644 --- a/esphome/components/ota.py +++ b/esphome/components/ota.py @@ -46,7 +46,6 @@ def get_auth(config): BUILD_FLAGS = '-DUSE_OTA' -REQUIRED_BUILD_FLAGS = '-DUSE_NEW_OTA' def lib_deps(config): diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 3d7371c03d..75fe0dda6b 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -242,9 +242,10 @@ def setup_sensor(sensor_obj, config): def register_sensor(var, config): - sensor_var = Pvariable(config[CONF_ID], var, has_side_effects=True) - add(App.register_sensor(sensor_var)) - CORE.add_job(setup_sensor_core_, sensor_var, config) + if not CORE.has_id(config[CONF_ID]): + var = Pvariable(config[CONF_ID], var, has_side_effects=True) + add(App.register_sensor(var)) + CORE.add_job(setup_sensor_core_, var, config) BUILD_FLAGS = '-DUSE_SENSOR' diff --git a/esphome/config_validation.py b/esphome/config_validation.py index a78f0ad503..abc462818c 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -16,7 +16,7 @@ from esphome.const import CONF_AVAILABILITY, CONF_COMMAND_TOPIC, CONF_DISCOVERY, ESP_PLATFORM_ESP8266 from esphome.core import CORE, HexInt, IPAddress, Lambda, TimePeriod, TimePeriodMicroseconds, \ TimePeriodMilliseconds, TimePeriodSeconds, TimePeriodMinutes -from esphome.py_compat import integer_types, string_types, text_type +from esphome.py_compat import integer_types, string_types, text_type, IS_PY2 from esphome.voluptuous_schema import _Schema _LOGGER = logging.getLogger(__name__) @@ -420,7 +420,7 @@ METRIC_SUFFIXES = { def float_with_unit(quantity, regex_suffix): - pattern = re.compile(r"^([-+]?[0-9]*\.?[0-9]*)\s*(\w*?)" + regex_suffix + "$") + pattern = re.compile(ur"^([-+]?[0-9]*\.?[0-9]*)\s*(\w*?)" + regex_suffix + ur"$", re.UNICODE) def validator(value): match = pattern.match(string(value)) @@ -438,12 +438,62 @@ def float_with_unit(quantity, regex_suffix): return validator -frequency = float_with_unit("frequency", r"(Hz|HZ|hz)?") -resistance = float_with_unit("resistance", r"(Ω|Ω|ohm|Ohm|OHM)?") -current = float_with_unit("current", r"(a|A|amp|Amp|amps|Amps|ampere|Ampere)?") -voltage = float_with_unit("voltage", r"(v|V|volt|Volts)?") -distance = float_with_unit("distance", r"(m)") -framerate = float_with_unit("framerate", r"(FPS|fps|Fps|FpS|Hz)") +frequency = float_with_unit("frequency", ur"(Hz|HZ|hz)?") +resistance = float_with_unit("resistance", ur"(Ω|Ω|ohm|Ohm|OHM)?") +current = float_with_unit("current", ur"(a|A|amp|Amp|amps|Amps|ampere|Ampere)?") +voltage = float_with_unit("voltage", ur"(v|V|volt|Volts)?") +distance = float_with_unit("distance", ur"(m)") +framerate = float_with_unit("framerate", ur"(FPS|fps|Fps|FpS|Hz)") +_temperature_c = float_with_unit("temperature", ur"(°C|° C|°|C)?") +_temperature_k = float_with_unit("temperature", ur"(° K|° K|K)?") +_temperature_f = float_with_unit("temperature", ur"(°F|° F|F)?") + +if IS_PY2: + # Override voluptuous invalid to unicode for py2 + def _vol_invalid_unicode(self): + path = u' @ data[%s]' % u']['.join(map(repr, self.path)) \ + if self.path else '' + output = Exception.__unicode__(self) + if self.error_type: + output += u' for ' + self.error_type + return output + path + + vol.Invalid.__unicode__ = _vol_invalid_unicode + + +def temperature(value): + try: + return _temperature_c(value) + except vol.Invalid as orig_err: + pass + + try: + kelvin = _temperature_k(value) + return kelvin - 273.15 + except vol.Invalid: + pass + + try: + fahrenheit = _temperature_f(value) + return (fahrenheit - 32) * (5/9) + except vol.Invalid: + pass + + raise orig_err + + +_color_temperature_mireds = float_with_unit('Color Temperature', ur'(mireds|Mireds)') +_color_temperature_kelvin = float_with_unit('Color Temperature', ur'(K|Kelvin)') + + +def color_temperature(value): + try: + val = _color_temperature_mireds(value) + except vol.Invalid: + val = 1000000.0 / _color_temperature_kelvin(value) + if val < 0: + raise vol.Invalid("Color temperature cannot be negative") + return val def validate_bytes(value): diff --git a/esphome/const.py b/esphome/const.py index 901940f2dc..36a2861a4f 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -266,6 +266,9 @@ CONF_INTERNAL = 'internal' CONF_BUILD_PATH = 'build_path' CONF_PLATFORMIO_OPTIONS = 'platformio_options' CONF_REBOOT_TIMEOUT = 'reboot_timeout' +CONF_STOP = 'stop' +CONF_POSITION = 'position' +CONF_TILT = 'tilt' CONF_INVERT = 'invert' CONF_DELAYED_ON = 'delayed_on' CONF_DELAYED_OFF = 'delayed_off' @@ -299,7 +302,13 @@ CONF_COLOR_TEMPERATURE = 'color_temperature' CONF_BATTERY_LEVEL = 'battery_level' CONF_MOISTURE = 'moisture' CONF_CONDUCTIVITY = 'conductivity' +CONF_OPEN_ENDSTOP = 'open_endstop' +CONF_OPEN_DURATION = 'open_duration' +CONF_CLOSE_ENDSTOP = 'close_endstop' +CONF_CLOSE_DURATION = 'close_duration' +CONF_MAX_DURATION = 'max_duration' CONF_RC_SWITCH_RAW = 'rc_switch_raw' +CONF_CURRENT_OPERATION = 'current_operation' CONF_RC_SWITCH_TYPE_A = 'rc_switch_type_a' CONF_RC_SWITCH_TYPE_B = 'rc_switch_type_b' CONF_RC_SWITCH_TYPE_C = 'rc_switch_type_c' diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index f2f2f058ff..ea8ab17f34 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -1,14 +1,14 @@ from collections import OrderedDict import math -from esphome.core import CORE, HexInt, Lambda, TimePeriod, TimePeriodMicroseconds, \ - TimePeriodMilliseconds, TimePeriodSeconds, TimePeriodMinutes -from esphome.helpers import cpp_string_escape, indent_all_but_first_and_last - # pylint: disable=unused-import, wrong-import-order -from typing import Any, Generator, List, Optional, Tuple, Union # noqa -from esphome.core import ID # noqa -from esphome.py_compat import text_type, string_types, integer_types +from typing import Any, Generator, List, Optional, Tuple, Type, Union, Dict, Callable # noqa + +from esphome.core import ( # noqa + CORE, HexInt, ID, Lambda, TimePeriod, TimePeriodMicroseconds, + TimePeriodMilliseconds, TimePeriodMinutes, TimePeriodSeconds) +from esphome.helpers import cpp_string_escape, indent_all_but_first_and_last +from esphome.py_compat import integer_types, string_types, text_type class Expression(object): @@ -30,7 +30,8 @@ class Expression(object): return self.required -SafeExpType = Union[Expression, bool, str, text_type, int, float, TimePeriod] +SafeExpType = Union[Expression, bool, str, text_type, int, float, TimePeriod, + Type[bool], Type[int], Type[float]] class RawExpression(Expression): @@ -190,9 +191,9 @@ class LambdaExpression(Expression): self.parameters = parameters self.requires.append(self.parameters) self.capture = capture - self.return_type = return_type + self.return_type = safe_exp(return_type) if return_type is not None else None if return_type is not None: - self.requires.append(return_type) + self.requires.append(self.return_type) for i in range(1, len(parts), 3): self.requires.append(parts[i]) @@ -271,6 +272,8 @@ def safe_exp( obj # type: Union[Expression, bool, str, unicode, int, long, float, TimePeriod, list] ): # type: (...) -> Expression + from esphome.cpp_types import bool_, float_, int32 + if isinstance(obj, Expression): return obj if isinstance(obj, bool): @@ -293,6 +296,12 @@ def safe_exp( return IntLiteral(int(obj.total_minutes)) if isinstance(obj, (tuple, list)): return ArrayInitializer(*[safe_exp(o) for o in obj]) + if obj is bool: + return bool_ + if obj is int: + return int32 + if obj is float: + return float_ raise ValueError(u"Object is not an expression", obj) @@ -425,15 +434,21 @@ def process_lambda(value, # type: Lambda def templatable(value, # type: Any - args, # type: List[Tuple[Expression, str]] - output_type # type: Optional[Expression] + args, # type: List[Tuple[SafeExpType, str]] + output_type, # type: Optional[SafeExpType], + to_exp=None # type: Optional[Any] ): if isinstance(value, Lambda): for lambda_ in process_lambda(value, args, return_type=output_type): yield None yield lambda_ else: - yield value + if to_exp is None: + yield value + elif isinstance(to_exp, dict): + yield to_exp[value] + else: + yield to_exp(value) class MockObj(Expression): diff --git a/tests/test1.yaml b/tests/test1.yaml index ff75ac29eb..56da4e708f 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -88,11 +88,11 @@ mqtt: ESP_LOGD("main", "The data is: %d", data); - light.turn_on: id: living_room_lights - transition_length: !lambda |- - int length = 1000; - if (x.containsKey("length")) - length = x["length"]; - return length; + brightness: !lambda |- + float brightness = 1.0; + if (x.containsKey("brightness")) + brightness = x["brightness"]; + return brightness; effect: !lambda |- const char *effect = "None"; if (x.containsKey("effect"))