Fix triggers being interpreted as a sequence of automations (#199)

This commit is contained in:
Otto Winter 2018-10-20 13:15:09 +02:00 committed by GitHub
parent 92b6ed4180
commit 4aeb756388
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 78 additions and 64 deletions

View file

@ -78,17 +78,33 @@ RangeCondition = esphomelib_ns.RangeCondition
LambdaCondition = esphomelib_ns.LambdaCondition 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 {}) schema = AUTOMATION_SCHEMA.extend(extra_schema or {})
def validator(value): def validator_(value):
if isinstance(value, list): 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): elif isinstance(value, dict):
if CONF_THEN in value: if CONF_THEN in value:
return schema(value) return [schema(value)]
return schema({CONF_THEN: value}) return [schema({CONF_THEN: value})]
return schema(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 return validator

View file

@ -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_DEVICE_CLASS): vol.All(vol.Lower, cv.one_of(*DEVICE_CLASSES)),
vol.Optional(CONF_FILTERS): FILTERS_SCHEMA, 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), 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), 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), 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_MIN_LENGTH, default='50ms'): cv.positive_time_period_milliseconds,
vol.Optional(CONF_MAX_LENGTH, default='350ms'): cv.positive_time_period_milliseconds, vol.Optional(CONF_MAX_LENGTH, default='350ms'): cv.positive_time_period_milliseconds,
})]), }),
vol.Optional(CONF_ON_DOUBLE_CLICK): vol.Optional(CONF_ON_DOUBLE_CLICK): automation.validate_automation({
vol.All(cv.ensure_list, [automation.validate_automation({ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(DoubleClickTrigger),
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_MIN_LENGTH, default='50ms'): cv.positive_time_period_milliseconds, vol.Optional(CONF_MAX_LENGTH, default='350ms'): cv.positive_time_period_milliseconds,
vol.Optional(CONF_MAX_LENGTH, default='350ms'): cv.positive_time_period_milliseconds, }),
})]),
vol.Optional(CONF_INVERTED): cv.invalid( vol.Optional(CONF_INVERTED): cv.invalid(
"The inverted binary_sensor property has been replaced by the " "The inverted binary_sensor property has been replaced by the "

View file

@ -13,9 +13,9 @@ PLATFORM_SCHEMA = cv.nameable(cover.COVER_PLATFORM_SCHEMA.extend({
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(MakeTemplateCover), cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(MakeTemplateCover),
vol.Optional(CONF_LAMBDA): cv.lambda_, vol.Optional(CONF_LAMBDA): cv.lambda_,
vol.Optional(CONF_OPTIMISTIC): cv.boolean, vol.Optional(CONF_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_OPEN_ACTION): automation.validate_automation(), vol.Optional(CONF_OPEN_ACTION): automation.validate_automation(single=True),
vol.Optional(CONF_CLOSE_ACTION): automation.validate_automation(), vol.Optional(CONF_CLOSE_ACTION): automation.validate_automation(single=True),
vol.Optional(CONF_STOP_ACTION): automation.validate_automation(), vol.Optional(CONF_STOP_ACTION): automation.validate_automation(single=True),
}), cv.has_at_least_one_key(CONF_LAMBDA, CONF_OPTIMISTIC)) }), cv.has_at_least_one_key(CONF_LAMBDA, CONF_OPTIMISTIC))

View file

@ -2,8 +2,8 @@ import re
import voluptuous as vol import voluptuous as vol
from esphomeyaml.automation import ACTION_REGISTRY
from esphomeyaml import automation from esphomeyaml import automation
from esphomeyaml.automation import ACTION_REGISTRY
from esphomeyaml.components import logger from esphomeyaml.components import logger
import esphomeyaml.config_validation as cv import esphomeyaml.config_validation as cv
from esphomeyaml.const import CONF_BIRTH_MESSAGE, CONF_BROKER, CONF_CLIENT_ID, CONF_DISCOVERY, \ 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]), cv.ensure_list, [validate_fingerprint]),
vol.Optional(CONF_KEEPALIVE): cv.positive_time_period_seconds, vol.Optional(CONF_KEEPALIVE): cv.positive_time_period_seconds,
vol.Optional(CONF_REBOOT_TIMEOUT): cv.positive_time_period_milliseconds, 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), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(MQTTMessageTrigger),
vol.Required(CONF_TOPIC): cv.subscribe_topic, vol.Required(CONF_TOPIC): cv.subscribe_topic,
vol.Optional(CONF_QOS, default=0): cv.mqtt_qos, 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), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(MQTTJsonMessageTrigger),
vol.Required(CONF_TOPIC): cv.subscribe_topic, vol.Required(CONF_TOPIC): cv.subscribe_topic,
vol.Optional(CONF_QOS, default=0): cv.mqtt_qos, vol.Optional(CONF_QOS, default=0): cv.mqtt_qos,
})]), }),
}) })

View file

@ -18,9 +18,9 @@ CONFIG_SCHEMA = vol.All(cv.ensure_list, [vol.Schema({
cv.GenerateID(CONF_SPI_ID): cv.use_variable_id(SPIComponent), cv.GenerateID(CONF_SPI_ID): cv.use_variable_id(SPIComponent),
vol.Required(CONF_CS_PIN): pins.gpio_output_pin_schema, vol.Required(CONF_CS_PIN): pins.gpio_output_pin_schema,
vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval, 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), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(PN532Trigger),
})]), }),
})]) })])

View file

@ -76,18 +76,17 @@ SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({
vol.Optional(CONF_ACCURACY_DECIMALS): vol.Coerce(int), vol.Optional(CONF_ACCURACY_DECIMALS): vol.Coerce(int),
vol.Optional(CONF_EXPIRE_AFTER): vol.Any(None, cv.positive_time_period_milliseconds), vol.Optional(CONF_EXPIRE_AFTER): vol.Any(None, cv.positive_time_period_milliseconds),
vol.Optional(CONF_FILTERS): FILTERS_SCHEMA, 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), 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), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(RawSensorValueTrigger),
})]), }),
vol.Optional(CONF_ON_VALUE_RANGE): vol.All(cv.ensure_list, [vol.All( vol.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation({
automation.validate_automation({ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(ValueRangeTrigger),
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(ValueRangeTrigger), vol.Optional(CONF_ABOVE): vol.Coerce(float),
vol.Optional(CONF_ABOVE): vol.Coerce(float), vol.Optional(CONF_BELOW): vol.Coerce(float),
vol.Optional(CONF_BELOW): vol.Coerce(float), }, cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW)),
}), cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW))]),
}) })
SENSOR_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(SENSOR_SCHEMA.schema) SENSOR_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(SENSOR_SCHEMA.schema)

View file

@ -14,8 +14,8 @@ PLATFORM_SCHEMA = cv.nameable(switch.SWITCH_PLATFORM_SCHEMA.extend({
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(MakeTemplateSwitch), cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(MakeTemplateSwitch),
vol.Optional(CONF_LAMBDA): cv.lambda_, vol.Optional(CONF_LAMBDA): cv.lambda_,
vol.Optional(CONF_OPTIMISTIC): cv.boolean, vol.Optional(CONF_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_TURN_OFF_ACTION): automation.validate_automation(), vol.Optional(CONF_TURN_OFF_ACTION): automation.validate_automation(single=True),
vol.Optional(CONF_TURN_ON_ACTION): automation.validate_automation(), vol.Optional(CONF_TURN_ON_ACTION): automation.validate_automation(single=True),
}), cv.has_at_least_one_key(CONF_LAMBDA, CONF_OPTIMISTIC)) }), cv.has_at_least_one_key(CONF_LAMBDA, CONF_OPTIMISTIC))

View file

@ -22,9 +22,9 @@ TEXT_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({
cv.GenerateID(CONF_MQTT_ID): cv.declare_variable_id(MQTTTextSensor), cv.GenerateID(CONF_MQTT_ID): cv.declare_variable_id(MQTTTextSensor),
cv.GenerateID(): cv.declare_variable_id(TextSensor), cv.GenerateID(): cv.declare_variable_id(TextSensor),
vol.Optional(CONF_ICON): cv.icon, 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), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(TextSensorValueTrigger),
})]), }),
}) })
TEXT_SENSOR_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(TEXT_SENSOR_SCHEMA.schema) TEXT_SENSOR_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(TEXT_SENSOR_SCHEMA.schema)

View file

@ -235,7 +235,7 @@ def validate_cron_keys(value):
TIME_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ TIME_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_TIMEZONE, default=detect_tz): cv.string, 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), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(CronTrigger),
vol.Optional(CONF_SECONDS): validate_cron_seconds, vol.Optional(CONF_SECONDS): validate_cron_seconds,
vol.Optional(CONF_MINUTES): validate_cron_minutes, 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_MONTHS): validate_cron_months,
vol.Optional(CONF_DAYS_OF_WEEK): validate_cron_days_of_week, vol.Optional(CONF_DAYS_OF_WEEK): validate_cron_days_of_week,
vol.Optional(CONF_CRON): validate_cron_raw, vol.Optional(CONF_CRON): validate_cron_raw,
}), validate_cron_keys)]), }, validate_cron_keys),
}) })

View file

@ -5,17 +5,17 @@ import subprocess
import voluptuous as vol import voluptuous as vol
import esphomeyaml.config_validation as cv
from esphomeyaml import automation, core, pins from esphomeyaml import automation, core, pins
from esphomeyaml.const import CONF_ARDUINO_VERSION, CONF_BOARD, CONF_BOARD_FLASH_MODE, \ import esphomeyaml.config_validation as cv
CONF_BRANCH, CONF_BUILD_PATH, CONF_COMMIT, CONF_ESPHOMELIB_VERSION, CONF_ESPHOMEYAML, \ from esphomeyaml.const import ARDUINO_VERSION_ESP32_DEV, ARDUINO_VERSION_ESP8266_DEV, \
CONF_LOCAL, CONF_NAME, CONF_ON_BOOT, CONF_ON_LOOP, CONF_ON_SHUTDOWN, CONF_PLATFORM, \ CONF_ARDUINO_VERSION, CONF_BOARD, CONF_BOARD_FLASH_MODE, CONF_BRANCH, CONF_BUILD_PATH, \
CONF_PRIORITY, CONF_REPOSITORY, CONF_TAG, CONF_TRIGGER_ID, CONF_USE_CUSTOM_CODE, \ CONF_COMMIT, CONF_ESPHOMELIB_VERSION, CONF_ESPHOMEYAML, CONF_LOCAL, CONF_NAME, CONF_ON_BOOT, \
ESPHOMELIB_VERSION, ESP_PLATFORM_ESP32, ESP_PLATFORM_ESP8266, ARDUINO_VERSION_ESP8266_DEV, \ CONF_ON_LOOP, CONF_ON_SHUTDOWN, CONF_PLATFORM, CONF_PRIORITY, CONF_REPOSITORY, CONF_TAG, \
ARDUINO_VERSION_ESP32_DEV CONF_TRIGGER_ID, CONF_USE_CUSTOM_CODE, ESPHOMELIB_VERSION, ESP_PLATFORM_ESP32, \
ESP_PLATFORM_ESP8266
from esphomeyaml.core import ESPHomeYAMLError from esphomeyaml.core import ESPHomeYAMLError
from esphomeyaml.helpers import App, NoArg, Pvariable, add, const_char_p, esphomelib_ns, \ from esphomeyaml.helpers import App, NoArg, Pvariable, RawExpression, add, const_char_p, \
relative_path, RawExpression esphomelib_ns, relative_path
_LOGGER = logging.getLogger(__name__) _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_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_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), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(StartupTrigger),
vol.Optional(CONF_PRIORITY): vol.Coerce(float), 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), 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), 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 " 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."), "was moved into the esphomelib_version option."),

View file

@ -232,12 +232,13 @@ class LambdaExpression(Expression):
cpp = u'[{}]({})'.format(self.capture, self.parameters) cpp = u'[{}]({})'.format(self.capture, self.parameters)
if self.return_type is not None: if self.return_type is not None:
cpp += u' -> {}'.format(self.return_type) cpp += u' -> {}'.format(self.return_type)
cpp += u' {\n' cpp += u' {{\n{}\n}}'.format(self.content)
for part in self.parts:
cpp += unicode(part)
cpp += u'\n}'
return indent_all_but_first_and_last(cpp) 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): class Literal(Expression):
def __str__(self): def __str__(self):
@ -422,7 +423,6 @@ def process_lambda(value, parameters, capture='=', return_type=None):
parts[i * 3 + 1] = var parts[i * 3 + 1] = var
parts[i * 3 + 2] = '' parts[i * 3 + 2] = ''
yield LambdaExpression(parts, parameters, capture, return_type) yield LambdaExpression(parts, parameters, capture, return_type)
return
def templatable(value, input_type, output_type): def templatable(value, input_type, output_type):