From 8bbfbc4cc144ae35e37e4f53493034f834779244 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 20 Oct 2018 15:19:59 +0200 Subject: [PATCH] Add logger.log action (#198) * Add logger.log Action * Simple schema * Validate printf * Improve error message * Undo unfix tests :) --- esphomeyaml/automation.py | 7 ++- esphomeyaml/components/logger.py | 75 ++++++++++++++++++++++++++++++-- esphomeyaml/const.py | 2 + tests/test1.yaml | 5 +++ 4 files changed, 83 insertions(+), 6 deletions(-) diff --git a/esphomeyaml/automation.py b/esphomeyaml/automation.py index b4b568d049..ee3a8113dc 100644 --- a/esphomeyaml/automation.py +++ b/esphomeyaml/automation.py @@ -86,9 +86,12 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False): try: # First try as a sequence of actions return [schema({CONF_THEN: value})] - except vol.Invalid: + except vol.Invalid as err: # Next try as a sequence of automations - return vol.Schema([schema])(value) + try: + return vol.Schema([schema])(value) + except vol.Invalid as err2: + raise vol.MultipleInvalid([err, err2]) elif isinstance(value, dict): if CONF_THEN in value: return [schema(value)] diff --git a/esphomeyaml/components/logger.py b/esphomeyaml/components/logger.py index 46debd66cf..996732df4c 100644 --- a/esphomeyaml/components/logger.py +++ b/esphomeyaml/components/logger.py @@ -1,10 +1,14 @@ +import re + import voluptuous as vol +from esphomeyaml.automation import ACTION_REGISTRY, LambdaAction import esphomeyaml.config_validation as cv -from esphomeyaml.const import CONF_BAUD_RATE, CONF_ID, CONF_LEVEL, CONF_LOGS, \ - CONF_TX_BUFFER_SIZE -from esphomeyaml.core import ESPHomeYAMLError -from esphomeyaml.helpers import App, Pvariable, add, esphomelib_ns, global_ns +from esphomeyaml.const import CONF_ARGS, CONF_BAUD_RATE, CONF_FORMAT, CONF_ID, CONF_LEVEL, \ + CONF_LOGS, CONF_TAG, CONF_TX_BUFFER_SIZE +from esphomeyaml.core import ESPHomeYAMLError, Lambda +from esphomeyaml.helpers import App, Pvariable, TemplateArguments, add, esphomelib_ns, global_ns, \ + process_lambda, RawExpression, statement LOG_LEVELS = { 'NONE': global_ns.ESPHOMELIB_LOG_LEVEL_NONE, @@ -16,6 +20,15 @@ LOG_LEVELS = { 'VERY_VERBOSE': global_ns.ESPHOMELIB_LOG_LEVEL_VERY_VERBOSE, } +LOG_LEVEL_TO_ESP_LOG = { + 'ERROR': global_ns.ESP_LOGE, + 'WARN': global_ns.ESP_LOGW, + 'INFO': global_ns.ESP_LOGI, + 'DEBUG': global_ns.ESP_LOGD, + 'VERBOSE': global_ns.ESP_LOGV, + 'VERY_VERBOSE': global_ns.ESP_LOGVV, +} + LOG_LEVEL_SEVERITY = ['NONE', 'ERROR', 'WARN', 'INFO', 'DEBUG', 'VERBOSE', 'VERY_VERBOSE'] # pylint: disable=invalid-name @@ -59,3 +72,57 @@ def required_build_flags(config): if CONF_LEVEL in config: return u'-DESPHOMELIB_LOG_LEVEL={}'.format(str(LOG_LEVELS[config[CONF_LEVEL]])) return None + + +def maybe_simple_message(schema): + def validator(value): + if isinstance(value, dict): + return vol.Schema(schema)(value) + return vol.Schema(schema)({CONF_FORMAT: value}) + return validator + + +def validate_printf(value): + # https://stackoverflow.com/questions/30011379/how-can-i-parse-a-c-format-string-in-python + # pylint: disable=anomalous-backslash-in-string + cfmt = u"""\ + ( # start of capture group 1 + % # literal "%" + (?: # first option + (?:[-+0 #]{0,5}) # optional flags + (?:\d+|\*)? # width + (?:\.(?:\d+|\*))? # precision + (?:h|l|ll|w|I|I32|I64)? # size + [cCdiouxXeEfgGaAnpsSZ] # type + ) | # OR + %%) # literal "%%" + """ + matches = re.findall(cfmt, value[CONF_FORMAT], flags=re.X) + if len(matches) != len(value[CONF_ARGS]): + raise vol.Invalid(u"Found {} printf-patterns ({}), but {} args were given!" + u"".format(len(matches), u', '.join(matches), len(value[CONF_ARGS]))) + return value + + +CONF_LOGGER_LOG = 'logger.log' +LOGGER_LOG_ACTION_SCHEMA = vol.All(maybe_simple_message({ + vol.Required(CONF_FORMAT): cv.string, + vol.Optional(CONF_ARGS, default=list): vol.All(cv.ensure_list, [cv.lambda_]), + vol.Optional(CONF_LEVEL, default="DEBUG"): vol.All(vol.Upper, cv.one_of(*LOG_LEVEL_TO_ESP_LOG)), + vol.Optional(CONF_TAG, default="main"): cv.string, +}), validate_printf) + + +@ACTION_REGISTRY.register(CONF_LOGGER_LOG, LOGGER_LOG_ACTION_SCHEMA) +def logger_log_action_to_code(config, action_id, arg_type): + template_arg = TemplateArguments(arg_type) + esp_log = LOG_LEVEL_TO_ESP_LOG[config[CONF_LEVEL]] + args = [RawExpression(unicode(x)) for x in config[CONF_ARGS]] + + text = unicode(statement(esp_log(config[CONF_TAG], config[CONF_FORMAT], *args))) + + for lambda_ in process_lambda(Lambda(text), [(arg_type, 'x')]): + yield None + rhs = LambdaAction.new(template_arg, lambda_) + type = LambdaAction.template(template_arg) + yield Pvariable(action_id, rhs, type=type) diff --git a/esphomeyaml/const.py b/esphomeyaml/const.py index f67061ef24..b7b7c9701a 100644 --- a/esphomeyaml/const.py +++ b/esphomeyaml/const.py @@ -346,6 +346,8 @@ CONF_PM_2_5 = 'pm_2_5' CONF_PM_10_0 = 'pm_10_0' CONF_FORMALDEHYDE = 'formaldehyde' CONF_ON_TAG = 'on_tag' +CONF_ARGS = 'args' +CONF_FORMAT = 'format' CONF_COLOR_CORRECT = 'color_correct' CONF_ON_JSON_MESSAGE = 'on_json_message' diff --git a/tests/test1.yaml b/tests/test1.yaml index e7a3bb83e7..6961bd2598 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -193,6 +193,11 @@ sensor: on_raw_value: - lambda: >- ESP_LOGD("main", "Got raw value %f", x); + - logger.log: + level: DEBUG + format: "Got raw value %f" + args: ['x'] + - logger.log: "Got raw value NAN" - mqtt.publish: topic: some/topic payload: Hello