From a14d12b0c12a0d3a9981fe7cc70150ecd104ca0c Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 8 Apr 2019 21:57:25 +0200 Subject: [PATCH] Add 'at' time trigger (#493) ## Description: **Related issue (if applicable):** fixes **Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** esphome/esphome-docs# **Pull request in [esphome-core](https://github.com/esphome/esphome-core) with C++ framework changes (if applicable):** esphome/esphome-core# ## Checklist: - [ ] The code change is tested and works locally. - [ ] Tests have been added to verify that the new code works (under `tests/` folder). If user exposed functionality or configuration variables are added/changed: - [ ] Documentation added/updated in [esphomedocs](https://github.com/OttoWinter/esphomedocs). --- esphome/__main__.py | 14 +++++++++++--- esphome/components/climate/__init__.py | 2 +- esphome/components/time/__init__.py | 26 +++++++++++++++++++++++++- esphome/config_validation.py | 20 +++++++++++++++++++- esphome/const.py | 4 ++++ 5 files changed, 60 insertions(+), 6 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index c32b9eaee9..ff97961811 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -7,13 +7,11 @@ import os import random import sys -from esphome import const, core_config, mqtt, platformio_api, wizard, writer, yaml_util -from esphome.api.client import run_logs +from esphome import const, core_config, platformio_api, wizard, writer, yaml_util from esphome.config import get_component, iter_components, read_config, strip_default_ids from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_ESPHOME, CONF_LOGGER, \ CONF_USE_CUSTOM_CODE from esphome.core import CORE, EsphomeError -from esphome.cpp_generator import Expression, RawStatement, add, statement from esphome.helpers import color, indent from esphome.py_compat import IS_PY2, safe_input, text_type from esphome.storage_json import StorageJSON, storage_path @@ -124,6 +122,8 @@ def run_miniterm(config, port): def write_cpp(config): + from esphome.cpp_generator import Expression, RawStatement, add, statement + _LOGGER.info("Generating C++ source...") CORE.add_job(core_config.to_code, config[CONF_ESPHOME], domain='esphome') @@ -223,14 +223,20 @@ def show_logs(config, args, port): run_miniterm(config, port) return 0 if get_port_type(port) == 'NETWORK' and 'api' in config: + from esphome.api.client import run_logs + return run_logs(config, port) if get_port_type(port) == 'MQTT' and 'mqtt' in config: + from esphome import mqtt + return mqtt.show_logs(config, args.topic, args.username, args.password, args.client_id) raise ValueError def clean_mqtt(config, args): + from esphome import mqtt + return mqtt.clear_topic(config, args.topic, args.username, args.password, args.client_id) @@ -329,6 +335,8 @@ def command_clean_mqtt(args, config): def command_mqtt_fingerprint(args, config): + from esphome import mqtt + return mqtt.get_fingerprint(config) diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index 193f872dea..cf734d82fd 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -86,8 +86,8 @@ CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema({ def climate_control_to_code(config, action_id, template_arg, args): for var in get_variable(config[CONF_ID]): yield None - rhs = var.make_control_action(template_arg) type = ControlAction.template(template_arg) + rhs = type.new(var) action = Pvariable(action_id, rhs, type=type) if CONF_MODE in config: if isinstance(config[CONF_MODE], core.Lambda): diff --git a/esphome/components/time/__init__.py b/esphome/components/time/__init__.py index 4c5f807003..30b1e91a8c 100644 --- a/esphome/components/time/__init__.py +++ b/esphome/components/time/__init__.py @@ -7,7 +7,8 @@ import voluptuous as vol from esphome import automation import esphome.config_validation as cv from esphome.const import CONF_CRON, CONF_DAYS_OF_MONTH, CONF_DAYS_OF_WEEK, CONF_HOURS, \ - CONF_MINUTES, CONF_MONTHS, CONF_ON_TIME, CONF_SECONDS, CONF_TIMEZONE, CONF_TRIGGER_ID + CONF_MINUTES, CONF_MONTHS, CONF_ON_TIME, CONF_SECONDS, CONF_TIMEZONE, CONF_TRIGGER_ID, \ + CONF_AT, CONF_SECOND, CONF_HOUR, CONF_MINUTE from esphome.core import CORE from esphome.cpp_generator import Pvariable, add from esphome.cpp_types import App, Component, Trigger, esphome_ns @@ -232,15 +233,37 @@ def validate_cron_raw(value): } +def validate_time_at(value): + value = cv.time_of_day(value) + return { + CONF_HOURS: [value[CONF_HOUR]], + CONF_MINUTES: [value[CONF_MINUTE]], + CONF_SECONDS: [value[CONF_SECOND]], + CONF_DAYS_OF_MONTH: validate_cron_days_of_month('*'), + CONF_MONTHS: validate_cron_months('*'), + CONF_DAYS_OF_WEEK: validate_cron_days_of_week('*'), + } + + def validate_cron_keys(value): if CONF_CRON in value: for key in value.keys(): if key in CRON_KEYS: raise vol.Invalid("Cannot use option {} when cron: is specified.".format(key)) + if CONF_AT in value: + raise vol.Invalid("Cannot use option at with cron!") cron_ = value[CONF_CRON] value = {x: value[x] for x in value if x != CONF_CRON} value.update(cron_) return value + elif CONF_AT in value: + for key in value.keys(): + if key in CRON_KEYS: + raise vol.Invalid("Cannot use option {} when at: is specified.".format(key)) + at_ = value[CONF_AT] + value = {x: value[x] for x in value if x != CONF_AT} + value.update(at_) + return value return cv.has_at_least_one_key(*CRON_KEYS)(value) @@ -266,6 +289,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, + vol.Optional(CONF_AT): validate_time_at, }, validate_cron_keys), }) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 67cd659803..24f6c05a48 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -2,6 +2,7 @@ """Helpers for config validation using voluptuous.""" from __future__ import print_function +from datetime import datetime import logging import os import re @@ -13,7 +14,7 @@ from esphome import core from esphome.const import CONF_AVAILABILITY, CONF_COMMAND_TOPIC, CONF_DISCOVERY, CONF_ID, \ CONF_INTERNAL, CONF_NAME, CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_PLATFORM, \ CONF_RETAIN, CONF_SETUP_PRIORITY, CONF_STATE_TOPIC, CONF_TOPIC, ESP_PLATFORM_ESP32, \ - ESP_PLATFORM_ESP8266 + ESP_PLATFORM_ESP8266, CONF_HOUR, CONF_MINUTE, CONF_SECOND from esphome.core import CORE, HexInt, IPAddress, Lambda, TimePeriod, TimePeriodMicroseconds, \ TimePeriodMilliseconds, TimePeriodSeconds, TimePeriodMinutes from esphome.py_compat import integer_types, string_types, text_type, IS_PY2 @@ -391,6 +392,23 @@ positive_not_null_time_period = vol.All(time_period, vol.Range(min=TimePeriod(), min_included=False)) +def time_of_day(value): + value = string(value) + try: + date = datetime.strptime(value, '%H:%M:%S') + except ValueError as err: + try: + date = datetime.strptime(value, '%H:%M:%S %p') + except ValueError: + raise vol.Invalid("Invalid time of day: {}".format(err)) + + return { + CONF_HOUR: date.hour, + CONF_MINUTE: date.minute, + CONF_SECOND: date.second, + } + + def mac_address(value): value = string_strict(value) parts = value.split(':') diff --git a/esphome/const.py b/esphome/const.py index f231166a58..840d4c53b2 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -361,8 +361,11 @@ CONF_HIDDEN = 'hidden' CONF_ON_LOOP = 'on_loop' CONF_ON_TIME = 'on_time' CONF_SECONDS = 'seconds' +CONF_SECOND = 'second' CONF_MINUTES = 'minutes' +CONF_MINUTE = 'minute' CONF_HOURS = 'hours' +CONF_HOUR = 'hour' CONF_DAYS_OF_MONTH = 'days_of_month' CONF_MONTHS = 'months' CONF_DAYS_OF_WEEK = 'days_of_week' @@ -389,6 +392,7 @@ CONF_DIR_PIN = 'dir_pin' CONF_SLEEP_PIN = 'sleep_pin' CONF_SEND_FIRST_AT = 'send_first_at' CONF_TIME_ID = 'time_id' +CONF_AT = 'at' CONF_RESTORE_STATE = 'restore_state' CONF_TIMING = 'timing' CONF_INVALID_COOLDOWN = 'invalid_cooldown'