mirror of
https://github.com/esphome/esphome.git
synced 2024-12-23 05:54:56 +01:00
Update
This commit is contained in:
parent
c75edc4880
commit
5e3bc4ed2b
32 changed files with 741 additions and 185 deletions
|
@ -16,6 +16,8 @@ from esphomeyaml.const import CONF_BAUD_RATE, CONF_DOMAIN, CONF_ESPHOMEYAML, \
|
||||||
from esphomeyaml.core import CORE, EsphomeyamlError
|
from esphomeyaml.core import CORE, EsphomeyamlError
|
||||||
from esphomeyaml.cpp_generator import Expression, RawStatement, add, statement
|
from esphomeyaml.cpp_generator import Expression, RawStatement, add, statement
|
||||||
from esphomeyaml.helpers import color, indent
|
from esphomeyaml.helpers import color, indent
|
||||||
|
from esphomeyaml.storage_json import StorageJSON, storage_path, start_update_check_thread, \
|
||||||
|
esphomeyaml_storage_path
|
||||||
from esphomeyaml.util import run_external_command, safe_print
|
from esphomeyaml.util import run_external_command, safe_print
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -125,7 +127,10 @@ def write_cpp(config):
|
||||||
|
|
||||||
def compile_program(args, config):
|
def compile_program(args, config):
|
||||||
_LOGGER.info("Compiling app...")
|
_LOGGER.info("Compiling app...")
|
||||||
return platformio_api.run_compile(config, args.verbose)
|
thread = start_update_check_thread(esphomeyaml_storage_path(CORE.config_dir))
|
||||||
|
rc = platformio_api.run_compile(config, args.verbose)
|
||||||
|
thread.join()
|
||||||
|
return rc
|
||||||
|
|
||||||
|
|
||||||
def get_upload_host(config):
|
def get_upload_host(config):
|
||||||
|
@ -179,9 +184,16 @@ def upload_program(config, args, port):
|
||||||
remote_port = ota.get_port(config)
|
remote_port = ota.get_port(config)
|
||||||
password = ota.get_auth(config)
|
password = ota.get_auth(config)
|
||||||
|
|
||||||
|
storage = StorageJSON.load(storage_path())
|
||||||
res = espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
|
res = espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
|
||||||
if res == 0:
|
if res == 0:
|
||||||
|
if storage is not None and storage.use_legacy_ota:
|
||||||
|
storage.use_legacy_ota = False
|
||||||
|
storage.save(storage_path())
|
||||||
return res
|
return res
|
||||||
|
if storage is not None and not storage.use_legacy_ota:
|
||||||
|
return res
|
||||||
|
|
||||||
_LOGGER.warn("OTA v2 method failed. Trying with legacy OTA...")
|
_LOGGER.warn("OTA v2 method failed. Trying with legacy OTA...")
|
||||||
return espota2.run_legacy_ota(verbose, host_port, host, remote_port, password,
|
return espota2.run_legacy_ota(verbose, host_port, host, remote_port, password,
|
||||||
CORE.firmware_bin)
|
CORE.firmware_bin)
|
||||||
|
|
|
@ -2,12 +2,11 @@ import copy
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from esphomeyaml import core
|
|
||||||
import esphomeyaml.config_validation as cv
|
import esphomeyaml.config_validation as cv
|
||||||
from esphomeyaml.const import CONF_ABOVE, CONF_ACTION_ID, CONF_AND, CONF_AUTOMATION_ID, \
|
from esphomeyaml.const import CONF_ABOVE, CONF_ACTION_ID, CONF_AND, CONF_AUTOMATION_ID, \
|
||||||
CONF_BELOW, CONF_CONDITION, CONF_CONDITION_ID, CONF_DELAY, CONF_ELSE, CONF_ID, CONF_IF, \
|
CONF_BELOW, CONF_CONDITION, CONF_CONDITION_ID, CONF_DELAY, CONF_ELSE, CONF_ID, CONF_IF, \
|
||||||
CONF_LAMBDA, CONF_OR, CONF_RANGE, CONF_THEN, CONF_TRIGGER_ID
|
CONF_LAMBDA, CONF_OR, CONF_RANGE, CONF_THEN, CONF_TRIGGER_ID, CONF_WHILE
|
||||||
from esphomeyaml.core import CORE, EsphomeyamlError
|
from esphomeyaml.core import CORE
|
||||||
from esphomeyaml.cpp_generator import ArrayInitializer, Pvariable, TemplateArguments, add, \
|
from esphomeyaml.cpp_generator import ArrayInitializer, Pvariable, TemplateArguments, add, \
|
||||||
get_variable, process_lambda, templatable
|
get_variable, process_lambda, templatable
|
||||||
from esphomeyaml.cpp_types import Action, App, Component, PollingComponent, Trigger, \
|
from esphomeyaml.cpp_types import Action, App, Component, PollingComponent, Trigger, \
|
||||||
|
@ -27,7 +26,29 @@ def maybe_simple_id(*validators):
|
||||||
|
|
||||||
|
|
||||||
def validate_recursive_condition(value):
|
def validate_recursive_condition(value):
|
||||||
return CONDITIONS_SCHEMA(value)
|
value = cv.ensure_list(value)[:]
|
||||||
|
for i, item in enumerate(value):
|
||||||
|
item = copy.deepcopy(item)
|
||||||
|
if not isinstance(item, dict):
|
||||||
|
raise vol.Invalid(u"Condition must consist of key-value mapping! Got {}".format(item))
|
||||||
|
key = next((x for x in item if x != CONF_CONDITION_ID), None)
|
||||||
|
if key is None:
|
||||||
|
raise vol.Invalid(u"Key missing from action! Got {}".format(item))
|
||||||
|
if key not in CONDITION_REGISTRY:
|
||||||
|
raise vol.Invalid(u"Unable to find condition with the name '{}', is the "
|
||||||
|
u"component loaded?".format(key))
|
||||||
|
item.setdefault(CONF_CONDITION_ID, None)
|
||||||
|
key2 = next((x for x in item if x != CONF_CONDITION_ID and x != key), None)
|
||||||
|
if key2 is not None:
|
||||||
|
raise vol.Invalid(u"Cannot have two conditions in one item. Key '{}' overrides '{}'! "
|
||||||
|
u"Did you forget to indent the block inside the condition?"
|
||||||
|
u"".format(key, key2))
|
||||||
|
validator = CONDITION_REGISTRY[key][0]
|
||||||
|
value[i] = {
|
||||||
|
CONF_CONDITION_ID: cv.declare_variable_id(Condition)(item[CONF_ACTION_ID]),
|
||||||
|
key: validator(item[key])
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
def validate_recursive_action(value):
|
def validate_recursive_action(value):
|
||||||
|
@ -46,7 +67,7 @@ def validate_recursive_action(value):
|
||||||
key2 = next((x for x in item if x != CONF_ACTION_ID and x != key), None)
|
key2 = next((x for x in item if x != CONF_ACTION_ID and x != key), None)
|
||||||
if key2 is not None:
|
if key2 is not None:
|
||||||
raise vol.Invalid(u"Cannot have two actions in one item. Key '{}' overrides '{}'! "
|
raise vol.Invalid(u"Cannot have two actions in one item. Key '{}' overrides '{}'! "
|
||||||
u"Did you forget to indent the action?"
|
u"Did you forget to indent the block inside the action?"
|
||||||
u"".format(key, key2))
|
u"".format(key, key2))
|
||||||
validator = ACTION_REGISTRY[key][0]
|
validator = ACTION_REGISTRY[key][0]
|
||||||
value[i] = {
|
value[i] = {
|
||||||
|
@ -57,11 +78,13 @@ def validate_recursive_action(value):
|
||||||
|
|
||||||
|
|
||||||
ACTION_REGISTRY = ServiceRegistry()
|
ACTION_REGISTRY = ServiceRegistry()
|
||||||
|
CONDITION_REGISTRY = ServiceRegistry()
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
DelayAction = esphomelib_ns.class_('DelayAction', Action, Component)
|
DelayAction = esphomelib_ns.class_('DelayAction', Action, Component)
|
||||||
LambdaAction = esphomelib_ns.class_('LambdaAction', Action)
|
LambdaAction = esphomelib_ns.class_('LambdaAction', Action)
|
||||||
IfAction = esphomelib_ns.class_('IfAction', Action)
|
IfAction = esphomelib_ns.class_('IfAction', Action)
|
||||||
|
WhileAction = esphomelib_ns.class_('WhileAction', Action)
|
||||||
UpdateComponentAction = esphomelib_ns.class_('UpdateComponentAction', Action)
|
UpdateComponentAction = esphomelib_ns.class_('UpdateComponentAction', Action)
|
||||||
Automation = esphomelib_ns.class_('Automation')
|
Automation = esphomelib_ns.class_('Automation')
|
||||||
|
|
||||||
|
@ -76,8 +99,8 @@ CONDITIONS_SCHEMA = vol.All(cv.ensure_list, [cv.templatable({
|
||||||
vol.Optional(CONF_AND): validate_recursive_condition,
|
vol.Optional(CONF_AND): validate_recursive_condition,
|
||||||
vol.Optional(CONF_OR): validate_recursive_condition,
|
vol.Optional(CONF_OR): validate_recursive_condition,
|
||||||
vol.Optional(CONF_RANGE): vol.All(vol.Schema({
|
vol.Optional(CONF_RANGE): vol.All(vol.Schema({
|
||||||
vol.Optional(CONF_ABOVE): vol.Coerce(float),
|
vol.Optional(CONF_ABOVE): cv.float_,
|
||||||
vol.Optional(CONF_BELOW): vol.Coerce(float),
|
vol.Optional(CONF_BELOW): cv.float_,
|
||||||
}), cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW)),
|
}), cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW)),
|
||||||
vol.Optional(CONF_LAMBDA): cv.lambda_,
|
vol.Optional(CONF_LAMBDA): cv.lambda_,
|
||||||
})])
|
})])
|
||||||
|
@ -122,60 +145,63 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
||||||
AUTOMATION_SCHEMA = vol.Schema({
|
AUTOMATION_SCHEMA = vol.Schema({
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(Trigger),
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(Trigger),
|
||||||
cv.GenerateID(CONF_AUTOMATION_ID): cv.declare_variable_id(Automation),
|
cv.GenerateID(CONF_AUTOMATION_ID): cv.declare_variable_id(Automation),
|
||||||
vol.Optional(CONF_IF): CONDITIONS_SCHEMA,
|
vol.Optional(CONF_IF): validate_recursive_condition,
|
||||||
vol.Required(CONF_THEN): validate_recursive_action,
|
vol.Required(CONF_THEN): validate_recursive_action,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
AND_CONDITION_SCHEMA = validate_recursive_condition
|
||||||
|
|
||||||
def build_condition(config, arg_type):
|
|
||||||
template_arg = TemplateArguments(arg_type)
|
@CONDITION_REGISTRY.register(CONF_AND, AND_CONDITION_SCHEMA)
|
||||||
if isinstance(config, core.Lambda):
|
def and_condition_to_code(config, condition_id, arg_type, template_arg):
|
||||||
for lambda_ in process_lambda(config, [(arg_type, 'x')]):
|
for conditions in build_conditions(config, arg_type):
|
||||||
yield
|
yield
|
||||||
yield LambdaCondition.new(template_arg, lambda_)
|
rhs = AndCondition.new(template_arg, conditions)
|
||||||
elif CONF_AND in config:
|
type = AndCondition.template(template_arg)
|
||||||
yield AndCondition.new(template_arg, build_conditions(config[CONF_AND], template_arg))
|
yield Pvariable(condition_id, rhs, type=type)
|
||||||
elif CONF_OR in config:
|
|
||||||
yield OrCondition.new(template_arg, build_conditions(config[CONF_OR], template_arg))
|
|
||||||
elif CONF_LAMBDA in config:
|
OR_CONDITION_SCHEMA = validate_recursive_condition
|
||||||
lambda_ = None
|
|
||||||
for lambda_ in process_lambda(config[CONF_LAMBDA], [(arg_type, 'x')]):
|
|
||||||
|
@CONDITION_REGISTRY.register(CONF_OR, OR_CONDITION_SCHEMA)
|
||||||
|
def or_condition_to_code(config, condition_id, arg_type, template_arg):
|
||||||
|
for conditions in build_conditions(config, arg_type):
|
||||||
yield
|
yield
|
||||||
yield LambdaCondition.new(template_arg, lambda_)
|
rhs = OrCondition.new(template_arg, conditions)
|
||||||
elif CONF_RANGE in config:
|
type = OrCondition.template(template_arg)
|
||||||
conf = config[CONF_RANGE]
|
yield Pvariable(condition_id, rhs, type=type)
|
||||||
rhs = RangeCondition.new(template_arg)
|
|
||||||
|
|
||||||
|
RANGE_CONDITION_SCHEMA = vol.All(vol.Schema({
|
||||||
|
vol.Optional(CONF_ABOVE): cv.templatable(cv.float_),
|
||||||
|
vol.Optional(CONF_BELOW): cv.templatable(cv.float_),
|
||||||
|
}), cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW))
|
||||||
|
|
||||||
|
|
||||||
|
@CONDITION_REGISTRY.register(CONF_RANGE, RANGE_CONDITION_SCHEMA)
|
||||||
|
def range_condition_to_code(config, condition_id, arg_type, template_arg):
|
||||||
|
for conditions in build_conditions(config, arg_type):
|
||||||
|
yield
|
||||||
|
rhs = RangeCondition.new(template_arg, conditions)
|
||||||
type = RangeCondition.template(template_arg)
|
type = RangeCondition.template(template_arg)
|
||||||
condition = Pvariable(config[CONF_CONDITION_ID], rhs, type=type)
|
condition = Pvariable(condition_id, rhs, type=type)
|
||||||
if CONF_ABOVE in conf:
|
if CONF_ABOVE in config:
|
||||||
for template_ in templatable(conf[CONF_ABOVE], arg_type, float_):
|
for template_ in templatable(config[CONF_ABOVE], arg_type, float_):
|
||||||
yield
|
yield
|
||||||
condition.set_min(template_)
|
condition.set_min(template_)
|
||||||
if CONF_BELOW in conf:
|
if CONF_BELOW in config:
|
||||||
for template_ in templatable(conf[CONF_BELOW], arg_type, float_):
|
for template_ in templatable(config[CONF_BELOW], arg_type, float_):
|
||||||
yield
|
yield
|
||||||
condition.set_max(template_)
|
condition.set_max(template_)
|
||||||
yield condition
|
yield condition
|
||||||
else:
|
|
||||||
raise EsphomeyamlError(u"Unsupported condition {}".format(config))
|
|
||||||
|
|
||||||
|
|
||||||
def build_conditions(config, arg_type):
|
|
||||||
conditions = []
|
|
||||||
for conf in config:
|
|
||||||
condition = None
|
|
||||||
for condition in build_condition(conf, arg_type):
|
|
||||||
yield None
|
|
||||||
conditions.append(condition)
|
|
||||||
yield ArrayInitializer(*conditions)
|
|
||||||
|
|
||||||
|
|
||||||
DELAY_ACTION_SCHEMA = cv.templatable(cv.positive_time_period_milliseconds)
|
DELAY_ACTION_SCHEMA = cv.templatable(cv.positive_time_period_milliseconds)
|
||||||
|
|
||||||
|
|
||||||
@ACTION_REGISTRY.register(CONF_DELAY, DELAY_ACTION_SCHEMA)
|
@ACTION_REGISTRY.register(CONF_DELAY, DELAY_ACTION_SCHEMA)
|
||||||
def delay_action_to_code(config, action_id, arg_type):
|
def delay_action_to_code(config, action_id, arg_type, template_arg):
|
||||||
template_arg = TemplateArguments(arg_type)
|
|
||||||
rhs = App.register_component(DelayAction.new(template_arg))
|
rhs = App.register_component(DelayAction.new(template_arg))
|
||||||
type = DelayAction.template(template_arg)
|
type = DelayAction.template(template_arg)
|
||||||
action = Pvariable(action_id, rhs, type=type)
|
action = Pvariable(action_id, rhs, type=type)
|
||||||
|
@ -193,8 +219,7 @@ IF_ACTION_SCHEMA = vol.All({
|
||||||
|
|
||||||
|
|
||||||
@ACTION_REGISTRY.register(CONF_IF, IF_ACTION_SCHEMA)
|
@ACTION_REGISTRY.register(CONF_IF, IF_ACTION_SCHEMA)
|
||||||
def if_action_to_code(config, action_id, arg_type):
|
def if_action_to_code(config, action_id, arg_type, template_arg):
|
||||||
template_arg = TemplateArguments(arg_type)
|
|
||||||
for conditions in build_conditions(config[CONF_CONDITION], arg_type):
|
for conditions in build_conditions(config[CONF_CONDITION], arg_type):
|
||||||
yield None
|
yield None
|
||||||
rhs = IfAction.new(template_arg, conditions)
|
rhs = IfAction.new(template_arg, conditions)
|
||||||
|
@ -211,12 +236,30 @@ def if_action_to_code(config, action_id, arg_type):
|
||||||
yield action
|
yield action
|
||||||
|
|
||||||
|
|
||||||
|
WHILE_ACTION_SCHEMA = vol.Schema({
|
||||||
|
vol.Required(CONF_CONDITION): validate_recursive_condition,
|
||||||
|
vol.Required(CONF_THEN): validate_recursive_action,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ACTION_REGISTRY.register(CONF_WHILE, WHILE_ACTION_SCHEMA)
|
||||||
|
def while_action_to_code(config, action_id, arg_type, template_arg):
|
||||||
|
for conditions in build_conditions(config[CONF_CONDITION], arg_type):
|
||||||
|
yield None
|
||||||
|
rhs = WhileAction.new(template_arg, conditions)
|
||||||
|
type = WhileAction.template(template_arg)
|
||||||
|
action = Pvariable(action_id, rhs, type=type)
|
||||||
|
for actions in build_actions(config[CONF_THEN], arg_type):
|
||||||
|
yield None
|
||||||
|
add(action.add_then(actions))
|
||||||
|
yield action
|
||||||
|
|
||||||
|
|
||||||
LAMBDA_ACTION_SCHEMA = cv.lambda_
|
LAMBDA_ACTION_SCHEMA = cv.lambda_
|
||||||
|
|
||||||
|
|
||||||
@ACTION_REGISTRY.register(CONF_LAMBDA, LAMBDA_ACTION_SCHEMA)
|
@ACTION_REGISTRY.register(CONF_LAMBDA, LAMBDA_ACTION_SCHEMA)
|
||||||
def lambda_action_to_code(config, action_id, arg_type):
|
def lambda_action_to_code(config, action_id, arg_type, template_arg):
|
||||||
template_arg = TemplateArguments(arg_type)
|
|
||||||
for lambda_ in process_lambda(config, [(arg_type, 'x')]):
|
for lambda_ in process_lambda(config, [(arg_type, 'x')]):
|
||||||
yield None
|
yield None
|
||||||
rhs = LambdaAction.new(template_arg, lambda_)
|
rhs = LambdaAction.new(template_arg, lambda_)
|
||||||
|
@ -224,6 +267,18 @@ def lambda_action_to_code(config, action_id, arg_type):
|
||||||
yield Pvariable(action_id, rhs, type=type)
|
yield Pvariable(action_id, rhs, type=type)
|
||||||
|
|
||||||
|
|
||||||
|
LAMBDA_CONDITION_SCHEMA = cv.lambda_
|
||||||
|
|
||||||
|
|
||||||
|
@CONDITION_REGISTRY.register(CONF_LAMBDA, LAMBDA_CONDITION_SCHEMA)
|
||||||
|
def lambda_condition_to_code(config, condition_id, arg_type, template_arg):
|
||||||
|
for lambda_ in process_lambda(config, [(arg_type, 'x')]):
|
||||||
|
yield
|
||||||
|
rhs = LambdaCondition.new(template_arg, lambda_)
|
||||||
|
type = LambdaAction.template(template_arg)
|
||||||
|
yield Pvariable(condition_id, rhs, type=type)
|
||||||
|
|
||||||
|
|
||||||
CONF_COMPONENT_UPDATE = 'component.update'
|
CONF_COMPONENT_UPDATE = 'component.update'
|
||||||
COMPONENT_UPDATE_ACTION_SCHEMA = maybe_simple_id({
|
COMPONENT_UPDATE_ACTION_SCHEMA = maybe_simple_id({
|
||||||
vol.Required(CONF_ID): cv.use_variable_id(PollingComponent),
|
vol.Required(CONF_ID): cv.use_variable_id(PollingComponent),
|
||||||
|
@ -231,8 +286,7 @@ COMPONENT_UPDATE_ACTION_SCHEMA = maybe_simple_id({
|
||||||
|
|
||||||
|
|
||||||
@ACTION_REGISTRY.register(CONF_COMPONENT_UPDATE, COMPONENT_UPDATE_ACTION_SCHEMA)
|
@ACTION_REGISTRY.register(CONF_COMPONENT_UPDATE, COMPONENT_UPDATE_ACTION_SCHEMA)
|
||||||
def component_update_action_to_code(config, action_id, arg_type):
|
def component_update_action_to_code(config, action_id, arg_type, template_arg):
|
||||||
template_arg = TemplateArguments(arg_type)
|
|
||||||
for var in get_variable(config[CONF_ID]):
|
for var in get_variable(config[CONF_ID]):
|
||||||
yield None
|
yield None
|
||||||
rhs = UpdateComponentAction.new(var)
|
rhs = UpdateComponentAction.new(var)
|
||||||
|
@ -245,7 +299,8 @@ def build_action(full_config, arg_type):
|
||||||
key, config = next((k, v) for k, v in full_config.items() if k in ACTION_REGISTRY)
|
key, config = next((k, v) for k, v in full_config.items() if k in ACTION_REGISTRY)
|
||||||
|
|
||||||
builder = ACTION_REGISTRY[key][1]
|
builder = ACTION_REGISTRY[key][1]
|
||||||
for result in builder(config, action_id, arg_type):
|
template_arg = TemplateArguments(arg_type)
|
||||||
|
for result in builder(config, action_id, arg_type, template_arg):
|
||||||
yield None
|
yield None
|
||||||
yield result
|
yield result
|
||||||
|
|
||||||
|
@ -260,6 +315,26 @@ def build_actions(config, arg_type):
|
||||||
yield ArrayInitializer(*actions, multiline=False)
|
yield ArrayInitializer(*actions, multiline=False)
|
||||||
|
|
||||||
|
|
||||||
|
def build_condition(full_config, arg_type):
|
||||||
|
action_id = full_config[CONF_ACTION_ID]
|
||||||
|
key, config = next((k, v) for k, v in full_config.items() if k in CONDITION_REGISTRY)
|
||||||
|
|
||||||
|
builder = CONDITION_REGISTRY[key][1]
|
||||||
|
template_arg = TemplateArguments(arg_type)
|
||||||
|
for result in builder(config, action_id, arg_type, template_arg):
|
||||||
|
yield None
|
||||||
|
yield result
|
||||||
|
|
||||||
|
|
||||||
|
def build_conditions(config, arg_type):
|
||||||
|
conditions = []
|
||||||
|
for conf in config:
|
||||||
|
for condition in build_condition(conf, arg_type):
|
||||||
|
yield None
|
||||||
|
conditions.append(condition)
|
||||||
|
yield ArrayInitializer(*conditions, multiline=False)
|
||||||
|
|
||||||
|
|
||||||
def build_automation_(trigger, arg_type, config):
|
def build_automation_(trigger, arg_type, config):
|
||||||
rhs = App.make_automation(TemplateArguments(arg_type), trigger)
|
rhs = App.make_automation(TemplateArguments(arg_type), trigger)
|
||||||
type = Automation.template(arg_type)
|
type = Automation.template(arg_type)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from esphomeyaml import automation, core
|
from esphomeyaml import automation, core
|
||||||
|
from esphomeyaml.automation import maybe_simple_id, CONDITION_REGISTRY, Condition
|
||||||
from esphomeyaml.components import mqtt
|
from esphomeyaml.components import mqtt
|
||||||
from esphomeyaml.components.mqtt import setup_mqtt_component
|
from esphomeyaml.components.mqtt import setup_mqtt_component
|
||||||
import esphomeyaml.config_validation as cv
|
import esphomeyaml.config_validation as cv
|
||||||
|
@ -11,7 +12,7 @@ from esphomeyaml.const import CONF_DELAYED_OFF, CONF_DELAYED_ON, CONF_DEVICE_CLA
|
||||||
CONF_TIMING, CONF_TRIGGER_ID
|
CONF_TIMING, CONF_TRIGGER_ID
|
||||||
from esphomeyaml.core import CORE
|
from esphomeyaml.core import CORE
|
||||||
from esphomeyaml.cpp_generator import process_lambda, ArrayInitializer, add, Pvariable, \
|
from esphomeyaml.cpp_generator import process_lambda, ArrayInitializer, add, Pvariable, \
|
||||||
StructInitializer
|
StructInitializer, get_variable
|
||||||
from esphomeyaml.cpp_types import esphomelib_ns, Nameable, Trigger, NoArg, Component, App, bool_
|
from esphomeyaml.cpp_types import esphomelib_ns, Nameable, Trigger, NoArg, Component, App, bool_
|
||||||
|
|
||||||
DEVICE_CLASSES = [
|
DEVICE_CLASSES = [
|
||||||
|
@ -38,6 +39,9 @@ DoubleClickTrigger = binary_sensor_ns.class_('DoubleClickTrigger', Trigger.templ
|
||||||
MultiClickTrigger = binary_sensor_ns.class_('MultiClickTrigger', Trigger.template(NoArg), Component)
|
MultiClickTrigger = binary_sensor_ns.class_('MultiClickTrigger', Trigger.template(NoArg), Component)
|
||||||
MultiClickTriggerEvent = binary_sensor_ns.struct('MultiClickTriggerEvent')
|
MultiClickTriggerEvent = binary_sensor_ns.struct('MultiClickTriggerEvent')
|
||||||
|
|
||||||
|
# Condition
|
||||||
|
BinarySensorCondition = binary_sensor_ns.class_('BinarySensorCondition', Condition)
|
||||||
|
|
||||||
# Filters
|
# Filters
|
||||||
Filter = binary_sensor_ns.class_('Filter')
|
Filter = binary_sensor_ns.class_('Filter')
|
||||||
DelayedOnFilter = binary_sensor_ns.class_('DelayedOnFilter', Filter, Component)
|
DelayedOnFilter = binary_sensor_ns.class_('DelayedOnFilter', Filter, Component)
|
||||||
|
@ -293,3 +297,33 @@ def core_to_hass_config(data, config):
|
||||||
|
|
||||||
|
|
||||||
BUILD_FLAGS = '-DUSE_BINARY_SENSOR'
|
BUILD_FLAGS = '-DUSE_BINARY_SENSOR'
|
||||||
|
|
||||||
|
|
||||||
|
CONF_BINARY_SENSOR_IS_ON = 'binary_sensor.is_on'
|
||||||
|
BINARY_SENSOR_IS_ON_CONDITION_SCHEMA = maybe_simple_id({
|
||||||
|
vol.Required(CONF_ID): cv.use_variable_id(BinarySensor),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@CONDITION_REGISTRY.register(CONF_BINARY_SENSOR_IS_ON, BINARY_SENSOR_IS_ON_CONDITION_SCHEMA)
|
||||||
|
def binary_sensor_is_on_to_code(config, condition_id, arg_type, template_arg):
|
||||||
|
for var in get_variable(config[CONF_ID]):
|
||||||
|
yield None
|
||||||
|
rhs = var.make_binary_sensor_is_on_condition(template_arg)
|
||||||
|
type = BinarySensorCondition.template(arg_type)
|
||||||
|
yield Pvariable(condition_id, rhs, type=type)
|
||||||
|
|
||||||
|
|
||||||
|
CONF_BINARY_SENSOR_IS_OFF = 'binary_sensor.is_off'
|
||||||
|
BINARY_SENSOR_IS_OFF_CONDITION_SCHEMA = maybe_simple_id({
|
||||||
|
vol.Required(CONF_ID): cv.use_variable_id(BinarySensor),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@CONDITION_REGISTRY.register(CONF_BINARY_SENSOR_IS_OFF, BINARY_SENSOR_IS_OFF_CONDITION_SCHEMA)
|
||||||
|
def binary_sensor_is_off_to_code(config, condition_id, arg_type, template_arg):
|
||||||
|
for var in get_variable(config[CONF_ID]):
|
||||||
|
yield None
|
||||||
|
rhs = var.make_binary_sensor_is_off_condition(template_arg)
|
||||||
|
type = BinarySensorCondition.template(arg_type)
|
||||||
|
yield Pvariable(condition_id, rhs, type=type)
|
||||||
|
|
|
@ -55,8 +55,7 @@ COVER_OPEN_ACTION_SCHEMA = maybe_simple_id({
|
||||||
|
|
||||||
|
|
||||||
@ACTION_REGISTRY.register(CONF_COVER_OPEN, COVER_OPEN_ACTION_SCHEMA)
|
@ACTION_REGISTRY.register(CONF_COVER_OPEN, COVER_OPEN_ACTION_SCHEMA)
|
||||||
def cover_open_to_code(config, action_id, arg_type):
|
def cover_open_to_code(config, action_id, arg_type, template_arg):
|
||||||
template_arg = TemplateArguments(arg_type)
|
|
||||||
for var in get_variable(config[CONF_ID]):
|
for var in get_variable(config[CONF_ID]):
|
||||||
yield None
|
yield None
|
||||||
rhs = var.make_open_action(template_arg)
|
rhs = var.make_open_action(template_arg)
|
||||||
|
@ -71,8 +70,7 @@ COVER_CLOSE_ACTION_SCHEMA = maybe_simple_id({
|
||||||
|
|
||||||
|
|
||||||
@ACTION_REGISTRY.register(CONF_COVER_CLOSE, COVER_CLOSE_ACTION_SCHEMA)
|
@ACTION_REGISTRY.register(CONF_COVER_CLOSE, COVER_CLOSE_ACTION_SCHEMA)
|
||||||
def cover_close_to_code(config, action_id, arg_type):
|
def cover_close_to_code(config, action_id, arg_type, template_arg):
|
||||||
template_arg = TemplateArguments(arg_type)
|
|
||||||
for var in get_variable(config[CONF_ID]):
|
for var in get_variable(config[CONF_ID]):
|
||||||
yield None
|
yield None
|
||||||
rhs = var.make_close_action(template_arg)
|
rhs = var.make_close_action(template_arg)
|
||||||
|
@ -87,8 +85,7 @@ COVER_STOP_ACTION_SCHEMA = maybe_simple_id({
|
||||||
|
|
||||||
|
|
||||||
@ACTION_REGISTRY.register(CONF_COVER_STOP, COVER_STOP_ACTION_SCHEMA)
|
@ACTION_REGISTRY.register(CONF_COVER_STOP, COVER_STOP_ACTION_SCHEMA)
|
||||||
def cover_stop_to_code(config, action_id, arg_type):
|
def cover_stop_to_code(config, action_id, arg_type, template_arg):
|
||||||
template_arg = TemplateArguments(arg_type)
|
|
||||||
for var in get_variable(config[CONF_ID]):
|
for var in get_variable(config[CONF_ID]):
|
||||||
yield None
|
yield None
|
||||||
rhs = var.make_stop_action(template_arg)
|
rhs = var.make_stop_action(template_arg)
|
||||||
|
|
|
@ -1,25 +1,34 @@
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
import esphomeyaml.config_validation as cv
|
import esphomeyaml.config_validation as cv
|
||||||
from esphomeyaml.const import CONF_ID, CONF_LAMBDA
|
from esphomeyaml.const import CONF_ID, CONF_LAMBDA, CONF_COMPONENTS
|
||||||
from esphomeyaml.cpp_generator import process_lambda, variable
|
from esphomeyaml.cpp_generator import process_lambda, variable
|
||||||
|
from esphomeyaml.cpp_helpers import setup_component
|
||||||
from esphomeyaml.cpp_types import Component, ComponentPtr, esphomelib_ns, std_vector
|
from esphomeyaml.cpp_types import Component, ComponentPtr, esphomelib_ns, std_vector
|
||||||
|
|
||||||
CustomComponentConstructor = esphomelib_ns.class_('CustomComponentConstructor')
|
CustomComponentConstructor = esphomelib_ns.class_('CustomComponentConstructor')
|
||||||
|
|
||||||
CUSTOM_COMPONENT_SCHEMA = vol.Schema({
|
CUSTOM_COMPONENT_SCHEMA = vol.Schema({
|
||||||
cv.GenerateID(): cv.declare_variable_id(Component),
|
cv.GenerateID(): cv.declare_variable_id(CustomComponentConstructor),
|
||||||
vol.Required(CONF_LAMBDA): cv.lambda_,
|
vol.Required(CONF_LAMBDA): cv.lambda_,
|
||||||
|
vol.Optional(CONF_COMPONENTS): vol.All(cv.ensure_list, [vol.Schema({
|
||||||
|
cv.GenerateID(): cv.declare_variable_id(Component)
|
||||||
|
}).extend(cv.COMPONENT_SCHEMA.schema)]),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = vol.All(cv.ensure_list, [CUSTOM_COMPONENT_SCHEMA])
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
for template_ in process_lambda(config[CONF_LAMBDA], [],
|
for conf in config:
|
||||||
|
for template_ in process_lambda(conf[CONF_LAMBDA], [],
|
||||||
return_type=std_vector.template(ComponentPtr)):
|
return_type=std_vector.template(ComponentPtr)):
|
||||||
yield
|
yield
|
||||||
|
|
||||||
rhs = CustomComponentConstructor(template_)
|
rhs = CustomComponentConstructor(template_)
|
||||||
variable(config[CONF_ID], rhs)
|
custom = variable(conf[CONF_ID], rhs)
|
||||||
|
for i, comp in enumerate(conf.get(CONF_COMPONENTS, [])):
|
||||||
|
setup_component(custom.get_component(i), comp)
|
||||||
|
|
||||||
|
|
||||||
BUILD_FLAGS = '-DUSE_CUSTOM_COMPONENT'
|
BUILD_FLAGS = '-DUSE_CUSTOM_COMPONENT'
|
||||||
|
|
|
@ -4,8 +4,7 @@ from esphomeyaml import config_validation as cv, pins
|
||||||
from esphomeyaml.automation import ACTION_REGISTRY, maybe_simple_id
|
from esphomeyaml.automation import ACTION_REGISTRY, maybe_simple_id
|
||||||
from esphomeyaml.const import CONF_ID, CONF_MODE, CONF_NUMBER, CONF_PINS, CONF_RUN_CYCLES, \
|
from esphomeyaml.const import CONF_ID, CONF_MODE, CONF_NUMBER, CONF_PINS, CONF_RUN_CYCLES, \
|
||||||
CONF_RUN_DURATION, CONF_SLEEP_DURATION, CONF_WAKEUP_PIN
|
CONF_RUN_DURATION, CONF_SLEEP_DURATION, CONF_WAKEUP_PIN
|
||||||
from esphomeyaml.cpp_generator import Pvariable, StructInitializer, TemplateArguments, add, \
|
from esphomeyaml.cpp_generator import Pvariable, StructInitializer, add, get_variable
|
||||||
get_variable
|
|
||||||
from esphomeyaml.cpp_helpers import gpio_input_pin_expression, setup_component
|
from esphomeyaml.cpp_helpers import gpio_input_pin_expression, setup_component
|
||||||
from esphomeyaml.cpp_types import Action, App, Component, esphomelib_ns, global_ns
|
from esphomeyaml.cpp_types import Action, App, Component, esphomelib_ns, global_ns
|
||||||
|
|
||||||
|
@ -96,8 +95,7 @@ DEEP_SLEEP_ENTER_ACTION_SCHEMA = maybe_simple_id({
|
||||||
|
|
||||||
|
|
||||||
@ACTION_REGISTRY.register(CONF_DEEP_SLEEP_ENTER, DEEP_SLEEP_ENTER_ACTION_SCHEMA)
|
@ACTION_REGISTRY.register(CONF_DEEP_SLEEP_ENTER, DEEP_SLEEP_ENTER_ACTION_SCHEMA)
|
||||||
def deep_sleep_enter_to_code(config, action_id, arg_type):
|
def deep_sleep_enter_to_code(config, action_id, arg_type, template_arg):
|
||||||
template_arg = TemplateArguments(arg_type)
|
|
||||||
for var in get_variable(config[CONF_ID]):
|
for var in get_variable(config[CONF_ID]):
|
||||||
yield None
|
yield None
|
||||||
rhs = var.make_enter_deep_sleep_action(template_arg)
|
rhs = var.make_enter_deep_sleep_action(template_arg)
|
||||||
|
@ -112,8 +110,7 @@ DEEP_SLEEP_PREVENT_ACTION_SCHEMA = maybe_simple_id({
|
||||||
|
|
||||||
|
|
||||||
@ACTION_REGISTRY.register(CONF_DEEP_SLEEP_PREVENT, DEEP_SLEEP_PREVENT_ACTION_SCHEMA)
|
@ACTION_REGISTRY.register(CONF_DEEP_SLEEP_PREVENT, DEEP_SLEEP_PREVENT_ACTION_SCHEMA)
|
||||||
def deep_sleep_prevent_to_code(config, action_id, arg_type):
|
def deep_sleep_prevent_to_code(config, action_id, arg_type, template_arg):
|
||||||
template_arg = TemplateArguments(arg_type)
|
|
||||||
for var in get_variable(config[CONF_ID]):
|
for var in get_variable(config[CONF_ID]):
|
||||||
yield None
|
yield None
|
||||||
rhs = var.make_prevent_deep_sleep_action(template_arg)
|
rhs = var.make_prevent_deep_sleep_action(template_arg)
|
||||||
|
|
|
@ -77,8 +77,7 @@ FAN_TOGGLE_ACTION_SCHEMA = maybe_simple_id({
|
||||||
|
|
||||||
|
|
||||||
@ACTION_REGISTRY.register(CONF_FAN_TOGGLE, FAN_TOGGLE_ACTION_SCHEMA)
|
@ACTION_REGISTRY.register(CONF_FAN_TOGGLE, FAN_TOGGLE_ACTION_SCHEMA)
|
||||||
def fan_toggle_to_code(config, action_id, arg_type):
|
def fan_toggle_to_code(config, action_id, arg_type, template_arg):
|
||||||
template_arg = TemplateArguments(arg_type)
|
|
||||||
for var in get_variable(config[CONF_ID]):
|
for var in get_variable(config[CONF_ID]):
|
||||||
yield None
|
yield None
|
||||||
rhs = var.make_toggle_action(template_arg)
|
rhs = var.make_toggle_action(template_arg)
|
||||||
|
@ -93,8 +92,7 @@ FAN_TURN_OFF_ACTION_SCHEMA = maybe_simple_id({
|
||||||
|
|
||||||
|
|
||||||
@ACTION_REGISTRY.register(CONF_FAN_TURN_OFF, FAN_TURN_OFF_ACTION_SCHEMA)
|
@ACTION_REGISTRY.register(CONF_FAN_TURN_OFF, FAN_TURN_OFF_ACTION_SCHEMA)
|
||||||
def fan_turn_off_to_code(config, action_id, arg_type):
|
def fan_turn_off_to_code(config, action_id, arg_type, template_arg):
|
||||||
template_arg = TemplateArguments(arg_type)
|
|
||||||
for var in get_variable(config[CONF_ID]):
|
for var in get_variable(config[CONF_ID]):
|
||||||
yield None
|
yield None
|
||||||
rhs = var.make_turn_off_action(template_arg)
|
rhs = var.make_turn_off_action(template_arg)
|
||||||
|
@ -111,8 +109,7 @@ FAN_TURN_ON_ACTION_SCHEMA = maybe_simple_id({
|
||||||
|
|
||||||
|
|
||||||
@ACTION_REGISTRY.register(CONF_FAN_TURN_ON, FAN_TURN_ON_ACTION_SCHEMA)
|
@ACTION_REGISTRY.register(CONF_FAN_TURN_ON, FAN_TURN_ON_ACTION_SCHEMA)
|
||||||
def fan_turn_on_to_code(config, action_id, arg_type):
|
def fan_turn_on_to_code(config, action_id, arg_type, template_arg):
|
||||||
template_arg = TemplateArguments(arg_type)
|
|
||||||
for var in get_variable(config[CONF_ID]):
|
for var in get_variable(config[CONF_ID]):
|
||||||
yield None
|
yield None
|
||||||
rhs = var.make_turn_on_action(template_arg)
|
rhs = var.make_turn_on_action(template_arg)
|
||||||
|
|
|
@ -363,8 +363,7 @@ LIGHT_TOGGLE_ACTION_SCHEMA = maybe_simple_id({
|
||||||
|
|
||||||
|
|
||||||
@ACTION_REGISTRY.register(CONF_LIGHT_TOGGLE, LIGHT_TOGGLE_ACTION_SCHEMA)
|
@ACTION_REGISTRY.register(CONF_LIGHT_TOGGLE, LIGHT_TOGGLE_ACTION_SCHEMA)
|
||||||
def light_toggle_to_code(config, action_id, arg_type):
|
def light_toggle_to_code(config, action_id, arg_type, template_arg):
|
||||||
template_arg = TemplateArguments(arg_type)
|
|
||||||
for var in get_variable(config[CONF_ID]):
|
for var in get_variable(config[CONF_ID]):
|
||||||
yield None
|
yield None
|
||||||
rhs = var.make_toggle_action(template_arg)
|
rhs = var.make_toggle_action(template_arg)
|
||||||
|
@ -385,8 +384,7 @@ LIGHT_TURN_OFF_ACTION_SCHEMA = maybe_simple_id({
|
||||||
|
|
||||||
|
|
||||||
@ACTION_REGISTRY.register(CONF_LIGHT_TURN_OFF, LIGHT_TURN_OFF_ACTION_SCHEMA)
|
@ACTION_REGISTRY.register(CONF_LIGHT_TURN_OFF, LIGHT_TURN_OFF_ACTION_SCHEMA)
|
||||||
def light_turn_off_to_code(config, action_id, arg_type):
|
def light_turn_off_to_code(config, action_id, arg_type, template_arg):
|
||||||
template_arg = TemplateArguments(arg_type)
|
|
||||||
for var in get_variable(config[CONF_ID]):
|
for var in get_variable(config[CONF_ID]):
|
||||||
yield None
|
yield None
|
||||||
rhs = var.make_turn_off_action(template_arg)
|
rhs = var.make_turn_off_action(template_arg)
|
||||||
|
@ -417,8 +415,7 @@ LIGHT_TURN_ON_ACTION_SCHEMA = maybe_simple_id({
|
||||||
|
|
||||||
|
|
||||||
@ACTION_REGISTRY.register(CONF_LIGHT_TURN_ON, LIGHT_TURN_ON_ACTION_SCHEMA)
|
@ACTION_REGISTRY.register(CONF_LIGHT_TURN_ON, LIGHT_TURN_ON_ACTION_SCHEMA)
|
||||||
def light_turn_on_to_code(config, action_id, arg_type):
|
def light_turn_on_to_code(config, action_id, arg_type, template_arg):
|
||||||
template_arg = TemplateArguments(arg_type)
|
|
||||||
for var in get_variable(config[CONF_ID]):
|
for var in get_variable(config[CONF_ID]):
|
||||||
yield None
|
yield None
|
||||||
rhs = var.make_turn_on_action(template_arg)
|
rhs = var.make_turn_on_action(template_arg)
|
||||||
|
|
|
@ -7,9 +7,8 @@ import esphomeyaml.config_validation as cv
|
||||||
from esphomeyaml.const import CONF_ARGS, CONF_BAUD_RATE, CONF_FORMAT, CONF_ID, CONF_LEVEL, \
|
from esphomeyaml.const import CONF_ARGS, CONF_BAUD_RATE, CONF_FORMAT, CONF_ID, CONF_LEVEL, \
|
||||||
CONF_LOGS, CONF_TAG, CONF_TX_BUFFER_SIZE
|
CONF_LOGS, CONF_TAG, CONF_TX_BUFFER_SIZE
|
||||||
from esphomeyaml.core import EsphomeyamlError, Lambda
|
from esphomeyaml.core import EsphomeyamlError, Lambda
|
||||||
from esphomeyaml.cpp_generator import Pvariable, add, TemplateArguments, RawExpression, statement, \
|
from esphomeyaml.cpp_generator import Pvariable, RawExpression, add, process_lambda, statement
|
||||||
process_lambda
|
from esphomeyaml.cpp_types import App, Component, esphomelib_ns, global_ns
|
||||||
from esphomeyaml.cpp_types import global_ns, esphomelib_ns, Component, App
|
|
||||||
|
|
||||||
LOG_LEVELS = {
|
LOG_LEVELS = {
|
||||||
'NONE': global_ns.ESPHOMELIB_LOG_LEVEL_NONE,
|
'NONE': global_ns.ESPHOMELIB_LOG_LEVEL_NONE,
|
||||||
|
@ -116,8 +115,7 @@ LOGGER_LOG_ACTION_SCHEMA = vol.All(maybe_simple_message({
|
||||||
|
|
||||||
|
|
||||||
@ACTION_REGISTRY.register(CONF_LOGGER_LOG, LOGGER_LOG_ACTION_SCHEMA)
|
@ACTION_REGISTRY.register(CONF_LOGGER_LOG, LOGGER_LOG_ACTION_SCHEMA)
|
||||||
def logger_log_action_to_code(config, action_id, arg_type):
|
def logger_log_action_to_code(config, action_id, arg_type, template_arg):
|
||||||
template_arg = TemplateArguments(arg_type)
|
|
||||||
esp_log = LOG_LEVEL_TO_ESP_LOG[config[CONF_LEVEL]]
|
esp_log = LOG_LEVEL_TO_ESP_LOG[config[CONF_LEVEL]]
|
||||||
args = [RawExpression(unicode(x)) for x in config[CONF_ARGS]]
|
args = [RawExpression(unicode(x)) for x in config[CONF_ARGS]]
|
||||||
|
|
||||||
|
|
|
@ -197,8 +197,7 @@ MQTT_PUBLISH_ACTION_SCHEMA = vol.Schema({
|
||||||
|
|
||||||
|
|
||||||
@ACTION_REGISTRY.register(CONF_MQTT_PUBLISH, MQTT_PUBLISH_ACTION_SCHEMA)
|
@ACTION_REGISTRY.register(CONF_MQTT_PUBLISH, MQTT_PUBLISH_ACTION_SCHEMA)
|
||||||
def mqtt_publish_action_to_code(config, action_id, arg_type):
|
def mqtt_publish_action_to_code(config, action_id, arg_type, template_arg):
|
||||||
template_arg = TemplateArguments(arg_type)
|
|
||||||
rhs = App.Pget_mqtt_client().Pmake_publish_action(template_arg)
|
rhs = App.Pget_mqtt_client().Pmake_publish_action(template_arg)
|
||||||
type = MQTTPublishAction.template(template_arg)
|
type = MQTTPublishAction.template(template_arg)
|
||||||
action = Pvariable(action_id, rhs, type=type)
|
action = Pvariable(action_id, rhs, type=type)
|
||||||
|
@ -230,8 +229,7 @@ MQTT_PUBLISH_JSON_ACTION_SCHEMA = vol.Schema({
|
||||||
|
|
||||||
|
|
||||||
@ACTION_REGISTRY.register(CONF_MQTT_PUBLISH_JSON, MQTT_PUBLISH_JSON_ACTION_SCHEMA)
|
@ACTION_REGISTRY.register(CONF_MQTT_PUBLISH_JSON, MQTT_PUBLISH_JSON_ACTION_SCHEMA)
|
||||||
def mqtt_publish_json_action_to_code(config, action_id, arg_type):
|
def mqtt_publish_json_action_to_code(config, action_id, arg_type, template_arg):
|
||||||
template_arg = TemplateArguments(arg_type)
|
|
||||||
rhs = App.Pget_mqtt_client().Pmake_publish_json_action(template_arg)
|
rhs = App.Pget_mqtt_client().Pmake_publish_json_action(template_arg)
|
||||||
type = MQTTPublishJsonAction.template(template_arg)
|
type = MQTTPublishJsonAction.template(template_arg)
|
||||||
action = Pvariable(action_id, rhs, type=type)
|
action = Pvariable(action_id, rhs, type=type)
|
||||||
|
|
|
@ -68,8 +68,7 @@ OUTPUT_TURN_ON_ACTION = maybe_simple_id({
|
||||||
|
|
||||||
|
|
||||||
@ACTION_REGISTRY.register(CONF_OUTPUT_TURN_ON, OUTPUT_TURN_ON_ACTION)
|
@ACTION_REGISTRY.register(CONF_OUTPUT_TURN_ON, OUTPUT_TURN_ON_ACTION)
|
||||||
def output_turn_on_to_code(config, action_id, arg_type):
|
def output_turn_on_to_code(config, action_id, arg_type, template_arg):
|
||||||
template_arg = TemplateArguments(arg_type)
|
|
||||||
for var in get_variable(config[CONF_ID]):
|
for var in get_variable(config[CONF_ID]):
|
||||||
yield None
|
yield None
|
||||||
rhs = var.make_turn_on_action(template_arg)
|
rhs = var.make_turn_on_action(template_arg)
|
||||||
|
@ -84,8 +83,7 @@ OUTPUT_TURN_OFF_ACTION = maybe_simple_id({
|
||||||
|
|
||||||
|
|
||||||
@ACTION_REGISTRY.register(CONF_OUTPUT_TURN_OFF, OUTPUT_TURN_OFF_ACTION)
|
@ACTION_REGISTRY.register(CONF_OUTPUT_TURN_OFF, OUTPUT_TURN_OFF_ACTION)
|
||||||
def output_turn_off_to_code(config, action_id, arg_type):
|
def output_turn_off_to_code(config, action_id, arg_type, template_arg):
|
||||||
template_arg = TemplateArguments(arg_type)
|
|
||||||
for var in get_variable(config[CONF_ID]):
|
for var in get_variable(config[CONF_ID]):
|
||||||
yield None
|
yield None
|
||||||
rhs = var.make_turn_off_action(template_arg)
|
rhs = var.make_turn_off_action(template_arg)
|
||||||
|
@ -101,8 +99,7 @@ OUTPUT_SET_LEVEL_ACTION = vol.Schema({
|
||||||
|
|
||||||
|
|
||||||
@ACTION_REGISTRY.register(CONF_OUTPUT_SET_LEVEL, OUTPUT_SET_LEVEL_ACTION)
|
@ACTION_REGISTRY.register(CONF_OUTPUT_SET_LEVEL, OUTPUT_SET_LEVEL_ACTION)
|
||||||
def output_set_level_to_code(config, action_id, arg_type):
|
def output_set_level_to_code(config, action_id, arg_type, template_arg):
|
||||||
template_arg = TemplateArguments(arg_type)
|
|
||||||
for var in get_variable(config[CONF_ID]):
|
for var in get_variable(config[CONF_ID]):
|
||||||
yield None
|
yield None
|
||||||
rhs = var.make_set_level_action(template_arg)
|
rhs = var.make_set_level_action(template_arg)
|
||||||
|
|
|
@ -64,7 +64,3 @@ def to_code(config):
|
||||||
|
|
||||||
|
|
||||||
BUILD_FLAGS = '-DUSE_CUSTOM_OUTPUT'
|
BUILD_FLAGS = '-DUSE_CUSTOM_OUTPUT'
|
||||||
|
|
||||||
|
|
||||||
def to_hass_config(data, config):
|
|
||||||
return [binary_sensor.core_to_hass_config(data, sens) for sens in config[CONF_OUTPUTS]]
|
|
||||||
|
|
|
@ -4,11 +4,12 @@ from esphomeyaml import automation
|
||||||
from esphomeyaml.automation import ACTION_REGISTRY, maybe_simple_id
|
from esphomeyaml.automation import ACTION_REGISTRY, maybe_simple_id
|
||||||
import esphomeyaml.config_validation as cv
|
import esphomeyaml.config_validation as cv
|
||||||
from esphomeyaml.const import CONF_ID
|
from esphomeyaml.const import CONF_ID
|
||||||
from esphomeyaml.cpp_generator import Pvariable, TemplateArguments, get_variable
|
from esphomeyaml.cpp_generator import Pvariable, get_variable
|
||||||
from esphomeyaml.cpp_types import Action, NoArg, Trigger, esphomelib_ns
|
from esphomeyaml.cpp_types import Action, NoArg, Trigger, esphomelib_ns
|
||||||
|
|
||||||
Script = esphomelib_ns.class_('Script', Trigger.template(NoArg))
|
Script = esphomelib_ns.class_('Script', Trigger.template(NoArg))
|
||||||
ScriptExecuteAction = esphomelib_ns.class_('ScriptExecuteAction', Action)
|
ScriptExecuteAction = esphomelib_ns.class_('ScriptExecuteAction', Action)
|
||||||
|
ScriptStopAction = esphomelib_ns.class_('ScriptStopAction', Action)
|
||||||
|
|
||||||
CONFIG_SCHEMA = automation.validate_automation({
|
CONFIG_SCHEMA = automation.validate_automation({
|
||||||
vol.Required(CONF_ID): cv.declare_variable_id(Script),
|
vol.Required(CONF_ID): cv.declare_variable_id(Script),
|
||||||
|
@ -28,10 +29,24 @@ SCRIPT_EXECUTE_ACTION_SCHEMA = maybe_simple_id({
|
||||||
|
|
||||||
|
|
||||||
@ACTION_REGISTRY.register(CONF_SCRIPT_EXECUTE, SCRIPT_EXECUTE_ACTION_SCHEMA)
|
@ACTION_REGISTRY.register(CONF_SCRIPT_EXECUTE, SCRIPT_EXECUTE_ACTION_SCHEMA)
|
||||||
def script_execute_action_to_code(config, action_id, arg_type):
|
def script_execute_action_to_code(config, action_id, arg_type, template_arg):
|
||||||
template_arg = TemplateArguments(arg_type)
|
|
||||||
for var in get_variable(config[CONF_ID]):
|
for var in get_variable(config[CONF_ID]):
|
||||||
yield None
|
yield None
|
||||||
rhs = var.make_execute_action(template_arg)
|
rhs = var.make_execute_action(template_arg)
|
||||||
type = ScriptExecuteAction.template(arg_type)
|
type = ScriptExecuteAction.template(arg_type)
|
||||||
yield Pvariable(action_id, rhs, type=type)
|
yield Pvariable(action_id, rhs, type=type)
|
||||||
|
|
||||||
|
|
||||||
|
CONF_SCRIPT_STOP = 'script.stop'
|
||||||
|
SCRIPT_STOP_ACTION_SCHEMA = maybe_simple_id({
|
||||||
|
vol.Required(CONF_ID): cv.use_variable_id(Script),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ACTION_REGISTRY.register(CONF_SCRIPT_STOP, SCRIPT_STOP_ACTION_SCHEMA)
|
||||||
|
def script_stop_action_to_code(config, action_id, arg_type, template_arg):
|
||||||
|
for var in get_variable(config[CONF_ID]):
|
||||||
|
yield None
|
||||||
|
rhs = var.make_stop_action(template_arg)
|
||||||
|
type = ScriptStopAction.template(arg_type)
|
||||||
|
yield Pvariable(action_id, rhs, type=type)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from esphomeyaml import automation
|
from esphomeyaml import automation
|
||||||
|
from esphomeyaml.automation import CONDITION_REGISTRY
|
||||||
from esphomeyaml.components import mqtt
|
from esphomeyaml.components import mqtt
|
||||||
from esphomeyaml.components.mqtt import setup_mqtt_component
|
from esphomeyaml.components.mqtt import setup_mqtt_component
|
||||||
import esphomeyaml.config_validation as cv
|
import esphomeyaml.config_validation as cv
|
||||||
|
@ -12,7 +13,8 @@ from esphomeyaml.const import CONF_ABOVE, CONF_ACCURACY_DECIMALS, CONF_ALPHA, CO
|
||||||
CONF_SLIDING_WINDOW_MOVING_AVERAGE, CONF_THROTTLE, CONF_TRIGGER_ID, CONF_UNIQUE, \
|
CONF_SLIDING_WINDOW_MOVING_AVERAGE, CONF_THROTTLE, CONF_TRIGGER_ID, CONF_UNIQUE, \
|
||||||
CONF_UNIT_OF_MEASUREMENT, CONF_WINDOW_SIZE
|
CONF_UNIT_OF_MEASUREMENT, CONF_WINDOW_SIZE
|
||||||
from esphomeyaml.core import CORE
|
from esphomeyaml.core import CORE
|
||||||
from esphomeyaml.cpp_generator import ArrayInitializer, Pvariable, add, process_lambda, templatable
|
from esphomeyaml.cpp_generator import ArrayInitializer, Pvariable, add, process_lambda, \
|
||||||
|
templatable, get_variable
|
||||||
from esphomeyaml.cpp_types import App, Component, Nameable, PollingComponent, Trigger, \
|
from esphomeyaml.cpp_types import App, Component, Nameable, PollingComponent, Trigger, \
|
||||||
esphomelib_ns, float_
|
esphomelib_ns, float_
|
||||||
|
|
||||||
|
@ -39,9 +41,9 @@ FILTER_KEYS = [CONF_OFFSET, CONF_MULTIPLY, CONF_FILTER_OUT, CONF_FILTER_NAN,
|
||||||
CONF_THROTTLE, CONF_DELTA, CONF_UNIQUE, CONF_HEARTBEAT, CONF_DEBOUNCE, CONF_OR]
|
CONF_THROTTLE, CONF_DELTA, CONF_UNIQUE, CONF_HEARTBEAT, CONF_DEBOUNCE, CONF_OR]
|
||||||
|
|
||||||
FILTERS_SCHEMA = vol.All(cv.ensure_list, [vol.All({
|
FILTERS_SCHEMA = vol.All(cv.ensure_list, [vol.All({
|
||||||
vol.Optional(CONF_OFFSET): vol.Coerce(float),
|
vol.Optional(CONF_OFFSET): cv.float_,
|
||||||
vol.Optional(CONF_MULTIPLY): vol.Coerce(float),
|
vol.Optional(CONF_MULTIPLY): cv.float_,
|
||||||
vol.Optional(CONF_FILTER_OUT): vol.Coerce(float),
|
vol.Optional(CONF_FILTER_OUT): cv.float_,
|
||||||
vol.Optional(CONF_FILTER_NAN): None,
|
vol.Optional(CONF_FILTER_NAN): None,
|
||||||
vol.Optional(CONF_SLIDING_WINDOW_MOVING_AVERAGE): vol.All(vol.Schema({
|
vol.Optional(CONF_SLIDING_WINDOW_MOVING_AVERAGE): vol.All(vol.Schema({
|
||||||
vol.Required(CONF_WINDOW_SIZE): cv.positive_not_null_int,
|
vol.Required(CONF_WINDOW_SIZE): cv.positive_not_null_int,
|
||||||
|
@ -54,7 +56,7 @@ FILTERS_SCHEMA = vol.All(cv.ensure_list, [vol.All({
|
||||||
}),
|
}),
|
||||||
vol.Optional(CONF_LAMBDA): cv.lambda_,
|
vol.Optional(CONF_LAMBDA): cv.lambda_,
|
||||||
vol.Optional(CONF_THROTTLE): cv.positive_time_period_milliseconds,
|
vol.Optional(CONF_THROTTLE): cv.positive_time_period_milliseconds,
|
||||||
vol.Optional(CONF_DELTA): vol.Coerce(float),
|
vol.Optional(CONF_DELTA): cv.float_,
|
||||||
vol.Optional(CONF_UNIQUE): None,
|
vol.Optional(CONF_UNIQUE): None,
|
||||||
vol.Optional(CONF_HEARTBEAT): cv.positive_time_period_milliseconds,
|
vol.Optional(CONF_HEARTBEAT): cv.positive_time_period_milliseconds,
|
||||||
vol.Optional(CONF_DEBOUNCE): cv.positive_time_period_milliseconds,
|
vol.Optional(CONF_DEBOUNCE): cv.positive_time_period_milliseconds,
|
||||||
|
@ -74,7 +76,7 @@ EmptyPollingParentSensor = sensor_ns.class_('EmptyPollingParentSensor', EmptySen
|
||||||
# Triggers
|
# Triggers
|
||||||
SensorStateTrigger = sensor_ns.class_('SensorStateTrigger', Trigger.template(float_))
|
SensorStateTrigger = sensor_ns.class_('SensorStateTrigger', Trigger.template(float_))
|
||||||
SensorRawStateTrigger = sensor_ns.class_('SensorRawStateTrigger', Trigger.template(float_))
|
SensorRawStateTrigger = sensor_ns.class_('SensorRawStateTrigger', Trigger.template(float_))
|
||||||
ValueRangeTrigger = sensor_ns.class_('ValueRangeTrigger', Trigger.template(float_))
|
ValueRangeTrigger = sensor_ns.class_('ValueRangeTrigger', Trigger.template(float_), Component)
|
||||||
|
|
||||||
# Filters
|
# Filters
|
||||||
Filter = sensor_ns.class_('Filter')
|
Filter = sensor_ns.class_('Filter')
|
||||||
|
@ -91,6 +93,7 @@ HeartbeatFilter = sensor_ns.class_('HeartbeatFilter', Filter, Component)
|
||||||
DeltaFilter = sensor_ns.class_('DeltaFilter', Filter)
|
DeltaFilter = sensor_ns.class_('DeltaFilter', Filter)
|
||||||
OrFilter = sensor_ns.class_('OrFilter', Filter)
|
OrFilter = sensor_ns.class_('OrFilter', Filter)
|
||||||
UniqueFilter = sensor_ns.class_('UniqueFilter', Filter)
|
UniqueFilter = sensor_ns.class_('UniqueFilter', Filter)
|
||||||
|
SensorInRangeCondition = sensor_ns.class_('SensorInRangeCondition', Filter)
|
||||||
|
|
||||||
SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({
|
SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({
|
||||||
cv.GenerateID(CONF_MQTT_ID): cv.declare_variable_id(MQTTSensorComponent),
|
cv.GenerateID(CONF_MQTT_ID): cv.declare_variable_id(MQTTSensorComponent),
|
||||||
|
@ -107,8 +110,8 @@ SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({
|
||||||
}),
|
}),
|
||||||
vol.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation({
|
vol.Optional(CONF_ON_VALUE_RANGE): 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): cv.float_,
|
||||||
vol.Optional(CONF_BELOW): vol.Coerce(float),
|
vol.Optional(CONF_BELOW): cv.float_,
|
||||||
}, cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW)),
|
}, cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW)),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -189,6 +192,7 @@ def setup_sensor_core_(sensor_var, mqtt_var, config):
|
||||||
for conf in config.get(CONF_ON_VALUE_RANGE, []):
|
for conf in config.get(CONF_ON_VALUE_RANGE, []):
|
||||||
rhs = sensor_var.make_value_range_trigger()
|
rhs = sensor_var.make_value_range_trigger()
|
||||||
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs)
|
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs)
|
||||||
|
add(App.register_component(trigger))
|
||||||
if CONF_ABOVE in conf:
|
if CONF_ABOVE in conf:
|
||||||
for template_ in templatable(conf[CONF_ABOVE], float_, float_):
|
for template_ in templatable(conf[CONF_ABOVE], float_, float_):
|
||||||
yield
|
yield
|
||||||
|
@ -223,6 +227,30 @@ def register_sensor(var, config):
|
||||||
BUILD_FLAGS = '-DUSE_SENSOR'
|
BUILD_FLAGS = '-DUSE_SENSOR'
|
||||||
|
|
||||||
|
|
||||||
|
CONF_SENSOR_IN_RANGE = 'sensor.in_range'
|
||||||
|
SENSOR_IN_RANGE_CONDITION_SCHEMA = vol.All({
|
||||||
|
vol.Required(CONF_ID): cv.use_variable_id(Sensor),
|
||||||
|
vol.Optional(CONF_ABOVE): cv.float_,
|
||||||
|
vol.Optional(CONF_BELOW): cv.float_,
|
||||||
|
}, cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW))
|
||||||
|
|
||||||
|
|
||||||
|
@CONDITION_REGISTRY.register(CONF_SENSOR_IN_RANGE, SENSOR_IN_RANGE_CONDITION_SCHEMA)
|
||||||
|
def sensor_in_range_to_code(config, condition_id, arg_type, template_arg):
|
||||||
|
for var in get_variable(config[CONF_ID]):
|
||||||
|
yield None
|
||||||
|
rhs = var.make_sensor_in_range_condition(template_arg)
|
||||||
|
type = SensorInRangeCondition.template(arg_type)
|
||||||
|
cond = Pvariable(condition_id, rhs, type=type)
|
||||||
|
|
||||||
|
if CONF_ABOVE in config:
|
||||||
|
add(cond.set_min(config[CONF_ABOVE]))
|
||||||
|
if CONF_BELOW in config:
|
||||||
|
add(cond.set_max(config[CONF_BELOW]))
|
||||||
|
|
||||||
|
yield cond
|
||||||
|
|
||||||
|
|
||||||
def core_to_hass_config(data, config):
|
def core_to_hass_config(data, config):
|
||||||
ret = mqtt.build_hass_config(data, 'sensor', config, include_state=True, include_command=False)
|
ret = mqtt.build_hass_config(data, 'sensor', config, include_state=True, include_command=False)
|
||||||
if ret is None:
|
if ret is None:
|
||||||
|
|
|
@ -92,8 +92,7 @@ STEPPER_SET_TARGET_ACTION_SCHEMA = vol.Schema({
|
||||||
|
|
||||||
|
|
||||||
@ACTION_REGISTRY.register(CONF_STEPPER_SET_TARGET, STEPPER_SET_TARGET_ACTION_SCHEMA)
|
@ACTION_REGISTRY.register(CONF_STEPPER_SET_TARGET, STEPPER_SET_TARGET_ACTION_SCHEMA)
|
||||||
def stepper_set_target_to_code(config, action_id, arg_type):
|
def stepper_set_target_to_code(config, action_id, arg_type, template_arg):
|
||||||
template_arg = TemplateArguments(arg_type)
|
|
||||||
for var in get_variable(config[CONF_ID]):
|
for var in get_variable(config[CONF_ID]):
|
||||||
yield None
|
yield None
|
||||||
rhs = var.make_set_target_action(template_arg)
|
rhs = var.make_set_target_action(template_arg)
|
||||||
|
@ -113,8 +112,7 @@ STEPPER_REPORT_POSITION_ACTION_SCHEMA = vol.Schema({
|
||||||
|
|
||||||
|
|
||||||
@ACTION_REGISTRY.register(CONF_STEPPER_REPORT_POSITION, STEPPER_REPORT_POSITION_ACTION_SCHEMA)
|
@ACTION_REGISTRY.register(CONF_STEPPER_REPORT_POSITION, STEPPER_REPORT_POSITION_ACTION_SCHEMA)
|
||||||
def stepper_report_position_to_code(config, action_id, arg_type):
|
def stepper_report_position_to_code(config, action_id, arg_type, template_arg):
|
||||||
template_arg = TemplateArguments(arg_type)
|
|
||||||
for var in get_variable(config[CONF_ID]):
|
for var in get_variable(config[CONF_ID]):
|
||||||
yield None
|
yield None
|
||||||
rhs = var.make_report_position_action(template_arg)
|
rhs = var.make_report_position_action(template_arg)
|
||||||
|
|
136
esphomeyaml/components/substitutions.py
Normal file
136
esphomeyaml/components/substitutions.py
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from esphomeyaml import core
|
||||||
|
import esphomeyaml.config_validation as cv
|
||||||
|
from esphomeyaml.core import EsphomeyamlError
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
CONF_SUBSTITUTIONS = 'substitutions'
|
||||||
|
|
||||||
|
VALID_SUBSTITUTIONS_CHARACTERS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' \
|
||||||
|
'0123456789_'
|
||||||
|
|
||||||
|
|
||||||
|
def validate_substitution_key(value):
|
||||||
|
value = cv.string(value)
|
||||||
|
if not value:
|
||||||
|
raise vol.Invalid("Substitution key must not be empty")
|
||||||
|
if value[0].isdigit():
|
||||||
|
raise vol.Invalid("First character in substitutions cannot be a digit.")
|
||||||
|
if value[0] == '$':
|
||||||
|
value = value[1:]
|
||||||
|
for char in value:
|
||||||
|
if char not in VALID_SUBSTITUTIONS_CHARACTERS:
|
||||||
|
raise vol.Invalid(
|
||||||
|
u"Substitution must only consist of upper/lowercase characters, the underscore "
|
||||||
|
u"and numbers. The character '{}' cannot be used".format(char))
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = vol.Schema({
|
||||||
|
validate_substitution_key: cv.string_strict,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def to_code(config):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
VARIABLE_PROG = re.compile('\\$([{0}]+|\\{{[{0}]*\\}})'.format(VALID_SUBSTITUTIONS_CHARACTERS))
|
||||||
|
|
||||||
|
|
||||||
|
def _expand_substitutions(substitutions, value, path):
|
||||||
|
if u'$' not in value:
|
||||||
|
return value
|
||||||
|
|
||||||
|
orig_value = value
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
while True:
|
||||||
|
m = VARIABLE_PROG.search(value, i)
|
||||||
|
if not m:
|
||||||
|
# Nothing more to match. Done
|
||||||
|
break
|
||||||
|
|
||||||
|
i, j = m.span(0)
|
||||||
|
name = m.group(1)
|
||||||
|
if name.startswith(u'{') and name.endswith(u'}'):
|
||||||
|
name = name[1:-1]
|
||||||
|
if name not in substitutions:
|
||||||
|
_LOGGER.warn(u"Found '%s' (see %s) which looks like a substitution, but '%s' was not "
|
||||||
|
u"declared", orig_value, u'->'.join(str(x) for x in path), name)
|
||||||
|
i = j
|
||||||
|
continue
|
||||||
|
|
||||||
|
sub = substitutions[name]
|
||||||
|
tail = value[j:]
|
||||||
|
value = value[:i] + sub
|
||||||
|
i = len(value)
|
||||||
|
value += tail
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def _substitute_item(substitutions, item, path):
|
||||||
|
if isinstance(item, list):
|
||||||
|
for i, it in enumerate(item):
|
||||||
|
sub = _substitute_item(substitutions, it, path + [i])
|
||||||
|
if sub is not None:
|
||||||
|
item[i] = sub
|
||||||
|
elif isinstance(item, dict):
|
||||||
|
replace_keys = []
|
||||||
|
for k, v in item.iteritems():
|
||||||
|
if path or k != CONF_SUBSTITUTIONS:
|
||||||
|
sub = _substitute_item(substitutions, k, path + [k])
|
||||||
|
if sub is not None:
|
||||||
|
replace_keys.append((k, sub))
|
||||||
|
sub = _substitute_item(substitutions, v, path + [k])
|
||||||
|
if sub is not None:
|
||||||
|
item[k] = sub
|
||||||
|
for old, new in replace_keys:
|
||||||
|
item[new] = item[old]
|
||||||
|
del item[old]
|
||||||
|
elif isinstance(item, str):
|
||||||
|
sub = _expand_substitutions(substitutions, item, path)
|
||||||
|
if sub != item:
|
||||||
|
return sub
|
||||||
|
elif isinstance(item, core.Lambda):
|
||||||
|
sub = _expand_substitutions(substitutions, item.value, path)
|
||||||
|
if sub != item:
|
||||||
|
item.value = sub
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def do_substitution_pass(config):
|
||||||
|
if CONF_SUBSTITUTIONS not in config:
|
||||||
|
return config
|
||||||
|
|
||||||
|
substitutions = config[CONF_SUBSTITUTIONS]
|
||||||
|
if not isinstance(substitutions, dict):
|
||||||
|
raise EsphomeyamlError(u"Substitutions must be a key to value mapping, got {}"
|
||||||
|
u"".format(type(substitutions)))
|
||||||
|
|
||||||
|
key = ''
|
||||||
|
try:
|
||||||
|
replace_keys = []
|
||||||
|
for key, value in substitutions.iteritems():
|
||||||
|
sub = validate_substitution_key(key)
|
||||||
|
if sub != key:
|
||||||
|
replace_keys.append((key, sub))
|
||||||
|
substitutions[key] = cv.string_strict(value)
|
||||||
|
for old, new in replace_keys:
|
||||||
|
substitutions[new] = substitutions[old]
|
||||||
|
del substitutions[old]
|
||||||
|
except vol.Invalid as err:
|
||||||
|
from esphomeyaml.config import _format_config_error
|
||||||
|
err.path.append(key)
|
||||||
|
|
||||||
|
raise EsphomeyamlError(_format_config_error(err, CONF_SUBSTITUTIONS, substitutions))
|
||||||
|
|
||||||
|
config[CONF_SUBSTITUTIONS] = substitutions
|
||||||
|
_substitute_item(substitutions, config, [])
|
||||||
|
|
||||||
|
return config
|
|
@ -1,6 +1,6 @@
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from esphomeyaml.automation import maybe_simple_id, ACTION_REGISTRY
|
from esphomeyaml.automation import maybe_simple_id, ACTION_REGISTRY, CONDITION_REGISTRY, Condition
|
||||||
from esphomeyaml.components import mqtt
|
from esphomeyaml.components import mqtt
|
||||||
from esphomeyaml.components.mqtt import setup_mqtt_component
|
from esphomeyaml.components.mqtt import setup_mqtt_component
|
||||||
import esphomeyaml.config_validation as cv
|
import esphomeyaml.config_validation as cv
|
||||||
|
@ -22,6 +22,8 @@ ToggleAction = switch_ns.class_('ToggleAction', Action)
|
||||||
TurnOffAction = switch_ns.class_('TurnOffAction', Action)
|
TurnOffAction = switch_ns.class_('TurnOffAction', Action)
|
||||||
TurnOnAction = switch_ns.class_('TurnOnAction', Action)
|
TurnOnAction = switch_ns.class_('TurnOnAction', Action)
|
||||||
|
|
||||||
|
SwitchCondition = switch_ns.class_('SwitchCondition', Condition)
|
||||||
|
|
||||||
SWITCH_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({
|
SWITCH_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({
|
||||||
cv.GenerateID(CONF_MQTT_ID): cv.declare_variable_id(MQTTSwitchComponent),
|
cv.GenerateID(CONF_MQTT_ID): cv.declare_variable_id(MQTTSwitchComponent),
|
||||||
vol.Optional(CONF_ICON): cv.icon,
|
vol.Optional(CONF_ICON): cv.icon,
|
||||||
|
@ -65,8 +67,7 @@ SWITCH_TOGGLE_ACTION_SCHEMA = maybe_simple_id({
|
||||||
|
|
||||||
|
|
||||||
@ACTION_REGISTRY.register(CONF_SWITCH_TOGGLE, SWITCH_TOGGLE_ACTION_SCHEMA)
|
@ACTION_REGISTRY.register(CONF_SWITCH_TOGGLE, SWITCH_TOGGLE_ACTION_SCHEMA)
|
||||||
def switch_toggle_to_code(config, action_id, arg_type):
|
def switch_toggle_to_code(config, action_id, arg_type, template_arg):
|
||||||
template_arg = TemplateArguments(arg_type)
|
|
||||||
for var in get_variable(config[CONF_ID]):
|
for var in get_variable(config[CONF_ID]):
|
||||||
yield None
|
yield None
|
||||||
rhs = var.make_toggle_action(template_arg)
|
rhs = var.make_toggle_action(template_arg)
|
||||||
|
@ -81,8 +82,7 @@ SWITCH_TURN_OFF_ACTION_SCHEMA = maybe_simple_id({
|
||||||
|
|
||||||
|
|
||||||
@ACTION_REGISTRY.register(CONF_SWITCH_TURN_OFF, SWITCH_TURN_OFF_ACTION_SCHEMA)
|
@ACTION_REGISTRY.register(CONF_SWITCH_TURN_OFF, SWITCH_TURN_OFF_ACTION_SCHEMA)
|
||||||
def switch_turn_off_to_code(config, action_id, arg_type):
|
def switch_turn_off_to_code(config, action_id, arg_type, template_arg):
|
||||||
template_arg = TemplateArguments(arg_type)
|
|
||||||
for var in get_variable(config[CONF_ID]):
|
for var in get_variable(config[CONF_ID]):
|
||||||
yield None
|
yield None
|
||||||
rhs = var.make_turn_off_action(template_arg)
|
rhs = var.make_turn_off_action(template_arg)
|
||||||
|
@ -97,8 +97,7 @@ SWITCH_TURN_ON_ACTION_SCHEMA = maybe_simple_id({
|
||||||
|
|
||||||
|
|
||||||
@ACTION_REGISTRY.register(CONF_SWITCH_TURN_ON, SWITCH_TURN_ON_ACTION_SCHEMA)
|
@ACTION_REGISTRY.register(CONF_SWITCH_TURN_ON, SWITCH_TURN_ON_ACTION_SCHEMA)
|
||||||
def switch_turn_on_to_code(config, action_id, arg_type):
|
def switch_turn_on_to_code(config, action_id, arg_type, template_arg):
|
||||||
template_arg = TemplateArguments(arg_type)
|
|
||||||
for var in get_variable(config[CONF_ID]):
|
for var in get_variable(config[CONF_ID]):
|
||||||
yield None
|
yield None
|
||||||
rhs = var.make_turn_on_action(template_arg)
|
rhs = var.make_turn_on_action(template_arg)
|
||||||
|
@ -106,6 +105,36 @@ def switch_turn_on_to_code(config, action_id, arg_type):
|
||||||
yield Pvariable(action_id, rhs, type=type)
|
yield Pvariable(action_id, rhs, type=type)
|
||||||
|
|
||||||
|
|
||||||
|
CONF_SWITCH_IS_ON = 'switch.is_on'
|
||||||
|
SWITCH_IS_ON_CONDITION_SCHEMA = maybe_simple_id({
|
||||||
|
vol.Required(CONF_ID): cv.use_variable_id(Switch),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@CONDITION_REGISTRY.register(CONF_SWITCH_IS_ON, SWITCH_IS_ON_CONDITION_SCHEMA)
|
||||||
|
def switch_is_on_to_code(config, condition_id, arg_type, template_arg):
|
||||||
|
for var in get_variable(config[CONF_ID]):
|
||||||
|
yield None
|
||||||
|
rhs = var.make_switch_is_on_condition(template_arg)
|
||||||
|
type = SwitchCondition.template(arg_type)
|
||||||
|
yield Pvariable(condition_id, rhs, type=type)
|
||||||
|
|
||||||
|
|
||||||
|
CONF_SWITCH_IS_OFF = 'switch.is_off'
|
||||||
|
SWITCH_IS_OFF_CONDITION_SCHEMA = maybe_simple_id({
|
||||||
|
vol.Required(CONF_ID): cv.use_variable_id(Switch),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@CONDITION_REGISTRY.register(CONF_SWITCH_IS_OFF, SWITCH_IS_OFF_CONDITION_SCHEMA)
|
||||||
|
def switch_is_off_to_code(config, condition_id, arg_type, template_arg):
|
||||||
|
for var in get_variable(config[CONF_ID]):
|
||||||
|
yield None
|
||||||
|
rhs = var.make_switch_is_off_condition(template_arg)
|
||||||
|
type = SwitchCondition.template(arg_type)
|
||||||
|
yield Pvariable(condition_id, rhs, type=type)
|
||||||
|
|
||||||
|
|
||||||
def core_to_hass_config(data, config):
|
def core_to_hass_config(data, config):
|
||||||
ret = mqtt.build_hass_config(data, 'switch', config, include_state=True, include_command=True)
|
ret = mqtt.build_hass_config(data, 'switch', config, include_state=True, include_command=True)
|
||||||
if ret is None:
|
if ret is None:
|
||||||
|
|
|
@ -8,6 +8,7 @@ import logging
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from esphomeyaml import core, core_config, yaml_util
|
from esphomeyaml import core, core_config, yaml_util
|
||||||
|
from esphomeyaml.components import substitutions
|
||||||
from esphomeyaml.const import CONF_ESPHOMEYAML, CONF_PLATFORM, CONF_WIFI, ESP_PLATFORMS
|
from esphomeyaml.const import CONF_ESPHOMEYAML, CONF_PLATFORM, CONF_WIFI, ESP_PLATFORMS
|
||||||
from esphomeyaml.core import CORE, EsphomeyamlError
|
from esphomeyaml.core import CORE, EsphomeyamlError
|
||||||
from esphomeyaml.helpers import color
|
from esphomeyaml.helpers import color
|
||||||
|
@ -325,6 +326,7 @@ def load_config():
|
||||||
except OSError:
|
except OSError:
|
||||||
raise EsphomeyamlError(u"Could not read configuration file at {}".format(CORE.config_path))
|
raise EsphomeyamlError(u"Could not read configuration file at {}".format(CORE.config_path))
|
||||||
CORE.raw_config = config
|
CORE.raw_config = config
|
||||||
|
config = substitutions.do_substitution_pass(config)
|
||||||
core_config.preload_core_config(config)
|
core_config.preload_core_config(config)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -338,16 +340,15 @@ def load_config():
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def line_info(obj, **kwargs):
|
def line_info(obj):
|
||||||
"""Display line config source."""
|
"""Display line config source."""
|
||||||
if hasattr(obj, '__config_file__'):
|
if hasattr(obj, '__config_file__'):
|
||||||
return color('cyan', "[source {}:{}]"
|
return color('cyan', "[source {}:{}]"
|
||||||
.format(obj.__config_file__, obj.__line__ or '?'),
|
.format(obj.__config_file__, obj.__line__ or '?'))
|
||||||
**kwargs)
|
|
||||||
return '?'
|
return '?'
|
||||||
|
|
||||||
|
|
||||||
def dump_dict(layer, indent_count=0, listi=False, **kwargs):
|
def dump_dict(layer, indent_count=0, listi=False):
|
||||||
def sort_dict_key(val):
|
def sort_dict_key(val):
|
||||||
"""Return the dict key for sorting."""
|
"""Return the dict key for sorting."""
|
||||||
key = str.lower(val[0])
|
key = str.lower(val[0])
|
||||||
|
@ -359,7 +360,7 @@ def dump_dict(layer, indent_count=0, listi=False, **kwargs):
|
||||||
if isinstance(layer, dict):
|
if isinstance(layer, dict):
|
||||||
for key, value in sorted(layer.items(), key=sort_dict_key):
|
for key, value in sorted(layer.items(), key=sort_dict_key):
|
||||||
if isinstance(value, (dict, list)):
|
if isinstance(value, (dict, list)):
|
||||||
safe_print(u"{} {}: {}".format(indent_str, key, line_info(value, **kwargs)))
|
safe_print(u"{} {}: {}".format(indent_str, key, line_info(value)))
|
||||||
dump_dict(value, indent_count + 2)
|
dump_dict(value, indent_count + 2)
|
||||||
else:
|
else:
|
||||||
safe_print(u"{} {}: {}".format(indent_str, key, value))
|
safe_print(u"{} {}: {}".format(indent_str, key, value))
|
||||||
|
|
|
@ -22,8 +22,9 @@ _LOGGER = logging.getLogger(__name__)
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
|
|
||||||
port = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535))
|
port = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535))
|
||||||
positive_float = vol.All(vol.Coerce(float), vol.Range(min=0))
|
float_ = vol.Coerce(float)
|
||||||
zero_to_one_float = vol.All(vol.Coerce(float), vol.Range(min=0, max=1))
|
positive_float = vol.All(float_, vol.Range(min=0))
|
||||||
|
zero_to_one_float = vol.All(float_, vol.Range(min=0, max=1))
|
||||||
positive_int = vol.All(vol.Coerce(int), vol.Range(min=0))
|
positive_int = vol.All(vol.Coerce(int), vol.Range(min=0))
|
||||||
positive_not_null_int = vol.All(vol.Coerce(int), vol.Range(min=0, min_included=False))
|
positive_not_null_int = vol.All(vol.Coerce(int), vol.Range(min=0, min_included=False))
|
||||||
|
|
||||||
|
@ -155,8 +156,9 @@ def variable_id_str_(value):
|
||||||
raise vol.Invalid("Dashes are not supported in IDs, please use underscores instead.")
|
raise vol.Invalid("Dashes are not supported in IDs, please use underscores instead.")
|
||||||
for char in value:
|
for char in value:
|
||||||
if char != '_' and not char.isalnum():
|
if char != '_' and not char.isalnum():
|
||||||
raise vol.Invalid(u"IDs must only consist of upper/lowercase characters and numbers."
|
raise vol.Invalid(u"IDs must only consist of upper/lowercase characters, the underscore"
|
||||||
u"The character '{}' cannot be used".format(char))
|
u"character and numbers. The character '{}' cannot be used"
|
||||||
|
u"".format(char))
|
||||||
if value in RESERVED_IDS:
|
if value in RESERVED_IDS:
|
||||||
raise vol.Invalid(u"ID {} is reserved internally and cannot be used".format(value))
|
raise vol.Invalid(u"ID {} is reserved internally and cannot be used".format(value))
|
||||||
return value
|
return value
|
||||||
|
@ -258,12 +260,12 @@ TIME_PERIOD_ERROR = "Time period {} should be format number + unit, for example
|
||||||
|
|
||||||
time_period_dict = vol.All(
|
time_period_dict = vol.All(
|
||||||
dict, vol.Schema({
|
dict, vol.Schema({
|
||||||
'days': vol.Coerce(float),
|
'days': float_,
|
||||||
'hours': vol.Coerce(float),
|
'hours': float_,
|
||||||
'minutes': vol.Coerce(float),
|
'minutes': float_,
|
||||||
'seconds': vol.Coerce(float),
|
'seconds': float_,
|
||||||
'milliseconds': vol.Coerce(float),
|
'milliseconds': float_,
|
||||||
'microseconds': vol.Coerce(float),
|
'microseconds': float_,
|
||||||
}),
|
}),
|
||||||
has_at_least_one_key('days', 'hours', 'minutes',
|
has_at_least_one_key('days', 'hours', 'minutes',
|
||||||
'seconds', 'milliseconds', 'microseconds'),
|
'seconds', 'milliseconds', 'microseconds'),
|
||||||
|
@ -701,5 +703,5 @@ MQTT_COMMAND_COMPONENT_SCHEMA = MQTT_COMPONENT_SCHEMA.extend({
|
||||||
})
|
})
|
||||||
|
|
||||||
COMPONENT_SCHEMA = vol.Schema({
|
COMPONENT_SCHEMA = vol.Schema({
|
||||||
vol.Optional(CONF_SETUP_PRIORITY): vol.Coerce(float)
|
vol.Optional(CONF_SETUP_PRIORITY): float_
|
||||||
})
|
})
|
||||||
|
|
|
@ -91,6 +91,7 @@ CONF_ABOVE = 'above'
|
||||||
CONF_BELOW = 'below'
|
CONF_BELOW = 'below'
|
||||||
CONF_ON = 'on'
|
CONF_ON = 'on'
|
||||||
CONF_IF = 'if'
|
CONF_IF = 'if'
|
||||||
|
CONF_WHILE = 'while'
|
||||||
CONF_THEN = 'then'
|
CONF_THEN = 'then'
|
||||||
CONF_BINARY = 'binary'
|
CONF_BINARY = 'binary'
|
||||||
CONF_WHITE = 'white'
|
CONF_WHITE = 'white'
|
||||||
|
@ -384,6 +385,7 @@ CONF_PIN_C = 'pin_c'
|
||||||
CONF_PIN_D = 'pin_d'
|
CONF_PIN_D = 'pin_d'
|
||||||
CONF_SLEEP_WHEN_DONE = 'sleep_when_done'
|
CONF_SLEEP_WHEN_DONE = 'sleep_when_done'
|
||||||
CONF_STEP_MODE = 'step_mode'
|
CONF_STEP_MODE = 'step_mode'
|
||||||
|
CONF_COMPONENTS = 'components'
|
||||||
|
|
||||||
ALLOWED_NAME_CHARS = u'abcdefghijklmnopqrstuvwxyz0123456789_'
|
ALLOWED_NAME_CHARS = u'abcdefghijklmnopqrstuvwxyz0123456789_'
|
||||||
ARDUINO_VERSION_ESP32_DEV = 'https://github.com/platformio/platform-espressif32.git#feature/stage'
|
ARDUINO_VERSION_ESP32_DEV = 'https://github.com/platformio/platform-espressif32.git#feature/stage'
|
||||||
|
|
|
@ -209,11 +209,36 @@ class TimePeriodSeconds(TimePeriod):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
LAMBDA_PROG = re.compile(r'id\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)(\.?)')
|
||||||
|
|
||||||
|
|
||||||
class Lambda(object):
|
class Lambda(object):
|
||||||
def __init__(self, value):
|
def __init__(self, value):
|
||||||
self.value = value
|
self._value = value
|
||||||
self.parts = re.split(r'id\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)(\.?)', value)
|
self._parts = None
|
||||||
self.requires_ids = [ID(self.parts[i]) for i in range(1, len(self.parts), 3)]
|
self._requires_ids = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parts(self):
|
||||||
|
if self._parts is None:
|
||||||
|
self._parts = re.split(LAMBDA_PROG, self._value)
|
||||||
|
return self._parts
|
||||||
|
|
||||||
|
@property
|
||||||
|
def requires_ids(self):
|
||||||
|
if self._requires_ids is None:
|
||||||
|
self._requires_ids = [ID(self.parts[i]) for i in range(1, len(self.parts), 3)]
|
||||||
|
return self._requires_ids
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
return self._value
|
||||||
|
|
||||||
|
@value.setter
|
||||||
|
def value(self, value):
|
||||||
|
self._value = value
|
||||||
|
self._parts = None
|
||||||
|
self._requires_ids = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.value
|
return self.value
|
||||||
|
|
|
@ -167,7 +167,7 @@ CONFIG_SCHEMA = vol.Schema({
|
||||||
vol.Optional(CONF_BOARD_FLASH_MODE): cv.one_of(*BUILD_FLASH_MODES, lower=True),
|
vol.Optional(CONF_BOARD_FLASH_MODE): cv.one_of(*BUILD_FLASH_MODES, lower=True),
|
||||||
vol.Optional(CONF_ON_BOOT): 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): cv.float_,
|
||||||
}),
|
}),
|
||||||
vol.Optional(CONF_ON_SHUTDOWN): 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),
|
||||||
|
|
|
@ -464,7 +464,8 @@ class MockObj(Expression):
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def class_(self, name, *parents): # type: (str, *MockObjClass) -> MockObjClass
|
def class_(self, name, *parents): # type: (str, *MockObjClass) -> MockObjClass
|
||||||
obj = MockObjClass(u'{}{}{}'.format(self.base, self.op, name), u'.', parents=parents)
|
op = '' if self.op == '' else '::'
|
||||||
|
obj = MockObjClass(u'{}{}{}'.format(self.base, op, name), u'.', parents=parents)
|
||||||
obj.requires.append(self)
|
obj.requires.append(self)
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
# pylint: disable=wrong-import-position
|
# pylint: disable=wrong-import-position
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import binascii
|
|
||||||
import collections
|
import collections
|
||||||
import hmac
|
import hmac
|
||||||
import json
|
import json
|
||||||
|
@ -24,8 +23,9 @@ import tornado.websocket
|
||||||
|
|
||||||
from esphomeyaml import const
|
from esphomeyaml import const
|
||||||
from esphomeyaml.__main__ import get_serial_ports
|
from esphomeyaml.__main__ import get_serial_ports
|
||||||
from esphomeyaml.helpers import run_system_command, mkdir_p
|
from esphomeyaml.helpers import mkdir_p, run_system_command
|
||||||
from esphomeyaml.storage_json import StorageJSON, ext_storage_path
|
from esphomeyaml.storage_json import EsphomeyamlStorageJSON, StorageJSON, \
|
||||||
|
esphomeyaml_storage_path, ext_storage_path
|
||||||
from esphomeyaml.util import shlex_quote
|
from esphomeyaml.util import shlex_quote
|
||||||
|
|
||||||
# pylint: disable=unused-import, wrong-import-order
|
# pylint: disable=unused-import, wrong-import-order
|
||||||
|
@ -273,7 +273,17 @@ class DashboardEntry(object):
|
||||||
def update_available(self):
|
def update_available(self):
|
||||||
if self.storage is None:
|
if self.storage is None:
|
||||||
return True
|
return True
|
||||||
return self.storage.esphomeyaml_version != const.__version__
|
return self.update_old != self.update_new
|
||||||
|
|
||||||
|
@property
|
||||||
|
def update_old(self):
|
||||||
|
if self.storage is None:
|
||||||
|
return ''
|
||||||
|
return self.storage.esphomeyaml_version or ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def update_new(self):
|
||||||
|
return const.__version__
|
||||||
|
|
||||||
|
|
||||||
class MainRequestHandler(BaseHandler):
|
class MainRequestHandler(BaseHandler):
|
||||||
|
@ -464,6 +474,11 @@ def make_app(debug=False):
|
||||||
log_method("%d %s %.2fms", handler.get_status(),
|
log_method("%d %s %.2fms", handler.get_status(),
|
||||||
handler._request_summary(), request_time)
|
handler._request_summary(), request_time)
|
||||||
|
|
||||||
|
class StaticFileHandler(tornado.web.StaticFileHandler):
|
||||||
|
def set_extra_headers(self, path):
|
||||||
|
if debug:
|
||||||
|
self.set_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||||
|
|
||||||
static_path = os.path.join(os.path.dirname(__file__), 'static')
|
static_path = os.path.join(os.path.dirname(__file__), 'static')
|
||||||
app = tornado.web.Application([
|
app = tornado.web.Application([
|
||||||
(r"/", MainRequestHandler),
|
(r"/", MainRequestHandler),
|
||||||
|
@ -480,7 +495,7 @@ def make_app(debug=False):
|
||||||
(r"/serial-ports", SerialPortRequestHandler),
|
(r"/serial-ports", SerialPortRequestHandler),
|
||||||
(r"/ping", PingRequestHandler),
|
(r"/ping", PingRequestHandler),
|
||||||
(r"/wizard.html", WizardRequestHandler),
|
(r"/wizard.html", WizardRequestHandler),
|
||||||
(r'/static/(.*)', tornado.web.StaticFileHandler, {'path': static_path}),
|
(r'/static/(.*)', StaticFileHandler, {'path': static_path}),
|
||||||
], debug=debug, cookie_secret=COOKIE_SECRET, log_function=log_function)
|
], debug=debug, cookie_secret=COOKIE_SECRET, log_function=log_function)
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
@ -542,14 +557,12 @@ def start_web_server(args):
|
||||||
PASSWORD_DIGEST = hmac.new(args.password).digest()
|
PASSWORD_DIGEST = hmac.new(args.password).digest()
|
||||||
|
|
||||||
if USING_HASSIO_AUTH or USING_PASSWORD:
|
if USING_HASSIO_AUTH or USING_PASSWORD:
|
||||||
cookie_secret_path = os.path.join(CONFIG_DIR, '.esphomeyaml', 'cookie_secret')
|
path = esphomeyaml_storage_path(CONFIG_DIR)
|
||||||
if os.path.exists(cookie_secret_path):
|
storage = EsphomeyamlStorageJSON.load(path)
|
||||||
with open(cookie_secret_path, 'r') as f:
|
if storage is None:
|
||||||
COOKIE_SECRET = f.read()
|
storage = EsphomeyamlStorageJSON.get_default()
|
||||||
else:
|
storage.save(path)
|
||||||
COOKIE_SECRET = binascii.hexlify(os.urandom(64))
|
COOKIE_SECRET = storage.cookie_secret
|
||||||
with open(cookie_secret_path, 'w') as f:
|
|
||||||
f.write(COOKIE_SECRET)
|
|
||||||
|
|
||||||
_LOGGER.info("Starting dashboard web server on port %s and configuration dir %s...",
|
_LOGGER.info("Starting dashboard web server on port %s and configuration dir %s...",
|
||||||
args.port, CONFIG_DIR)
|
args.port, CONFIG_DIR)
|
||||||
|
|
|
@ -212,3 +212,8 @@ ul.stepper:not(.horizontal) .step.active::before, ul.stepper:not(.horizontal) .s
|
||||||
margin-right: -4.5px;
|
margin-right: -4.5px;
|
||||||
margin-left: -5.5px;
|
margin-left: -5.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flash-using-esphomeflasher {
|
||||||
|
vertical-align: middle;
|
||||||
|
color: #666 !important;
|
||||||
|
}
|
||||||
|
|
|
@ -157,7 +157,7 @@ document.querySelectorAll(".action-show-logs").forEach((showLogs) => {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
if (data.event === "line") {
|
if (data.event === "line") {
|
||||||
const msg = data.data;
|
const msg = data.data;
|
||||||
log.innerHTML += colorReplace(msg);
|
log.insertAdjacentHTML('beforeend', colorReplace(msg));
|
||||||
} else if (data.event === "exit") {
|
} else if (data.event === "exit") {
|
||||||
if (data.code === 0) {
|
if (data.code === 0) {
|
||||||
M.toast({html: "Program exited successfully."});
|
M.toast({html: "Program exited successfully."});
|
||||||
|
@ -205,7 +205,7 @@ document.querySelectorAll(".action-upload").forEach((upload) => {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
if (data.event === "line") {
|
if (data.event === "line") {
|
||||||
const msg = data.data;
|
const msg = data.data;
|
||||||
log.innerHTML += colorReplace(msg);
|
log.insertAdjacentHTML('beforeend', colorReplace(msg));
|
||||||
} else if (data.event === "exit") {
|
} else if (data.event === "exit") {
|
||||||
if (data.code === 0) {
|
if (data.code === 0) {
|
||||||
M.toast({html: "Program exited successfully."});
|
M.toast({html: "Program exited successfully."});
|
||||||
|
@ -254,7 +254,7 @@ document.querySelectorAll(".action-validate").forEach((upload) => {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
if (data.event === "line") {
|
if (data.event === "line") {
|
||||||
const msg = data.data;
|
const msg = data.data;
|
||||||
log.innerHTML += colorReplace(msg);
|
log.insertAdjacentHTML('beforeend', colorReplace(msg));
|
||||||
} else if (data.event === "exit") {
|
} else if (data.event === "exit") {
|
||||||
if (data.code === 0) {
|
if (data.code === 0) {
|
||||||
M.toast({
|
M.toast({
|
||||||
|
@ -311,7 +311,7 @@ document.querySelectorAll(".action-compile").forEach((upload) => {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
if (data.event === "line") {
|
if (data.event === "line") {
|
||||||
const msg = data.data;
|
const msg = data.data;
|
||||||
log.innerHTML += colorReplace(msg);
|
log.insertAdjacentHTML('beforeend', colorReplace(msg));
|
||||||
} else if (data.event === "exit") {
|
} else if (data.event === "exit") {
|
||||||
if (data.code === 0) {
|
if (data.code === 0) {
|
||||||
M.toast({html: "Program exited successfully."});
|
M.toast({html: "Program exited successfully."});
|
||||||
|
@ -367,7 +367,7 @@ document.querySelectorAll(".action-clean-mqtt").forEach((btn) => {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
if (data.event === "line") {
|
if (data.event === "line") {
|
||||||
const msg = data.data;
|
const msg = data.data;
|
||||||
log.innerHTML += colorReplace(msg);
|
log.insertAdjacentHTML('beforeend', colorReplace(msg));
|
||||||
} else if (data.event === "exit") {
|
} else if (data.event === "exit") {
|
||||||
stopLogsButton.innerHTML = "Close";
|
stopLogsButton.innerHTML = "Close";
|
||||||
stopped = true;
|
stopped = true;
|
||||||
|
@ -409,7 +409,7 @@ document.querySelectorAll(".action-clean").forEach((btn) => {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
if (data.event === "line") {
|
if (data.event === "line") {
|
||||||
const msg = data.data;
|
const msg = data.data;
|
||||||
log.innerHTML += colorReplace(msg);
|
log.insertAdjacentHTML('beforeend', colorReplace(msg));
|
||||||
} else if (data.event === "exit") {
|
} else if (data.event === "exit") {
|
||||||
if (data.code === 0) {
|
if (data.code === 0) {
|
||||||
M.toast({html: "Program exited successfully."});
|
M.toast({html: "Program exited successfully."});
|
||||||
|
@ -457,7 +457,7 @@ document.querySelectorAll(".action-hass-config").forEach((btn) => {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
if (data.event === "line") {
|
if (data.event === "line") {
|
||||||
const msg = data.data;
|
const msg = data.data;
|
||||||
log.innerHTML += colorReplace(msg);
|
log.insertAdjacentHTML('beforeend', colorReplace(msg));
|
||||||
} else if (data.event === "exit") {
|
} else if (data.event === "exit") {
|
||||||
if (data.code === 0) {
|
if (data.code === 0) {
|
||||||
M.toast({html: "Program exited successfully."});
|
M.toast({html: "Program exited successfully."});
|
||||||
|
|
|
@ -67,7 +67,7 @@
|
||||||
{% if entry.update_available %}
|
{% if entry.update_available %}
|
||||||
<p class="update-available" data-node="{{ entry.filename }}">
|
<p class="update-available" data-node="{{ entry.filename }}">
|
||||||
<i class="material-icons">system_update</i>
|
<i class="material-icons">system_update</i>
|
||||||
Update Available!
|
Update Available! {{ entry.update_old }} ➡️{{ entry.update_new }}
|
||||||
</p>
|
</p>
|
||||||
{% end %}
|
{% end %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -110,6 +110,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
<a href="https://esphomelib.com/esphomeyaml/guides/faq.html#i-can-t-get-flashing-over-usb-to-work" target="_blank"
|
||||||
|
class="tooltipped" data-position="left" data-tooltip="Flash using esphomeflasher">
|
||||||
|
<i class="material-icons flash-using-esphomeflasher">help_outline</i>
|
||||||
|
</a>
|
||||||
<a class="modal-close waves-effect waves-green btn-flat stop-logs">Stop</a>
|
<a class="modal-close waves-effect waves-green btn-flat stop-logs">Stop</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -122,6 +126,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
<a href="https://esphomelib.com/esphomeyaml/guides/faq.html#i-can-t-get-flashing-over-usb-to-work" target="_blank"
|
||||||
|
class="tooltipped" data-position="left" data-tooltip="Flash using esphomeflasher">
|
||||||
|
<i class="material-icons flash-using-esphomeflasher">help_outline</i>
|
||||||
|
</a>
|
||||||
<a class="modal-close waves-effect waves-green btn-flat disabled download-binary">Download Binary</a>
|
<a class="modal-close waves-effect waves-green btn-flat disabled download-binary">Download Binary</a>
|
||||||
<a class="modal-close waves-effect waves-green btn-flat stop-logs">Stop</a>
|
<a class="modal-close waves-effect waves-green btn-flat stop-logs">Stop</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -427,7 +435,7 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<a href="#!" class="modal-close waves-effect waves-green btn-flat">Abort</a>
|
<a class="modal-close waves-effect waves-green btn-flat">Abort</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,11 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>esphomeyaml Dashboard</title>
|
<title>esphomeyaml Dashboard</title>
|
||||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="/static/materialize.min.css">
|
<link rel="stylesheet" href="/static/materialize.min.css?v=1">
|
||||||
<link rel="stylesheet" href="/static/esphomeyaml.css">
|
<link rel="stylesheet" href="/static/esphomeyaml.css?v=1">
|
||||||
<link rel="shortcut icon" href="/static/favicon.ico">
|
<link rel="shortcut icon" href="/static/favicon.ico?v=1">
|
||||||
|
|
||||||
<script src="/static/materialize.min.js"></script>
|
<script src="/static/materialize.min.js?v=1"></script>
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -263,7 +263,7 @@ def resolve_ip_address(host):
|
||||||
return ip
|
return ip
|
||||||
|
|
||||||
|
|
||||||
def run_ota(remote_host, remote_port, password, filename):
|
def run_ota_impl_(remote_host, remote_port, password, filename):
|
||||||
ip = resolve_ip_address(remote_host)
|
ip = resolve_ip_address(remote_host)
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
sock.settimeout(10.0)
|
sock.settimeout(10.0)
|
||||||
|
@ -287,6 +287,14 @@ def run_ota(remote_host, remote_port, password, filename):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def run_ota(remote_host, remote_port, password, filename):
|
||||||
|
try:
|
||||||
|
return run_ota_impl_(remote_host, remote_port, password, filename)
|
||||||
|
except OTAError as err:
|
||||||
|
_LOGGER.error(err)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
def run_legacy_ota(verbose, host_port, remote_host, remote_port, password, filename):
|
def run_legacy_ota(verbose, host_port, remote_host, remote_port, password, filename):
|
||||||
from esphomeyaml import espota
|
from esphomeyaml import espota
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
|
import binascii
|
||||||
import codecs
|
import codecs
|
||||||
|
from datetime import datetime, timedelta
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import threading
|
||||||
|
|
||||||
from esphomeyaml import const
|
from esphomeyaml import const
|
||||||
from esphomeyaml.core import CORE
|
from esphomeyaml.core import CORE
|
||||||
|
@ -11,6 +15,9 @@ from esphomeyaml.core import CoreType # noqa
|
||||||
from typing import Any, Dict, Optional # noqa
|
from typing import Any, Dict, Optional # noqa
|
||||||
|
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def storage_path(): # type: () -> str
|
def storage_path(): # type: () -> str
|
||||||
return CORE.relative_path('.esphomeyaml', '{}.json'.format(CORE.config_filename))
|
return CORE.relative_path('.esphomeyaml', '{}.json'.format(CORE.config_filename))
|
||||||
|
|
||||||
|
@ -19,11 +26,15 @@ def ext_storage_path(base_path, config_filename): # type: (str, str) -> str
|
||||||
return os.path.join(base_path, '.esphomeyaml', '{}.json'.format(config_filename))
|
return os.path.join(base_path, '.esphomeyaml', '{}.json'.format(config_filename))
|
||||||
|
|
||||||
|
|
||||||
|
def esphomeyaml_storage_path(base_path): # type: (str) -> str
|
||||||
|
return os.path.join(base_path, '.esphomeyaml', 'esphomeyaml.json')
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-instance-attributes
|
# pylint: disable=too-many-instance-attributes
|
||||||
class StorageJSON(object):
|
class StorageJSON(object):
|
||||||
def __init__(self, storage_version, name, esphomelib_version, esphomeyaml_version,
|
def __init__(self, storage_version, name, esphomelib_version, esphomeyaml_version,
|
||||||
src_version, arduino_version, address, esp_platform, board, build_path,
|
src_version, arduino_version, address, esp_platform, board, build_path,
|
||||||
firmware_bin_path):
|
firmware_bin_path, use_legacy_ota):
|
||||||
# Version of the storage JSON schema
|
# Version of the storage JSON schema
|
||||||
assert storage_version is None or isinstance(storage_version, int)
|
assert storage_version is None or isinstance(storage_version, int)
|
||||||
self.storage_version = storage_version # type: int
|
self.storage_version = storage_version # type: int
|
||||||
|
@ -50,6 +61,8 @@ class StorageJSON(object):
|
||||||
self.build_path = build_path # type: str
|
self.build_path = build_path # type: str
|
||||||
# The absolute path to the firmware binary
|
# The absolute path to the firmware binary
|
||||||
self.firmware_bin_path = firmware_bin_path # type: str
|
self.firmware_bin_path = firmware_bin_path # type: str
|
||||||
|
# Whether to use legacy OTA, will be off after the first successful flash
|
||||||
|
self.use_legacy_ota = use_legacy_ota
|
||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
return {
|
return {
|
||||||
|
@ -64,6 +77,7 @@ class StorageJSON(object):
|
||||||
'board': self.board,
|
'board': self.board,
|
||||||
'build_path': self.build_path,
|
'build_path': self.build_path,
|
||||||
'firmware_bin_path': self.firmware_bin_path,
|
'firmware_bin_path': self.firmware_bin_path,
|
||||||
|
'use_legacy_ota': self.use_legacy_ota,
|
||||||
}
|
}
|
||||||
|
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
|
@ -75,7 +89,7 @@ class StorageJSON(object):
|
||||||
f_handle.write(self.to_json())
|
f_handle.write(self.to_json())
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_esphomeyaml_core(esph): # type: (CoreType) -> StorageJSON
|
def from_esphomeyaml_core(esph, old): # type: (CoreType, Optional[StorageJSON]) -> StorageJSON
|
||||||
return StorageJSON(
|
return StorageJSON(
|
||||||
storage_version=1,
|
storage_version=1,
|
||||||
name=esph.name,
|
name=esph.name,
|
||||||
|
@ -88,6 +102,7 @@ class StorageJSON(object):
|
||||||
board=esph.board,
|
board=esph.board,
|
||||||
build_path=esph.build_path,
|
build_path=esph.build_path,
|
||||||
firmware_bin_path=esph.firmware_bin,
|
firmware_bin_path=esph.firmware_bin,
|
||||||
|
use_legacy_ota=True if old is None else old.use_legacy_ota,
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -105,6 +120,7 @@ class StorageJSON(object):
|
||||||
board=board,
|
board=board,
|
||||||
build_path=None,
|
build_path=None,
|
||||||
firmware_bin_path=None,
|
firmware_bin_path=None,
|
||||||
|
use_legacy_ota=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -123,9 +139,10 @@ class StorageJSON(object):
|
||||||
board = storage.get('board')
|
board = storage.get('board')
|
||||||
build_path = storage.get('build_path')
|
build_path = storage.get('build_path')
|
||||||
firmware_bin_path = storage.get('firmware_bin_path')
|
firmware_bin_path = storage.get('firmware_bin_path')
|
||||||
|
use_legacy_ota = storage.get('use_legacy_ota')
|
||||||
return StorageJSON(storage_version, name, esphomelib_version, esphomeyaml_version,
|
return StorageJSON(storage_version, name, esphomelib_version, esphomeyaml_version,
|
||||||
src_version, arduino_version, address, esp_platform, board, build_path,
|
src_version, arduino_version, address, esp_platform, board, build_path,
|
||||||
firmware_bin_path)
|
firmware_bin_path, use_legacy_ota)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load(path): # type: (str) -> Optional[StorageJSON]
|
def load(path): # type: (str) -> Optional[StorageJSON]
|
||||||
|
@ -136,3 +153,144 @@ class StorageJSON(object):
|
||||||
|
|
||||||
def __eq__(self, o): # type: (Any) -> bool
|
def __eq__(self, o): # type: (Any) -> bool
|
||||||
return isinstance(o, StorageJSON) and self.as_dict() == o.as_dict()
|
return isinstance(o, StorageJSON) and self.as_dict() == o.as_dict()
|
||||||
|
|
||||||
|
|
||||||
|
class EsphomeyamlStorageJSON(object):
|
||||||
|
def __init__(self, storage_version, cookie_secret, last_update_check,
|
||||||
|
remote_version):
|
||||||
|
# Version of the storage JSON schema
|
||||||
|
assert storage_version is None or isinstance(storage_version, int)
|
||||||
|
self.storage_version = storage_version # type: int
|
||||||
|
# The cookie secret for the dashboard
|
||||||
|
self.cookie_secret = cookie_secret # type: str
|
||||||
|
# The last time esphomeyaml checked for an update as an isoformat encoded str
|
||||||
|
self.last_update_check_str = last_update_check # type: str
|
||||||
|
# Cache of the version gotten in the last version check
|
||||||
|
self.remote_version = remote_version # type: Optional[str]
|
||||||
|
|
||||||
|
def as_dict(self): # type: () -> dict
|
||||||
|
return {
|
||||||
|
'storage_version': self.storage_version,
|
||||||
|
'cookie_secret': self.cookie_secret,
|
||||||
|
'last_update_check': self.last_update_check_str,
|
||||||
|
'remote_version': self.remote_version,
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def last_update_check(self): # type: () -> Optional[datetime]
|
||||||
|
try:
|
||||||
|
return datetime.strptime(self.last_update_check_str, "%Y-%m-%dT%H:%M:%S")
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
return None
|
||||||
|
|
||||||
|
@last_update_check.setter
|
||||||
|
def last_update_check(self, new): # type: (datetime) -> None
|
||||||
|
self.last_update_check_str = new.strftime("%Y-%m-%dT%H:%M:%S")
|
||||||
|
|
||||||
|
def to_json(self): # type: () -> dict
|
||||||
|
return json.dumps(self.as_dict(), indent=2) + u'\n'
|
||||||
|
|
||||||
|
def save(self, path): # type: (str) -> None
|
||||||
|
mkdir_p(os.path.dirname(path))
|
||||||
|
with codecs.open(path, 'w', encoding='utf-8') as f_handle:
|
||||||
|
f_handle.write(self.to_json())
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _load_impl(path): # type: (str) -> Optional[EsphomeyamlStorageJSON]
|
||||||
|
with codecs.open(path, 'r', encoding='utf-8') as f_handle:
|
||||||
|
text = f_handle.read()
|
||||||
|
storage = json.loads(text, encoding='utf-8')
|
||||||
|
storage_version = storage['storage_version']
|
||||||
|
cookie_secret = storage.get('cookie_secret')
|
||||||
|
last_update_check = storage.get('last_update_check')
|
||||||
|
remote_version = storage.get('remote_version')
|
||||||
|
return EsphomeyamlStorageJSON(storage_version, cookie_secret, last_update_check,
|
||||||
|
remote_version)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def load(path): # type: (str) -> Optional[EsphomeyamlStorageJSON]
|
||||||
|
try:
|
||||||
|
return EsphomeyamlStorageJSON._load_impl(path)
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_default(): # type: () -> EsphomeyamlStorageJSON
|
||||||
|
return EsphomeyamlStorageJSON(
|
||||||
|
storage_version=1,
|
||||||
|
cookie_secret=binascii.hexlify(os.urandom(64)),
|
||||||
|
last_update_check=None,
|
||||||
|
remote_version=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __eq__(self, o): # type: (Any) -> bool
|
||||||
|
return isinstance(o, EsphomeyamlStorageJSON) and self.as_dict() == o.as_dict()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_do_esphomeyaml_update_check(self): # type: () -> bool
|
||||||
|
if self.last_update_check is None:
|
||||||
|
return True
|
||||||
|
return self.last_update_check + timedelta(days=3) < datetime.utcnow()
|
||||||
|
|
||||||
|
|
||||||
|
class CheckForUpdateThread(threading.Thread):
|
||||||
|
def __init__(self, path):
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
self._path = path
|
||||||
|
|
||||||
|
@property
|
||||||
|
def docs_base(self):
|
||||||
|
return 'https://beta.esphomelib.com' if 'b' in const.__version__ else \
|
||||||
|
'https://esphomelib.com'
|
||||||
|
|
||||||
|
def fetch_remote_version(self):
|
||||||
|
import requests
|
||||||
|
|
||||||
|
storage = EsphomeyamlStorageJSON.load(self._path) or \
|
||||||
|
EsphomeyamlStorageJSON.get_default()
|
||||||
|
if not storage.should_do_esphomeyaml_update_check:
|
||||||
|
return storage
|
||||||
|
|
||||||
|
req = requests.get('{}/_static/version'.format(self.docs_base))
|
||||||
|
req.raise_for_status()
|
||||||
|
storage.remote_version = req.text
|
||||||
|
storage.last_update_check = datetime.utcnow()
|
||||||
|
storage.save(self._path)
|
||||||
|
return storage
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_version(ver):
|
||||||
|
vstr = '.'.join(map(str, ver.version))
|
||||||
|
if ver.prerelease:
|
||||||
|
vstr += ver.prerelease[0] + str(ver.prerelease[1])
|
||||||
|
return vstr
|
||||||
|
|
||||||
|
def cmp_versions(self, storage):
|
||||||
|
# pylint: disable=no-name-in-module, import-error
|
||||||
|
from distutils.version import StrictVersion
|
||||||
|
|
||||||
|
remote_version = StrictVersion(storage.remote_version)
|
||||||
|
self_version = StrictVersion(const.__version__)
|
||||||
|
if remote_version > self_version:
|
||||||
|
_LOGGER.warn("*" * 80)
|
||||||
|
_LOGGER.warn("A new version of esphomeyaml is available: %s (this is %s)",
|
||||||
|
self.format_version(remote_version), self.format_version(self_version))
|
||||||
|
_LOGGER.warn("Changelog: %s/esphomeyaml/changelog/index.html", self.docs_base)
|
||||||
|
_LOGGER.warn("Update Instructions: %s/esphomeyaml/guides/faq.html"
|
||||||
|
"#how-do-i-update-to-the-latest-version", self.docs_base)
|
||||||
|
_LOGGER.warn("*" * 80)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
storage = self.fetch_remote_version()
|
||||||
|
self.cmp_versions(storage)
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def start_update_check_thread(path):
|
||||||
|
# dummy call to strptime as python 2.7 has a bug with strptime when importing from threads
|
||||||
|
datetime.strptime('20180101', '%Y%m%d')
|
||||||
|
thread = CheckForUpdateThread(path)
|
||||||
|
thread.start()
|
||||||
|
return thread
|
||||||
|
|
|
@ -184,16 +184,36 @@ def migrate_src_version(old, new):
|
||||||
migrate_src_version_0_to_1()
|
migrate_src_version_0_to_1()
|
||||||
|
|
||||||
|
|
||||||
|
def storage_should_clean(old, new): # type: (StorageJSON, StorageJSON) -> bool
|
||||||
|
if old is None:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if old.esphomelib_version != new.esphomelib_version:
|
||||||
|
return True
|
||||||
|
if old.esphomeyaml_version != new.esphomeyaml_version:
|
||||||
|
return True
|
||||||
|
if old.src_version != new.src_version:
|
||||||
|
return True
|
||||||
|
if old.arduino_version != new.arduino_version:
|
||||||
|
return True
|
||||||
|
if old.board != new.board:
|
||||||
|
return True
|
||||||
|
if old.build_path != new.build_path:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def update_storage_json():
|
def update_storage_json():
|
||||||
path = storage_path()
|
path = storage_path()
|
||||||
old = StorageJSON.load(path)
|
old = StorageJSON.load(path)
|
||||||
new = StorageJSON.from_esphomeyaml_core(CORE)
|
new = StorageJSON.from_esphomeyaml_core(CORE, old)
|
||||||
if old == new:
|
if old == new:
|
||||||
return
|
return
|
||||||
|
|
||||||
old_src_version = old.src_version if old is not None else 0
|
old_src_version = old.src_version if old is not None else 0
|
||||||
migrate_src_version(old_src_version, new.src_version)
|
migrate_src_version(old_src_version, new.src_version)
|
||||||
|
|
||||||
|
if storage_should_clean(old, new):
|
||||||
_LOGGER.info("Core config or version changed, cleaning build files...")
|
_LOGGER.info("Core config or version changed, cleaning build files...")
|
||||||
clean_build()
|
clean_build()
|
||||||
|
|
||||||
|
|
|
@ -348,7 +348,7 @@ def represent_time_period(dumper, data):
|
||||||
|
|
||||||
|
|
||||||
def represent_lambda(_, data):
|
def represent_lambda(_, data):
|
||||||
node = yaml.ScalarNode(tag='!lambda', value=data.value, style='>')
|
node = yaml.ScalarNode(tag='!lambda', value=data.value, style='|')
|
||||||
return node
|
return node
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue