diff --git a/esphomeyaml/automation.py b/esphomeyaml/automation.py index 08077aaf2b..b4b568d049 100644 --- a/esphomeyaml/automation.py +++ b/esphomeyaml/automation.py @@ -78,17 +78,33 @@ RangeCondition = esphomelib_ns.RangeCondition LambdaCondition = esphomelib_ns.LambdaCondition -def validate_automation(extra_schema=None): +def validate_automation(extra_schema=None, extra_validators=None, single=False): schema = AUTOMATION_SCHEMA.extend(extra_schema or {}) - def validator(value): + def validator_(value): if isinstance(value, list): - return schema({CONF_THEN: value}) + try: + # First try as a sequence of actions + return [schema({CONF_THEN: value})] + except vol.Invalid: + # Next try as a sequence of automations + return vol.Schema([schema])(value) elif isinstance(value, dict): if CONF_THEN in value: - return schema(value) - return schema({CONF_THEN: value}) - return schema(value) + return [schema(value)] + return [schema({CONF_THEN: value})] + # This should only happen with invalid configs, but let's have a nice error message. + return [schema(value)] + + def validator(value): + value = validator_(value) + if extra_validators is not None: + value = vol.Schema([extra_validators])(value) + if single: + if len(value) != 1: + raise vol.Invalid("Cannot have more than 1 automation for templates") + return value[0] + return value return validator diff --git a/esphomeyaml/components/binary_sensor/__init__.py b/esphomeyaml/components/binary_sensor/__init__.py index 92d579d18b..32731a08ce 100644 --- a/esphomeyaml/components/binary_sensor/__init__.py +++ b/esphomeyaml/components/binary_sensor/__init__.py @@ -49,23 +49,22 @@ BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({ vol.Optional(CONF_DEVICE_CLASS): vol.All(vol.Lower, cv.one_of(*DEVICE_CLASSES)), vol.Optional(CONF_FILTERS): FILTERS_SCHEMA, - vol.Optional(CONF_ON_PRESS): vol.All(cv.ensure_list, [automation.validate_automation({ + vol.Optional(CONF_ON_PRESS): automation.validate_automation({ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(PressTrigger), - })]), - vol.Optional(CONF_ON_RELEASE): vol.All(cv.ensure_list, [automation.validate_automation({ + }), + vol.Optional(CONF_ON_RELEASE): automation.validate_automation({ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(ReleaseTrigger), - })]), - vol.Optional(CONF_ON_CLICK): vol.All(cv.ensure_list, [automation.validate_automation({ + }), + vol.Optional(CONF_ON_CLICK): automation.validate_automation({ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(ClickTrigger), vol.Optional(CONF_MIN_LENGTH, default='50ms'): cv.positive_time_period_milliseconds, vol.Optional(CONF_MAX_LENGTH, default='350ms'): cv.positive_time_period_milliseconds, - })]), - vol.Optional(CONF_ON_DOUBLE_CLICK): - vol.All(cv.ensure_list, [automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(DoubleClickTrigger), - vol.Optional(CONF_MIN_LENGTH, default='50ms'): cv.positive_time_period_milliseconds, - vol.Optional(CONF_MAX_LENGTH, default='350ms'): cv.positive_time_period_milliseconds, - })]), + }), + vol.Optional(CONF_ON_DOUBLE_CLICK): automation.validate_automation({ + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(DoubleClickTrigger), + vol.Optional(CONF_MIN_LENGTH, default='50ms'): cv.positive_time_period_milliseconds, + vol.Optional(CONF_MAX_LENGTH, default='350ms'): cv.positive_time_period_milliseconds, + }), vol.Optional(CONF_INVERTED): cv.invalid( "The inverted binary_sensor property has been replaced by the " diff --git a/esphomeyaml/components/cover/template.py b/esphomeyaml/components/cover/template.py index c6a85afc81..df9c0f30b5 100644 --- a/esphomeyaml/components/cover/template.py +++ b/esphomeyaml/components/cover/template.py @@ -13,9 +13,9 @@ PLATFORM_SCHEMA = cv.nameable(cover.COVER_PLATFORM_SCHEMA.extend({ cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(MakeTemplateCover), vol.Optional(CONF_LAMBDA): cv.lambda_, vol.Optional(CONF_OPTIMISTIC): cv.boolean, - vol.Optional(CONF_OPEN_ACTION): automation.validate_automation(), - vol.Optional(CONF_CLOSE_ACTION): automation.validate_automation(), - vol.Optional(CONF_STOP_ACTION): automation.validate_automation(), + 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), }), cv.has_at_least_one_key(CONF_LAMBDA, CONF_OPTIMISTIC)) diff --git a/esphomeyaml/components/mqtt.py b/esphomeyaml/components/mqtt.py index 98d43f6137..143f0aa4ad 100644 --- a/esphomeyaml/components/mqtt.py +++ b/esphomeyaml/components/mqtt.py @@ -2,8 +2,8 @@ import re import voluptuous as vol -from esphomeyaml.automation import ACTION_REGISTRY from esphomeyaml import automation +from esphomeyaml.automation import ACTION_REGISTRY from esphomeyaml.components import logger import esphomeyaml.config_validation as cv from esphomeyaml.const import CONF_BIRTH_MESSAGE, CONF_BROKER, CONF_CLIENT_ID, CONF_DISCOVERY, \ @@ -79,16 +79,16 @@ CONFIG_SCHEMA = vol.Schema({ cv.ensure_list, [validate_fingerprint]), vol.Optional(CONF_KEEPALIVE): cv.positive_time_period_seconds, vol.Optional(CONF_REBOOT_TIMEOUT): cv.positive_time_period_milliseconds, - vol.Optional(CONF_ON_MESSAGE): vol.All(cv.ensure_list, [automation.validate_automation({ + vol.Optional(CONF_ON_MESSAGE): automation.validate_automation({ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(MQTTMessageTrigger), vol.Required(CONF_TOPIC): cv.subscribe_topic, vol.Optional(CONF_QOS, default=0): cv.mqtt_qos, - })]), - vol.Optional(CONF_ON_JSON_MESSAGE): vol.All(cv.ensure_list, [automation.validate_automation({ + }), + vol.Optional(CONF_ON_JSON_MESSAGE): automation.validate_automation({ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(MQTTJsonMessageTrigger), vol.Required(CONF_TOPIC): cv.subscribe_topic, vol.Optional(CONF_QOS, default=0): cv.mqtt_qos, - })]), + }), }) diff --git a/esphomeyaml/components/pn532.py b/esphomeyaml/components/pn532.py index f59d08b161..6dda13b0c4 100644 --- a/esphomeyaml/components/pn532.py +++ b/esphomeyaml/components/pn532.py @@ -18,9 +18,9 @@ CONFIG_SCHEMA = vol.All(cv.ensure_list, [vol.Schema({ cv.GenerateID(CONF_SPI_ID): cv.use_variable_id(SPIComponent), vol.Required(CONF_CS_PIN): pins.gpio_output_pin_schema, vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval, - vol.Optional(CONF_ON_TAG): vol.All(cv.ensure_list, [automation.validate_automation({ + vol.Optional(CONF_ON_TAG): automation.validate_automation({ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(PN532Trigger), - })]), + }), })]) diff --git a/esphomeyaml/components/sensor/__init__.py b/esphomeyaml/components/sensor/__init__.py index a89d7c8f43..52e8af33e8 100644 --- a/esphomeyaml/components/sensor/__init__.py +++ b/esphomeyaml/components/sensor/__init__.py @@ -76,18 +76,17 @@ SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({ vol.Optional(CONF_ACCURACY_DECIMALS): vol.Coerce(int), vol.Optional(CONF_EXPIRE_AFTER): vol.Any(None, cv.positive_time_period_milliseconds), vol.Optional(CONF_FILTERS): FILTERS_SCHEMA, - vol.Optional(CONF_ON_VALUE): vol.All(cv.ensure_list, [automation.validate_automation({ + vol.Optional(CONF_ON_VALUE): automation.validate_automation({ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(SensorValueTrigger), - })]), - vol.Optional(CONF_ON_RAW_VALUE): vol.All(cv.ensure_list, [automation.validate_automation({ + }), + vol.Optional(CONF_ON_RAW_VALUE): automation.validate_automation({ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(RawSensorValueTrigger), - })]), - vol.Optional(CONF_ON_VALUE_RANGE): vol.All(cv.ensure_list, [vol.All( - automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(ValueRangeTrigger), - vol.Optional(CONF_ABOVE): vol.Coerce(float), - vol.Optional(CONF_BELOW): vol.Coerce(float), - }), cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW))]), + }), + vol.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation({ + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(ValueRangeTrigger), + vol.Optional(CONF_ABOVE): vol.Coerce(float), + vol.Optional(CONF_BELOW): vol.Coerce(float), + }, cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW)), }) SENSOR_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(SENSOR_SCHEMA.schema) diff --git a/esphomeyaml/components/switch/template.py b/esphomeyaml/components/switch/template.py index 4f33dbb10c..a8f110bb6f 100644 --- a/esphomeyaml/components/switch/template.py +++ b/esphomeyaml/components/switch/template.py @@ -14,8 +14,8 @@ PLATFORM_SCHEMA = cv.nameable(switch.SWITCH_PLATFORM_SCHEMA.extend({ cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(MakeTemplateSwitch), vol.Optional(CONF_LAMBDA): cv.lambda_, vol.Optional(CONF_OPTIMISTIC): cv.boolean, - vol.Optional(CONF_TURN_OFF_ACTION): automation.validate_automation(), - vol.Optional(CONF_TURN_ON_ACTION): automation.validate_automation(), + vol.Optional(CONF_TURN_OFF_ACTION): automation.validate_automation(single=True), + vol.Optional(CONF_TURN_ON_ACTION): automation.validate_automation(single=True), }), cv.has_at_least_one_key(CONF_LAMBDA, CONF_OPTIMISTIC)) diff --git a/esphomeyaml/components/text_sensor/__init__.py b/esphomeyaml/components/text_sensor/__init__.py index f4d57e64e4..bae3bdca4d 100644 --- a/esphomeyaml/components/text_sensor/__init__.py +++ b/esphomeyaml/components/text_sensor/__init__.py @@ -22,9 +22,9 @@ TEXT_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({ cv.GenerateID(CONF_MQTT_ID): cv.declare_variable_id(MQTTTextSensor), cv.GenerateID(): cv.declare_variable_id(TextSensor), vol.Optional(CONF_ICON): cv.icon, - vol.Optional(CONF_ON_VALUE): vol.All(cv.ensure_list, [automation.validate_automation({ + vol.Optional(CONF_ON_VALUE): automation.validate_automation({ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(TextSensorValueTrigger), - })]), + }), }) TEXT_SENSOR_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(TEXT_SENSOR_SCHEMA.schema) diff --git a/esphomeyaml/components/time/__init__.py b/esphomeyaml/components/time/__init__.py index 89a14ea359..375c4f6eff 100644 --- a/esphomeyaml/components/time/__init__.py +++ b/esphomeyaml/components/time/__init__.py @@ -235,7 +235,7 @@ def validate_cron_keys(value): TIME_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_TIMEZONE, default=detect_tz): cv.string, - vol.Optional(CONF_ON_TIME): vol.All(cv.ensure_list, [vol.All(automation.validate_automation({ + vol.Optional(CONF_ON_TIME): automation.validate_automation({ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(CronTrigger), vol.Optional(CONF_SECONDS): validate_cron_seconds, vol.Optional(CONF_MINUTES): validate_cron_minutes, @@ -244,7 +244,7 @@ TIME_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_MONTHS): validate_cron_months, vol.Optional(CONF_DAYS_OF_WEEK): validate_cron_days_of_week, vol.Optional(CONF_CRON): validate_cron_raw, - }), validate_cron_keys)]), + }, validate_cron_keys), }) diff --git a/esphomeyaml/core_config.py b/esphomeyaml/core_config.py index cea1e48600..b236d7cb2b 100644 --- a/esphomeyaml/core_config.py +++ b/esphomeyaml/core_config.py @@ -5,17 +5,17 @@ import subprocess import voluptuous as vol -import esphomeyaml.config_validation as cv from esphomeyaml import automation, core, pins -from esphomeyaml.const import CONF_ARDUINO_VERSION, CONF_BOARD, CONF_BOARD_FLASH_MODE, \ - CONF_BRANCH, CONF_BUILD_PATH, CONF_COMMIT, CONF_ESPHOMELIB_VERSION, CONF_ESPHOMEYAML, \ - CONF_LOCAL, CONF_NAME, CONF_ON_BOOT, CONF_ON_LOOP, CONF_ON_SHUTDOWN, CONF_PLATFORM, \ - CONF_PRIORITY, CONF_REPOSITORY, CONF_TAG, CONF_TRIGGER_ID, CONF_USE_CUSTOM_CODE, \ - ESPHOMELIB_VERSION, ESP_PLATFORM_ESP32, ESP_PLATFORM_ESP8266, ARDUINO_VERSION_ESP8266_DEV, \ - ARDUINO_VERSION_ESP32_DEV +import esphomeyaml.config_validation as cv +from esphomeyaml.const import ARDUINO_VERSION_ESP32_DEV, ARDUINO_VERSION_ESP8266_DEV, \ + CONF_ARDUINO_VERSION, CONF_BOARD, CONF_BOARD_FLASH_MODE, CONF_BRANCH, CONF_BUILD_PATH, \ + CONF_COMMIT, CONF_ESPHOMELIB_VERSION, CONF_ESPHOMEYAML, CONF_LOCAL, CONF_NAME, CONF_ON_BOOT, \ + CONF_ON_LOOP, CONF_ON_SHUTDOWN, CONF_PLATFORM, CONF_PRIORITY, CONF_REPOSITORY, CONF_TAG, \ + CONF_TRIGGER_ID, CONF_USE_CUSTOM_CODE, ESPHOMELIB_VERSION, ESP_PLATFORM_ESP32, \ + ESP_PLATFORM_ESP8266 from esphomeyaml.core import ESPHomeYAMLError -from esphomeyaml.helpers import App, NoArg, Pvariable, add, const_char_p, esphomelib_ns, \ - relative_path, RawExpression +from esphomeyaml.helpers import App, NoArg, Pvariable, RawExpression, add, const_char_p, \ + esphomelib_ns, relative_path _LOGGER = logging.getLogger(__name__) @@ -164,16 +164,16 @@ CONFIG_SCHEMA = vol.Schema({ vol.Optional(CONF_BUILD_PATH, default=default_build_path): cv.string, vol.Optional(CONF_BOARD_FLASH_MODE): vol.All(vol.Lower, cv.one_of(*BUILD_FLASH_MODES)), - vol.Optional(CONF_ON_BOOT): vol.All(cv.ensure_list, [automation.validate_automation({ + vol.Optional(CONF_ON_BOOT): automation.validate_automation({ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(StartupTrigger), vol.Optional(CONF_PRIORITY): vol.Coerce(float), - })]), - vol.Optional(CONF_ON_SHUTDOWN): vol.All(cv.ensure_list, [automation.validate_automation({ + }), + vol.Optional(CONF_ON_SHUTDOWN): automation.validate_automation({ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(ShutdownTrigger), - })]), - vol.Optional(CONF_ON_LOOP): vol.All(cv.ensure_list, [automation.validate_automation({ + }), + vol.Optional(CONF_ON_LOOP): automation.validate_automation({ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(LoopTrigger), - })]), + }), vol.Optional('library_uri'): cv.invalid("The library_uri option has been removed in 1.8.0 and " "was moved into the esphomelib_version option."), diff --git a/esphomeyaml/helpers.py b/esphomeyaml/helpers.py index b28eca3115..3f517fdce2 100644 --- a/esphomeyaml/helpers.py +++ b/esphomeyaml/helpers.py @@ -232,12 +232,13 @@ class LambdaExpression(Expression): cpp = u'[{}]({})'.format(self.capture, self.parameters) if self.return_type is not None: cpp += u' -> {}'.format(self.return_type) - cpp += u' {\n' - for part in self.parts: - cpp += unicode(part) - cpp += u'\n}' + cpp += u' {{\n{}\n}}'.format(self.content) return indent_all_but_first_and_last(cpp) + @property + def content(self): + return u''.join(unicode(part) for part in self.parts) + class Literal(Expression): def __str__(self): @@ -422,7 +423,6 @@ def process_lambda(value, parameters, capture='=', return_type=None): parts[i * 3 + 1] = var parts[i * 3 + 2] = '' yield LambdaExpression(parts, parameters, capture, return_type) - return def templatable(value, input_type, output_type):