This commit is contained in:
Otto Winter 2018-09-23 18:58:41 +02:00
parent 74c70509c2
commit 2abbe1bca3
No known key found for this signature in database
GPG key ID: DB66C0BE6013F97E
17 changed files with 653 additions and 219 deletions

View file

@ -4,7 +4,7 @@ python:
- "2.7"
install:
- pip install -r requirements.txt
- pip install tornado esptool flake8==3.5.0 pylint==1.8.4
- pip install tornado esptool flake8==3.5.0 pylint==1.8.4 tzlocal pillow
script:
- flake8 esphomeyaml
- pylint esphomeyaml

View file

@ -7,11 +7,11 @@ import random
import sys
from datetime import datetime
from esphomeyaml import const, core, mqtt, wizard, writer, yaml_util
from esphomeyaml.config import core_to_code, get_component, iter_components, read_config
from esphomeyaml import const, core, core_config, mqtt, wizard, writer, yaml_util
from esphomeyaml.config import get_component, iter_components, read_config
from esphomeyaml.const import CONF_BAUD_RATE, CONF_BUILD_PATH, CONF_DOMAIN, CONF_ESPHOMEYAML, \
CONF_HOSTNAME, CONF_LOGGER, CONF_MANUAL_IP, CONF_NAME, CONF_STATIC_IP, CONF_WIFI, \
ESP_PLATFORM_ESP8266
CONF_HOSTNAME, CONF_LOGGER, CONF_MANUAL_IP, CONF_NAME, CONF_STATIC_IP, CONF_USE_CUSTOM_CODE, \
CONF_WIFI, ESP_PLATFORM_ESP8266
from esphomeyaml.core import ESPHomeYAMLError
from esphomeyaml.helpers import AssignmentExpression, Expression, RawStatement, _EXPRESSIONS, add, \
add_job, color, flush_tasks, indent, quote, statement
@ -123,7 +123,7 @@ def run_miniterm(config, port, escape=False):
def write_cpp(config):
_LOGGER.info("Generating C++ source...")
add_job(core_to_code, config[CONF_ESPHOMEYAML], domain='esphomeyaml')
add_job(core_config.to_code, config[CONF_ESPHOMEYAML], domain='esphomeyaml')
for domain in PRE_INITIALIZE:
if domain == CONF_ESPHOMEYAML or domain not in config:
continue
@ -139,7 +139,7 @@ def write_cpp(config):
add(RawStatement(''))
all_code = []
for exp in _EXPRESSIONS:
if core.SIMPLIFY:
if not config[CONF_ESPHOMEYAML][CONF_USE_CUSTOM_CODE]:
if isinstance(exp, Expression) and not exp.required:
continue
if isinstance(exp, AssignmentExpression) and not exp.obj.required:
@ -302,7 +302,7 @@ def command_compile(args, config):
return exit_code
if args.only_generate:
_LOGGER.info(u"Successfully generated source code.")
return 0;
return 0
exit_code = compile_program(args, config)
if exit_code != 0:
return exit_code
@ -388,7 +388,8 @@ def parse_args(argv):
subparsers.required = True
subparsers.add_parser('config', help='Validate the configuration and spit it out.')
parser_compile = subparsers.add_parser('compile', help='Read the configuration and compile a program.')
parser_compile = subparsers.add_parser('compile',
help='Read the configuration and compile a program.')
parser_compile.add_argument('--only-generate',
help="Only generate source code, do not compile.",
action='store_true')

View file

@ -5,7 +5,7 @@ from esphomeyaml import automation
from esphomeyaml.const import CONF_DEVICE_CLASS, CONF_ID, CONF_INTERNAL, CONF_INVERTED, \
CONF_MAX_LENGTH, CONF_MIN_LENGTH, CONF_MQTT_ID, CONF_ON_CLICK, CONF_ON_DOUBLE_CLICK, \
CONF_ON_PRESS, CONF_ON_RELEASE, CONF_TRIGGER_ID, CONF_FILTERS, CONF_INVERT, CONF_DELAYED_ON, \
CONF_DELAYED_OFF, CONF_LAMBDA
CONF_DELAYED_OFF, CONF_LAMBDA, CONF_HEARTBEAT
from esphomeyaml.helpers import App, NoArg, Pvariable, add, add_job, esphomelib_ns, \
setup_mqtt_component, bool_, process_lambda, ArrayInitializer
@ -30,6 +30,7 @@ InvertFilter = binary_sensor_ns.InvertFilter
LambdaFilter = binary_sensor_ns.LambdaFilter
DelayedOnFilter = binary_sensor_ns.DelayedOnFilter
DelayedOffFilter = binary_sensor_ns.DelayedOffFilter
HeartbeatFilter = binary_sensor_ns.HeartbeatFilter
MQTTBinarySensorComponent = binary_sensor_ns.MQTTBinarySensorComponent
FILTER_KEYS = [CONF_INVERT, CONF_DELAYED_ON, CONF_DELAYED_OFF, CONF_LAMBDA]
@ -38,6 +39,7 @@ FILTERS_SCHEMA = vol.All(cv.ensure_list, [vol.All({
vol.Optional(CONF_INVERT): None,
vol.Optional(CONF_DELAYED_ON): cv.positive_time_period_milliseconds,
vol.Optional(CONF_DELAYED_OFF): cv.positive_time_period_milliseconds,
vol.Optional(CONF_HEARTBEAT): cv.positive_time_period_milliseconds,
vol.Optional(CONF_LAMBDA): cv.lambda_,
}, cv.has_exactly_one_key(*FILTER_KEYS))])
@ -82,6 +84,8 @@ def setup_filter(config):
yield App.register_component(DelayedOffFilter.new(config[CONF_DELAYED_OFF]))
elif CONF_DELAYED_ON in config:
yield App.register_component(DelayedOnFilter.new(config[CONF_DELAYED_ON]))
elif CONF_HEARTBEAT in config:
yield App.register_component(HeartbeatFilter.new(config[CONF_HEARTBEAT]))
elif CONF_LAMBDA in config:
lambda_ = None
for lambda_ in process_lambda(config[CONF_LAMBDA], [(bool_, 'x')]):

View file

@ -1,6 +1,4 @@
# coding=utf-8
import os.path
import voluptuous as vol
import esphomeyaml.config_validation as cv
@ -8,7 +6,8 @@ from esphomeyaml import core
from esphomeyaml.components import display
from esphomeyaml.const import CONF_FILE, CONF_GLYPHS, CONF_ID, CONF_SIZE
from esphomeyaml.core import HexInt
from esphomeyaml.helpers import App, ArrayInitializer, MockObj, Pvariable, RawExpression, add
from esphomeyaml.helpers import App, ArrayInitializer, MockObj, Pvariable, RawExpression, add, \
relative_path
DEPENDENCIES = ['display']
@ -57,17 +56,13 @@ def validate_pillow_installed(value):
def validate_truetype_file(value):
value = cv.string(value)
path = os.path.join(os.path.dirname(core.CONFIG_PATH), value)
if not os.path.isfile(path):
raise vol.Invalid(u"Could not find file '{}'. Please make sure it exists.".format(path))
if value.endswith('.zip'): # for Google Fonts downloads
raise vol.Invalid(u"Please unzip the font archive '{}' first and then use the .ttf files "
u"inside.".format(value))
if not value.endswith('.ttf'):
raise vol.Invalid(u"Only truetype (.ttf) files are supported. Please make sure you're "
u"using the correct format or rename the extension to .ttf")
return value
return cv.file_(value)
DEFAULT_GLYPHS = u' !"%()+,-.:0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°'
@ -88,7 +83,7 @@ def to_code(config):
from PIL import ImageFont
for conf in config:
path = os.path.join(os.path.dirname(core.CONFIG_PATH), conf[CONF_FILE])
path = relative_path(conf[CONF_FILE])
try:
font = ImageFont.truetype(path, conf[CONF_SIZE])
except Exception as e:

View file

@ -1,6 +1,5 @@
# coding=utf-8
import logging
import os.path
import voluptuous as vol
@ -9,7 +8,8 @@ from esphomeyaml import core
from esphomeyaml.components import display, font
from esphomeyaml.const import CONF_FILE, CONF_ID, CONF_RESIZE
from esphomeyaml.core import HexInt
from esphomeyaml.helpers import App, ArrayInitializer, MockObj, Pvariable, RawExpression, add
from esphomeyaml.helpers import App, ArrayInitializer, MockObj, Pvariable, RawExpression, add, \
relative_path
_LOGGER = logging.getLogger(__name__)
@ -17,20 +17,11 @@ DEPENDENCIES = ['display']
Image_ = display.display_ns.Image
def validate_image_file(value):
value = cv.string(value)
path = os.path.join(os.path.dirname(core.CONFIG_PATH), value)
if not os.path.isfile(path):
raise vol.Invalid(u"Could not find file '{}'. Please make sure it exists.".format(path))
return value
CONF_RAW_DATA_ID = 'raw_data_id'
IMAGE_SCHEMA = vol.Schema({
vol.Required(CONF_ID): cv.declare_variable_id(Image_),
vol.Required(CONF_FILE): validate_image_file,
vol.Required(CONF_FILE): cv.file_,
vol.Optional(CONF_RESIZE): cv.dimensions,
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_variable_id(None),
})
@ -42,7 +33,7 @@ def to_code(config):
from PIL import Image
for conf in config:
path = os.path.join(os.path.dirname(core.CONFIG_PATH), conf[CONF_FILE])
path = relative_path(conf[CONF_FILE])
try:
image = Image.open(path)
except Exception as e:

View file

@ -5,9 +5,10 @@ import math
import voluptuous as vol
import esphomeyaml.config_validation as cv
from esphomeyaml.const import CONF_TIMEZONE
from esphomeyaml.helpers import add, add_job, esphomelib_ns
from esphomeyaml import automation
from esphomeyaml.const import CONF_CRON, CONF_DAYS_OF_MONTH, CONF_DAYS_OF_WEEK, CONF_HOURS, \
CONF_MINUTES, CONF_MONTHS, CONF_ON_TIME, CONF_SECONDS, CONF_TIMEZONE, CONF_TRIGGER_ID
from esphomeyaml.helpers import App, NoArg, Pvariable, add, add_job, esphomelib_ns
_LOGGER = logging.getLogger(__name__)
@ -16,6 +17,7 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
})
time_ns = esphomelib_ns.namespace('time')
CronTrigger = time_ns.CronTrigger
def _tz_timedelta(td):
@ -117,14 +119,155 @@ def detect_tz():
return tzbase + tzext
def _parse_cron_int(value, special_mapping, message):
special_mapping = special_mapping or {}
if isinstance(value, (str, unicode)) and value in special_mapping:
return special_mapping[value]
try:
return int(value)
except ValueError:
raise vol.Invalid(message.format(value))
def _parse_cron_part(part, min_value, max_value, special_mapping):
if part == '*' or part == '?':
return set(x for x in range(min_value, max_value + 1))
if '/' in part:
data = part.split('/')
if len(data) > 2:
raise vol.Invalid(u"Can't have more than two '/' in one time expression, got {}"
.format(part))
offset, repeat = data
offset_n = 0
if offset:
offset_n = _parse_cron_int(offset, special_mapping,
u"Offset for '/' time expression must be an integer, got {}")
try:
repeat_n = int(repeat)
except ValueError:
raise vol.Invalid(u"Repeat for '/' time expression must be an integer, got {}"
.format(repeat))
return set(x for x in range(offset_n, max_value + 1, repeat_n))
if '-' in part:
data = part.split('-')
if len(data) > 2:
raise vol.Invalid(u"Can't have more than two '-' in range time expression '{}'"
.format(part))
begin, end = data
begin_n = _parse_cron_int(begin, special_mapping, u"Number for time range must be integer, "
u"got {}")
end_n = _parse_cron_int(end, special_mapping, u"Number for time range must be integer, "
u"got {}")
if end_n < begin_n:
return set(x for x in range(end_n, max_value + 1)) | \
set(x for x in range(min_value, begin_n + 1))
return set(x for x in range(begin_n, end_n + 1))
return {_parse_cron_int(part, special_mapping, u"Number for time expression must be an "
u"integer, got {}")}
def cron_expression_validator(name, min_value, max_value, special_mapping=None):
def validator(value):
if isinstance(value, list):
for v in value:
if not isinstance(v, int):
raise vol.Invalid(
"Expected integer for {} '{}', got {}".format(v, name, type(v)))
if v < min_value or v > max_value:
raise vol.Invalid(
"{} {} is out of range (min={} max={}).".format(name, v, min_value,
max_value))
return list(sorted(value))
value = cv.string(value)
values = set()
for part in value.split(','):
values |= _parse_cron_part(part, min_value, max_value, special_mapping)
return validator(list(values))
return validator
validate_cron_seconds = cron_expression_validator('seconds', 0, 60)
validate_cron_minutes = cron_expression_validator('minutes', 0, 59)
validate_cron_hours = cron_expression_validator('hours', 0, 23)
validate_cron_days_of_month = cron_expression_validator('days of month', 1, 31)
validate_cron_months = cron_expression_validator('months', 1, 12, {
'JAN': 1, 'FEB': 2, 'MAR': 3, 'APR': 4, 'MAY': 5, 'JUN': 6, 'JUL': 7, 'AUG': 8,
'SEP': 9, 'OCT': 10, 'NOV': 11, 'DEC': 12
})
validate_cron_days_of_week = cron_expression_validator('days of week', 1, 7, {
'SUN': 1, 'MON': 2, 'TUE': 3, 'WED': 4, 'THU': 5, 'FRI': 6, 'SAT': 7
})
CRON_KEYS = [CONF_SECONDS, CONF_MINUTES, CONF_HOURS, CONF_DAYS_OF_MONTH, CONF_MONTHS,
CONF_DAYS_OF_WEEK]
def validate_cron_raw(value):
value = cv.string(value)
value = value.split(' ')
if len(value) != 6:
raise vol.Invalid("Cron expression must consist of exactly 6 space-separated parts, "
"not {}".format(len(value)))
seconds, minutes, hours, days_of_month, months, days_of_week = value
return {
CONF_SECONDS: validate_cron_seconds(seconds),
CONF_MINUTES: validate_cron_minutes(minutes),
CONF_HOURS: validate_cron_hours(hours),
CONF_DAYS_OF_MONTH: validate_cron_days_of_month(days_of_month),
CONF_MONTHS: validate_cron_months(months),
CONF_DAYS_OF_WEEK: validate_cron_days_of_week(days_of_week),
}
def validate_cron_keys(value):
if CONF_CRON in value:
for key in value.keys():
if key in CRON_KEYS:
raise vol.Invalid("Cannot use option {} when cron: is specified.".format(key))
cron_ = value[CONF_CRON]
value = {x: value[x] for x in value if x != CONF_CRON}
value.update(cron_)
return value
return cv.has_at_least_one_key(*CRON_KEYS)(value)
TIME_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_TIMEZONE, default=detect_tz): cv.string,
vol.Optional(CONF_ON_TIME): vol.All(cv.ensure_list, [vol.All(automation.validate_automation({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(CronTrigger),
vol.Optional(CONF_SECONDS): validate_cron_seconds,
vol.Optional(CONF_MINUTES): validate_cron_minutes,
vol.Optional(CONF_HOURS): validate_cron_hours,
vol.Optional(CONF_DAYS_OF_MONTH): validate_cron_days_of_month,
vol.Optional(CONF_MONTHS): validate_cron_months,
vol.Optional(CONF_DAYS_OF_WEEK): validate_cron_days_of_week,
vol.Optional(CONF_CRON): validate_cron_raw,
}), validate_cron_keys)]),
})
def setup_time_core_(time_var, config):
add(time_var.set_timezone(config[CONF_TIMEZONE]))
for conf in config.get(CONF_ON_TIME, []):
rhs = App.register_component(time_var.Pmake_cron_trigger())
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs)
for second in conf.get(CONF_SECONDS, [x for x in range(0, 61)]):
add(trigger.add_second(second))
for minute in conf.get(CONF_MINUTES, [x for x in range(0, 60)]):
add(trigger.add_minute(minute))
for hour in conf.get(CONF_HOURS, [x for x in range(0, 24)]):
add(trigger.add_hour(hour))
for day_of_month in conf.get(CONF_DAYS_OF_MONTH, [x for x in range(1, 32)]):
add(trigger.add_day_of_month(day_of_month))
for month in conf.get(CONF_MONTHS, [x for x in range(1, 13)]):
add(trigger.add_month(month))
for day_of_week in conf.get(CONF_DAYS_OF_WEEK, [x for x in range(1, 8)]):
add(trigger.add_day_of_week(day_of_week))
automation.build_automation(trigger, NoArg, conf)
def setup_time(time_var, config):
add_job(setup_time_core_, time_var, config)

View file

@ -8,7 +8,7 @@ from esphomeyaml.helpers import App, Pvariable
SNTPComponent = time_.time_ns.SNTPComponent
PLATFORM_SCHEMA = time_.TIME_PLATFORM_SCHEMA.extend({
vol.Required(CONF_ID): cv.declare_variable_id(SNTPComponent),
cv.GenerateID(): cv.declare_variable_id(SNTPComponent),
vol.Optional(CONF_SERVERS): vol.All(cv.ensure_list, [cv.string], vol.Length(max=3)),
vol.Optional(CONF_LAMBDA): cv.lambda_,
})

View file

@ -7,62 +7,20 @@ from collections import OrderedDict
import voluptuous as vol
from voluptuous.humanize import humanize_error
import esphomeyaml.config_validation as cv
from esphomeyaml import core, yaml_util, automation
from esphomeyaml.const import CONF_BOARD, CONF_BOARD_FLASH_MODE, CONF_ESPHOMEYAML, \
CONF_LIBRARY_URI, CONF_NAME, CONF_PLATFORM, CONF_SIMPLIFY, CONF_USE_BUILD_FLAGS, CONF_WIFI, \
ESP_PLATFORMS, ESP_PLATFORM_ESP32, ESP_PLATFORM_ESP8266, CONF_ON_BOOT, CONF_TRIGGER_ID, \
CONF_PRIORITY, CONF_ON_SHUTDOWN, CONF_BUILD_PATH
from esphomeyaml import core, yaml_util, core_config
from esphomeyaml.const import CONF_ESPHOMEYAML, CONF_PLATFORM, CONF_WIFI, ESP_PLATFORMS
from esphomeyaml.core import ESPHomeYAMLError
from esphomeyaml.helpers import App, add, color, esphomelib_ns, Pvariable, NoArg, const_char_p
from esphomeyaml.helpers import color
_LOGGER = logging.getLogger(__name__)
DEFAULT_LIBRARY_URI = u'https://github.com/OttoWinter/esphomelib.git#v1.7.0'
BUILD_FLASH_MODES = ['qio', 'qout', 'dio', 'dout']
StartupTrigger = esphomelib_ns.StartupTrigger
ShutdownTrigger = esphomelib_ns.ShutdownTrigger
CORE_SCHEMA = vol.Schema({
vol.Required(CONF_NAME): cv.valid_name,
vol.Required(CONF_PLATFORM): cv.string,
vol.Required(CONF_BOARD): cv.string,
vol.Optional(CONF_LIBRARY_URI, default=DEFAULT_LIBRARY_URI): cv.string,
vol.Optional(CONF_SIMPLIFY, default=True): cv.boolean,
vol.Optional(CONF_USE_BUILD_FLAGS, default=True): cv.boolean,
vol.Optional(CONF_BOARD_FLASH_MODE): vol.All(vol.Lower, cv.one_of(*BUILD_FLASH_MODES)),
vol.Optional(CONF_ON_BOOT): vol.All(cv.ensure_list, [automation.validate_automation({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(StartupTrigger),
vol.Optional(CONF_PRIORITY): vol.Coerce(float),
})]),
vol.Optional(CONF_ON_SHUTDOWN): vol.All(cv.ensure_list, [automation.validate_automation({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(ShutdownTrigger),
})]),
vol.Optional(CONF_BUILD_PATH): cv.string,
})
REQUIRED_COMPONENTS = [
CONF_ESPHOMEYAML, CONF_WIFI
]
_COMPONENT_CACHE = {}
_ALL_COMPONENTS = []
def core_to_code(config):
add(App.set_name(config[CONF_NAME]))
for conf in config.get(CONF_ON_BOOT, []):
rhs = App.register_component(StartupTrigger.new(conf.get(CONF_PRIORITY)))
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs)
automation.build_automation(trigger, NoArg, conf)
for conf in config.get(CONF_ON_SHUTDOWN, []):
trigger = Pvariable(conf[CONF_TRIGGER_ID], ShutdownTrigger.new())
automation.build_automation(trigger, const_char_p, conf)
def get_component(domain):
if domain in _COMPONENT_CACHE:
return _COMPONENT_CACHE[domain]
@ -171,9 +129,9 @@ def validate_config(config):
result.add_error(_format_config_error(ex, domain, config), domain, config)
try:
result[CONF_ESPHOMEYAML] = CORE_SCHEMA(config[CONF_ESPHOMEYAML])
result[CONF_ESPHOMEYAML] = core_config.CONFIG_SCHEMA(config[CONF_ESPHOMEYAML])
except vol.Invalid as ex:
_comp_error(ex, CONF_ESPHOMEYAML, config)
_comp_error(ex, CONF_ESPHOMEYAML, config[CONF_ESPHOMEYAML])
for domain, conf in config.iteritems():
domain = str(domain)
@ -288,23 +246,7 @@ def load_config(path):
except OSError:
raise ESPHomeYAMLError(u"Could not read configuration file at {}".format(path))
core.RAW_CONFIG = config
if CONF_ESPHOMEYAML not in config:
raise ESPHomeYAMLError(u"No esphomeyaml section in config")
core_conf = config[CONF_ESPHOMEYAML]
if CONF_PLATFORM not in core_conf:
raise ESPHomeYAMLError("esphomeyaml.platform not specified.")
esp_platform = unicode(core_conf[CONF_PLATFORM])
esp_platform = esp_platform.upper()
if '8266' in esp_platform:
esp_platform = ESP_PLATFORM_ESP8266
if '32' in esp_platform:
esp_platform = ESP_PLATFORM_ESP32
core.ESP_PLATFORM = esp_platform
if CONF_BOARD not in core_conf:
raise ESPHomeYAMLError("esphomeyaml.board not specified.")
core.BOARD = unicode(core_conf[CONF_BOARD])
core.SIMPLIFY = cv.boolean(core_conf.get(CONF_SIMPLIFY, True))
core_config.preload_core_config(config)
try:
result = validate_config(config)

View file

@ -3,12 +3,13 @@
from __future__ import print_function
import logging
import os
import re
import uuid as uuid_
import voluptuous as vol
from esphomeyaml import core
from esphomeyaml import core, helpers
from esphomeyaml.const import CONF_AVAILABILITY, CONF_COMMAND_TOPIC, CONF_DISCOVERY, CONF_ID, \
CONF_NAME, CONF_PAYLOAD_AVAILABLE, \
CONF_PAYLOAD_NOT_AVAILABLE, CONF_PLATFORM, CONF_RETAIN, CONF_STATE_TOPIC, CONF_TOPIC, \
@ -240,6 +241,19 @@ def has_exactly_one_key(*keys):
return validate
def has_at_most_one_key(*keys):
def validate(obj):
if not isinstance(obj, dict):
raise vol.Invalid('expected dictionary')
number = sum(k in keys for k in obj)
if number > 1:
raise vol.Invalid("Cannot specify more than one of {}.".format(', '.join(keys)))
return obj
return validate
TIME_PERIOD_ERROR = "Time period {} should be format number + unit, for example 5ms, 5s, 5min, 5h"
time_period_dict = vol.All(
@ -598,6 +612,28 @@ def dimensions(value):
return dimensions([match.group(1), match.group(2)])
def directory(value):
value = string(value)
path = helpers.relative_path(value)
if not os.path.exists(path):
raise vol.Invalid(u"Could not find directory '{}'. Please make sure it exists.".format(
path))
if not os.path.isdir(path):
raise vol.Invalid(u"Path '{}' is not a directory.".format(path))
return value
def file_(value):
value = string(value)
path = helpers.relative_path(value)
if not os.path.exists(path):
raise vol.Invalid(u"Could not find file '{}'. Please make sure it exists.".format(
path))
if not os.path.isfile(path):
raise vol.Invalid(u"Path '{}' is not a file.".format(path))
return value
REGISTERED_IDS = set()

View file

@ -5,6 +5,7 @@ MINOR_VERSION = 7
PATCH_VERSION = '0'
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
ESPHOMELIB_VERSION = '1.7.0'
ESP_PLATFORM_ESP32 = 'ESP32'
ESP_PLATFORM_ESP8266 = 'ESP8266'
@ -16,9 +17,14 @@ CONF_ESPHOMEYAML = 'esphomeyaml'
CONF_NAME = 'name'
CONF_PLATFORM = 'platform'
CONF_BOARD = 'board'
CONF_SIMPLIFY = 'simplify'
CONF_USE_BUILD_FLAGS = 'use_build_flags'
CONF_LIBRARY_URI = 'library_uri'
CONF_ESPHOMELIB_VERSION = 'esphomelib_version'
CONF_USE_CUSTOM_CODE = 'use_custom_code'
CONF_ARDUINO_VERSION = 'arduino_version'
CONF_LOCAL = 'local'
CONF_REPOSITORY = 'repository'
CONF_COMMIT = 'commit'
CONF_TAG = 'tag'
CONF_BRANCH = 'branch'
CONF_LOGGER = 'logger'
CONF_WIFI = 'wifi'
CONF_SSID = 'ssid'
@ -323,27 +329,17 @@ CONF_COLD_WHITE = 'cold_white'
CONF_WARM_WHITE = 'warm_white'
CONF_COLD_WHITE_COLOR_TEMPERATURE = 'cold_white_color_temperature'
CONF_WARM_WHITE_COLOR_TEMPERATURE = 'warm_white_color_temperature'
ESP32_BOARDS = [
'featheresp32', 'node32s', 'espea32', 'firebeetle32', 'esp32doit-devkit-v1',
'pocket_32', 'espectro32', 'esp32vn-iot-uno', 'esp320', 'esp-wrover-kit',
'esp32dev', 'heltec_wifi_kit32', 'heltec_wifi_lora_32', 'hornbill32dev',
'hornbill32minima', 'intorobot', 'm5stack-core-esp32', 'mhetesp32devkit',
'mhetesp32minikit', 'nano32', 'microduino-core-esp32', 'nodemcu-32s',
'quantum', 'esp32-evb', 'esp32-gateway', 'onehorse32dev', 'esp32thing',
'espino32', 'lolin32', 'wemosbat', 'widora-air', 'nina_w10',
]
ESP8266_BOARDS = [
'gen4iod', 'huzzah', 'oak', 'espduino', 'espectro', 'espresso_lite_v1',
'espresso_lite_v2', 'espino', 'esp01', 'esp01_1m', 'esp07', 'esp12e', 'esp8285',
'esp_wroom_02', 'phoenix_v1', 'phoenix_v2', 'wifinfo', 'heltex_wifi_kit_8',
'nodemcu', 'nodemcuv2', 'modwifi', 'wio_node', 'sparkfunBlynk', 'thing',
'thingdev', 'esp210', 'espinotee', 'd1', 'd1_mini', 'd1_mini_lite', 'd1_mini_pro',
]
ESP_BOARDS_FOR_PLATFORM = {
ESP_PLATFORM_ESP32: ESP32_BOARDS,
ESP_PLATFORM_ESP8266: ESP8266_BOARDS
}
CONF_ON_LOOP = 'on_loop'
CONF_ON_TIME = 'on_time'
CONF_SECONDS = 'seconds'
CONF_MINUTES = 'minutes'
CONF_HOURS = 'hours'
CONF_DAYS_OF_MONTH = 'days_of_month'
CONF_MONTHS = 'months'
CONF_DAYS_OF_WEEK = 'days_of_week'
CONF_CRON = 'cron'
ALLOWED_NAME_CHARS = u'abcdefghijklmnopqrstuvwxyz0123456789_'
ARDUINO_VERSION_ESP32_DEV = 'https://github.com/platformio/platform-espressif32.git#feature/stage'
ARDUINO_VERSION_ESP8266_DEV = 'https://github.com/platformio/platform-espressif8266.git#feature' \
'/stage'

View file

@ -239,7 +239,6 @@ class ID(object):
CONFIG_PATH = None
SIMPLIFY = True
ESP_PLATFORM = ''
BOARD = ''
RAW_CONFIG = None

199
esphomeyaml/core_config.py Normal file
View file

@ -0,0 +1,199 @@
import os
import re
import voluptuous as vol
import esphomeyaml.config_validation as cv
from esphomeyaml import automation, core, pins
from esphomeyaml.const import CONF_ARDUINO_VERSION, CONF_BOARD, CONF_BOARD_FLASH_MODE, \
CONF_BRANCH, CONF_BUILD_PATH, CONF_COMMIT, CONF_ESPHOMELIB_VERSION, CONF_ESPHOMEYAML, \
CONF_LOCAL, CONF_NAME, CONF_ON_BOOT, CONF_ON_LOOP, CONF_ON_SHUTDOWN, CONF_PLATFORM, \
CONF_PRIORITY, CONF_REPOSITORY, CONF_TAG, CONF_TRIGGER_ID, CONF_USE_CUSTOM_CODE, \
ESPHOMELIB_VERSION, ESP_PLATFORM_ESP32, ESP_PLATFORM_ESP8266, ARDUINO_VERSION_ESP8266_DEV, \
ARDUINO_VERSION_ESP32_DEV
from esphomeyaml.core import ESPHomeYAMLError
from esphomeyaml.helpers import App, NoArg, Pvariable, add, const_char_p, esphomelib_ns, \
relative_path
LIBRARY_URI_REPO = u'https://github.com/OttoWinter/esphomelib.git'
BUILD_FLASH_MODES = ['qio', 'qout', 'dio', 'dout']
StartupTrigger = esphomelib_ns.StartupTrigger
ShutdownTrigger = esphomelib_ns.ShutdownTrigger
LoopTrigger = esphomelib_ns.LoopTrigger
VERSION_REGEX = re.compile(r'^[0-9]+\.[0-9]+\.[0-9]+(?:-beta)?(?:-alpha)?$')
def validate_board(value):
if core.ESP_PLATFORM == ESP_PLATFORM_ESP8266:
board_pins = pins.ESP8266_BOARD_PINS
elif core.ESP_PLATFORM == ESP_PLATFORM_ESP32:
board_pins = pins.ESP32_BOARD_PINS
else:
raise NotImplementedError
if value not in board_pins:
raise vol.Invalid(u"Could not find board '{}'. Valid boards are {}".format(
value, u', '.join(pins.ESP8266_BOARD_PINS.keys())))
return value
def validate_simple_esphomelib_version(value):
value = cv.string_strict(value)
if value.upper() == 'LATEST':
return LIBRARY_URI_REPO + '#v{}'.format(ESPHOMELIB_VERSION)
elif value.upper() == 'DEV':
return LIBRARY_URI_REPO
elif VERSION_REGEX.match(value) is not None:
return LIBRARY_URI_REPO + '#v{}'.format(value)
return value
def validate_local_esphomelib_version(value):
value = cv.directory(value)
path = relative_path(value)
library_json = os.path.join(path, 'library.json')
if not os.path.exists(library_json):
raise vol.Invalid(u"Could not find '{}' file. '{}' does not seem to point to an "
u"esphomelib copy.".format(library_json, value))
return value
def convert_esphomelib_version_schema(value):
if CONF_COMMIT in value:
return value[CONF_REPOSITORY] + '#' + value[CONF_COMMIT]
if CONF_BRANCH in value:
return value[CONF_REPOSITORY] + '#' + value[CONF_BRANCH]
return value[CONF_REPOSITORY] + '#' + value[CONF_TAG]
ESPHOMELIB_VERSION_SCHEMA = vol.Any(
validate_simple_esphomelib_version,
vol.Schema({
vol.Required(CONF_LOCAL): validate_local_esphomelib_version,
}),
vol.All(
vol.Schema({
vol.Optional(CONF_REPOSITORY, default=LIBRARY_URI_REPO): cv.string,
vol.Optional(CONF_COMMIT, 'tag'): cv.string,
vol.Optional(CONF_BRANCH, 'tag'): cv.string,
vol.Optional(CONF_TAG, 'tag'): cv.string,
}),
cv.has_at_most_one_key(CONF_COMMIT, CONF_BRANCH, CONF_TAG),
convert_esphomelib_version_schema
),
)
def validate_platform(value):
value = cv.string(value)
if value.upper() in ('ESP8266', 'ESPRESSIF8266'):
return ESP_PLATFORM_ESP8266
if value.upper() in ('ESP32', 'ESPRESSIF32'):
return ESP_PLATFORM_ESP32
raise vol.Invalid(u"Invalid platform '{}'. Only options are ESP8266 and ESP32. Please note "
u"the old way to use the latest arduino framework version has been split up "
u"into the arduino_version configuration option.".format(value))
PLATFORMIO_ESP8266_LUT = {
'2.4.2': 'espressif8266@1.8.0',
'2.4.1': 'espressif8266@1.7.3',
'2.4.0': 'espressif8266@1.6.0',
'2.3.0': 'espressif8266@1.5.0',
'RECOMMENDED': 'espressif8266@>=1.8.0',
'LATEST': 'espressif8266',
'DEV': ARDUINO_VERSION_ESP8266_DEV,
}
PLATFORMIO_ESP32_LUT = {
'1.0.0': 'espressif32@1.3.0',
'RECOMMENDED': 'espressif32@>=1.3.0',
'LATEST': 'espressif32',
'DEV': ARDUINO_VERSION_ESP32_DEV,
}
def validate_arduino_version(value):
value = cv.string_strict(value)
value_ = value.upper()
if core.ESP_PLATFORM == ESP_PLATFORM_ESP8266:
if VERSION_REGEX.match(value) is not None and value_ not in PLATFORMIO_ESP8266_LUT:
raise vol.Invalid("Unfortunately the arduino framework version '{}' is unsupported "
"at this time. You can override this by manually using "
"espressif8266@<platformio version>")
if value_ in PLATFORMIO_ESP8266_LUT:
return PLATFORMIO_ESP8266_LUT[value_]
return value
elif core.ESP_PLATFORM == ESP_PLATFORM_ESP32:
if VERSION_REGEX.match(value) is not None and value_ not in PLATFORMIO_ESP32_LUT:
raise vol.Invalid("Unfortunately the arduino framework version '{}' is unsupported "
"at this time. You can override this by manually using "
"espressif32@<platformio version>")
if value_ in PLATFORMIO_ESP32_LUT:
return PLATFORMIO_ESP32_LUT[value_]
return value
else:
raise NotImplementedError
CONFIG_SCHEMA = vol.Schema({
vol.Required(CONF_NAME): cv.valid_name,
vol.Required(CONF_PLATFORM): vol.All(vol.Upper, cv.one_of('ESP8266', 'ESPRESSIF8266',
'ESP32', 'ESPRESSIF32')),
vol.Required(CONF_BOARD): validate_board,
vol.Optional(CONF_ESPHOMELIB_VERSION, default='latest'): ESPHOMELIB_VERSION_SCHEMA,
vol.Optional(CONF_ARDUINO_VERSION, default='recommended'): validate_arduino_version,
vol.Optional(CONF_USE_CUSTOM_CODE, default=False): cv.boolean,
vol.Optional(CONF_BUILD_PATH): cv.string,
vol.Optional(CONF_BOARD_FLASH_MODE): vol.All(vol.Lower, cv.one_of(*BUILD_FLASH_MODES)),
vol.Optional(CONF_ON_BOOT): vol.All(cv.ensure_list, [automation.validate_automation({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(StartupTrigger),
vol.Optional(CONF_PRIORITY): vol.Coerce(float),
})]),
vol.Optional(CONF_ON_SHUTDOWN): vol.All(cv.ensure_list, [automation.validate_automation({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(ShutdownTrigger),
})]),
vol.Optional(CONF_ON_LOOP): vol.All(cv.ensure_list, [automation.validate_automation({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(LoopTrigger),
})]),
vol.Optional('library_uri'): cv.invalid("The library_uri option has been removed in 1.8.0 and "
"was moved into the esphomelib_version option.")
})
def preload_core_config(config):
if CONF_ESPHOMEYAML not in config:
raise ESPHomeYAMLError(u"No esphomeyaml section in config")
core_conf = config[CONF_ESPHOMEYAML]
if CONF_PLATFORM not in core_conf:
raise ESPHomeYAMLError("esphomeyaml.platform not specified.")
if CONF_BOARD not in core_conf:
raise ESPHomeYAMLError("esphomeyaml.board not specified.")
try:
core.ESP_PLATFORM = validate_platform(core_conf[CONF_PLATFORM])
core.BOARD = validate_board(core_conf[CONF_BOARD])
except vol.Invalid as e:
raise ESPHomeYAMLError(unicode(e))
def to_code(config):
add(App.set_name(config[CONF_NAME]))
for conf in config.get(CONF_ON_BOOT, []):
rhs = App.register_component(StartupTrigger.new(conf.get(CONF_PRIORITY)))
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs)
automation.build_automation(trigger, NoArg, conf)
for conf in config.get(CONF_ON_SHUTDOWN, []):
trigger = Pvariable(conf[CONF_TRIGGER_ID], ShutdownTrigger.new())
automation.build_automation(trigger, const_char_p, conf)
for conf in config.get(CONF_ON_LOOP, []):
rhs = App.register_component(LoopTrigger.new())
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs)
automation.build_automation(trigger, NoArg, conf)

View file

@ -2,6 +2,7 @@ from __future__ import print_function
import inspect
import logging
import os
import re
from collections import OrderedDict, deque
@ -85,8 +86,6 @@ class AssignmentExpression(Expression):
def __str__(self):
type_ = self.type
if core.SIMPLIFY:
type_ = u'auto'
return u"{} {}{} = {}".format(type_, self.modifier, self.name, self.rhs)
def has_side_effects(self):
@ -662,3 +661,7 @@ def color(the_color, message='', reset=None):
if not message:
return parse_colors(the_color)
return parse_colors(the_color) + message + escape_codes[reset or 'reset']
def relative_path(path):
return os.path.join(os.path.dirname(core.CONFIG_PATH), os.path.expanduser(path))

View file

@ -10,59 +10,162 @@ from esphomeyaml.const import CONF_INVERTED, CONF_MODE, CONF_NUMBER, CONF_PCF857
_LOGGER = logging.getLogger(__name__)
ESP8266_PINS = {
'A0': 17, 'SS': 15, 'MOSI': 13, 'MISO': 12, 'SCK': 14,
}
ESP8266_NODEMCU_PINS = dict(ESP8266_PINS, **{
'D0': 16, 'D1': 5, 'D2': 4, 'D3': 0, 'D4': 2, 'D5': 14, 'D6': 12, 'D7': 13, 'D8': 15, 'D9': 3,
'D10': 1, 'LED': 16, 'SDA': 4, 'SCL': 5,
})
ESP8266_D1_PINS = dict(ESP8266_PINS, **{
'D0': 3, 'D1': 1, 'D2': 16, 'D3': 5, 'D4': 4, 'D5': 14, 'D6': 12, 'D7': 13, 'D8': 0, 'D9': 2,
'D10': 15, 'D11': 13, 'D12': 14, 'D13': 14, 'D14': 4, 'D15': 5, 'LED': 2, 'SDA': 4, 'SCL': 5,
})
ESP8266_D1_MINI_PINS = dict(ESP8266_PINS, **{
'D0': 16, 'D1': 5, 'D2': 4, 'D3': 0, 'D4': 2, 'D5': 14, 'D6': 12, 'D7': 13, 'D8': 15, 'RX': 3,
'TX': 1, 'LED': 2, 'SDA': 4, 'SCL': 5,
})
ESP8266_THING_PINS = dict(ESP8266_PINS, **{
'LED': 5, 'SDA': 2, 'SCL': 14,
})
ESP8266_ADAFRUIT_PINS = dict(ESP8266_PINS, **{
'LED': 0, 'SDA': 4, 'SCL': 5,
})
ESP8266_ESPDUINO_PINS = dict(ESP8266_PINS, **{
'LED': 16, 'SDA': 4, 'SCL': 5,
})
ESP8266_BOARD_TO_PINS = {
'huzzah': ESP8266_ADAFRUIT_PINS,
'espduino': ESP8266_ESPDUINO_PINS,
'nodemcu': ESP8266_NODEMCU_PINS, 'nodemcuv2': ESP8266_NODEMCU_PINS,
'thing': ESP8266_THING_PINS, 'thingdev': ESP8266_THING_PINS,
'd1': ESP8266_D1_PINS,
'd1_mini': ESP8266_D1_MINI_PINS, 'd1_mini_lite': ESP8266_D1_MINI_PINS,
'd1_mini_pro': ESP8266_D1_MINI_PINS
ESP8266_BASE_PINS = {
'A0': 17, 'SS': 15, 'MOSI': 13, 'MISO': 12, 'SCK': 14, 'SDA': 4, 'SCL': 5, 'RX': 3, 'TX': 1
}
ESP32_PINS = {
ESP8266_BOARD_PINS = {
'd1': {'D0': 3, 'D1': 1, 'D2': 16, 'D3': 5, 'D4': 4, 'D5': 14, 'D6': 12, 'D7': 13, 'D8': 0,
'D9': 2, 'D10': 15, 'D11': 13, 'D12': 14, 'D13': 14, 'D14': 4, 'D15': 5, 'LED': 2},
'd1_mini': {'D0': 16, 'D1': 5, 'D2': 4, 'D3': 0, 'D4': 2, 'D5': 14, 'D6': 12, 'D7': 13,
'D8': 15, 'LED': 2},
'd1_mini_lite': 'd1_mini',
'd1_mini_pro': 'd1_mini',
'esp01': {},
'esp01_1m': {},
'esp07': {},
'esp12e': {},
'esp210': {},
'esp8285': {},
'esp_wroom_02': {},
'espduino': {'LED': 16},
'espectro': {'LED': 15, 'BUTTON': 2},
'espino': {'LED': 2, 'LED_RED': 2, 'LED_GREEN': 4, 'LED_BLUE': 5, 'BUTTON': 0},
'espinotee': {'LED': 16},
'espresso_lite_v1': {'LED': 16},
'espresso_lite_v2': {'LED': 2},
'gen4iod': {},
'heltec_wifi_kit_8': 'd1_mini',
'huzzah': {'LED': 0},
'modwifi': {},
'nodemcu': {'D0': 16, 'D1': 5, 'D2': 4, 'D3': 0, 'D4': 2, 'D5': 14, 'D6': 12, 'D7': 13,
'D8': 15, 'D9': 3, 'D10': 1, 'LED': 16},
'nodemcuv2': 'nodemcu',
'oak': {'P0': 2, 'P1': 5, 'P2': 0, 'P3': 3, 'P4': 1, 'P5': 4, 'P6': 15, 'P7': 13, 'P8': 12,
'P9': 14, 'P10': 16, 'P11': 17, 'LED': 5},
'phoenix_v1': {'LED': 16},
'phoenix_v2': {'LED': 2},
'sparkfunBlynk': 'thing',
'thing': {'LED': 5, 'SDA': 2, 'SCL': 14},
'thingdev': 'thing',
'wifi_slot': {'LED': 2},
'wifiduino': {'D0': 3, 'D1': 1, 'D2': 2, 'D3': 0, 'D4': 4, 'D5': 5, 'D6': 16, 'D7': 14,
'D8': 12, 'D9': 13, 'D10': 15, 'D11': 13, 'D12': 12, 'D13': 14},
'wifinfo': {'LED': 12, 'D0': 16, 'D1': 5, 'D2': 4, 'D3': 0, 'D4': 2, 'D5': 14, 'D6': 12,
'D7': 13, 'D8': 15, 'D9': 3, 'D10': 1},
'wio_link': {'LED': 2, 'GROVE': 15},
'wio_node': 'nodemcu',
'xinabox_cw01': {'SDA': 2, 'SCL': 14, 'LED': 5, 'LED_RED': 12, 'LED_GREEN': 13}
}
ESP32_BASE_PINS = {
'TX': 1, 'RX': 3, 'SDA': 21, 'SCL': 22, 'SS': 5, 'MOSI': 23, 'MISO': 19, 'SCK': 18, 'A0': 36,
'A3': 39, 'A4': 32, 'A5': 33, 'A6': 34, 'A7': 35, 'A10': 4, 'A11': 0, 'A12': 2, 'A13': 15,
'A14': 13, 'A15': 12, 'A16': 14, 'A17': 27, 'A18': 25, 'A19': 26, 'T0': 4, 'T1': 0, 'T2': 2,
'T3': 15, 'T4': 12, 'T5': 12, 'T6': 14, 'T7': 27, 'T8': 33, 'T9': 32, 'DAC1': 25, 'DAC2': 26,
'T3': 15, 'T4': 13, 'T5': 12, 'T6': 14, 'T7': 27, 'T8': 33, 'T9': 32, 'DAC1': 25, 'DAC2': 26,
'SVP': 36, 'SVN': 39,
}
ESP32_NODEMCU_32S_PINS = dict(ESP32_PINS, **{
'LED': 2,
})
ESP32_LOLIN32_PINS = dict(ESP32_PINS, **{
'LED': 5
})
ESP32_BOARD_TO_PINS = {
'nodemcu-32s': ESP32_NODEMCU_32S_PINS,
'lolin32': ESP32_LOLIN32_PINS,
ESP32_BOARD_PINS = {
'alksesp32': {'D0': 40, 'D1': 41, 'D2': 15, 'D3': 2, 'D4': 0, 'D5': 4, 'D6': 16, 'D7': 17,
'D8': 5, 'D9': 18, 'D10': 19, 'D11': 21, 'D12': 22, 'D13': 23, 'A0': 32, 'A1': 33,
'A2': 25, 'A3': 26, 'A4': 27, 'A5': 14, 'A6': 12, 'A7': 15, 'L_R': 22, 'L_G': 17,
'L_Y': 23, 'L_B': 5, 'L_RGB_R': 4, 'L_RGB_G': 21, 'L_RGB_B': 16, 'SW1': 15,
'SW2': 2, 'SW3': 0, 'POT1': 32, 'POT2': 33, 'PIEZO1': 19, 'PIEZO2': 18,
'PHOTO': 25, 'DHT_PIN': 26, 'S1': 4, 'S2': 16, 'S3': 18, 'S4': 19, 'S5': 21,
'SDA': 27, 'SCL': 14, 'SS': 19, 'MOSI': 21, 'MISO': 22, 'SCK': 23},
'esp-wrover-kit': {},
'esp32-evb': {'BUTTON': 34, 'SDA': 13, 'SCL': 16, 'SS': 17, 'MOSI': 2, 'MISO': 15, 'SCK': 14},
'esp32-gateway': {'LED': 33, 'BUTTON': 34, 'SCL': 16, 'SDA': 17},
'esp320': {'LED': 5, 'SDA': 2, 'SCL': 14, 'SS': 15, 'MOSI': 13, 'MISO': 12, 'SCK': 14},
'esp32dev': {},
'esp32doit-devkit-v1': {'LED': 2},
'esp32thing': {'LED': 5, 'BUTTON': 0, 'SS': 2},
'esp32vn-iot-uno': {},
'espea32': {'LED': 5, 'BUTTON': 0},
'espectro32': {'LED': 15, 'SD_SS': 33},
'espino32': {'LED': 16, 'BUTTON': 0},
'featheresp32': {'LED': 13, 'TX': 17, 'RX': 16, 'SDA': 23, 'SS': 2, 'MOSI': 18, 'SCK': 5,
'A0': 26, 'A1': 25, 'A2': 34, 'A4': 36, 'A5': 4, 'A6': 14, 'A7': 32, 'A8': 15,
'A9': 33, 'A10': 27, 'A11': 12, 'A12': 13, 'A13': 35},
'firebeetle32': {'LED': 2},
'heltec_wifi_kit_32': {'LED': 25, 'BUTTON': 0, 'A1': 37, 'A2': 38},
'heltec_wifi_lora_32': {'LED': 25, 'BUTTON': 0, 'SDA': 4, 'SCL': 15, 'SS': 18, 'MOSI': 27,
'SCK': 5, 'A1': 37, 'A2': 38, 'T8': 32, 'T9': 33, 'DAC1': 26,
'DAC2': 25, 'OLED_SCL': 15, 'OLED_SDA': 4, 'OLED_RST': 16,
'LORA_SCK': 5, 'LORA_MOSI': 27, 'LORA_MISO': 19, 'LORA_CS': 18,
'LORA_RST': 14, 'LORA_IRQ': 26},
'hornbill32dev': {'LED': 13, 'BUTTON': 0},
'hornbill32minima': {'SS': 2},
'intorobot': {'LED': 4, 'LED_RED': 27, 'LED_GREEN': 21, 'LED_BLUE': 22,
'BUTTON': 0, 'SDA': 23, 'SCL': 19, 'MOSI': 16, 'MISO': 17, 'A1': 39, 'A2': 35,
'A3': 25, 'A4': 26, 'A5': 14, 'A6': 12, 'A7': 15, 'A8': 13, 'A9': 2, 'D0': 19,
'D1': 23, 'D2': 18, 'D3': 17, 'D4': 16, 'D5': 5, 'D6': 4, 'T0': 19, 'T1': 23,
'T2': 18, 'T3': 17, 'T4': 16, 'T5': 5, 'T6': 4},
'lolin32': {'LED': 5},
'lolin_d32': {'LED': 5, 'VBAT': 35},
'lolin_d32_pro': {'LED': 5, 'VBAT': 35, 'TF_CS': 4, 'TS_CS': 12, 'TFT_CS': 14, 'TFT_LED': 32,
'TFT_RST': 33, 'TFT_DC': 27},
'm5stack-core-esp32': {'TXD2': 17, 'RXD2': 16, 'G23': 23, 'G19': 19, 'G18': 18, 'G3': 3,
'G16': 16, 'G21': 21, 'G2': 2, 'G12': 12, 'G15': 15, 'G35': 35,
'G36': 36, 'G25': 25, 'G26': 26, 'G1': 1, 'G17': 17, 'G22': 22, 'G5': 5,
'G13': 13, 'G0': 0, 'G34': 34, 'ADC1': 35, 'ADC2': 36},
'm5stack-fire': {'G23': 23, 'G19': 19, 'G18': 18, 'G3': 3, 'G16': 16, 'G21': 21, 'G2': 2,
'G12': 12, 'G15': 15, 'G35': 35, 'G36': 36, 'G25': 25, 'G26': 26, 'G1': 1,
'G17': 17, 'G22': 22, 'G5': 5, 'G13': 13, 'G0': 0, 'G34': 34, 'ADC1': 35,
'ADC2': 36},
'mhetesp32devkit': {'LED': 2},
'mhetesp32minikit': {'LED': 2},
'microduino-core-esp32': {'SDA': 22, 'SCL': 21, 'SDA1': 12, 'SCL1': 13, 'A0': 12, 'A1': 13,
'A2': 15, 'A3': 4, 'A6': 38, 'A7': 37, 'A8': 32, 'A9': 33, 'A10': 25,
'A11': 26, 'A12': 27, 'A13': 14, 'D0': 3, 'D1': 1, 'D2': 16, 'D3': 17,
'D4': 32, 'D5': 33, 'D6': 25, 'D7': 26, 'D8': 27, 'D9': 14, 'D10': 5,
'D11': 23, 'D12': 19, 'D13': 18, 'D14': 12, 'D15': 13, 'D16': 15,
'D17': 4, 'D18': 22, 'D19': 21, 'D20': 38, 'D21': 37},
'nano32': {'LED': 16, 'BUTTON': 0},
'nina_w10': {'LED_GREEN': 33, 'LED_RED': 23, 'LED_BLUE': 21, 'SW1': 33, 'SW2': 27, 'SDA': 12,
'SCL': 13, 'D0': 3, 'D1': 1, 'D2': 26, 'D3': 25, 'D4': 35, 'D5': 27, 'D6': 22,
'D7': 0, 'D8': 15, 'D9': 14, 'D10': 5, 'D11': 19, 'D12': 23, 'D13': 18, 'D14': 13,
'D15': 12, 'D16': 32, 'D17': 33, 'D18': 21, 'D19': 34, 'D20': 36, 'D21': 39},
'node32s': {},
'nodemcu-32s': {'LED': 2, 'BUTTON': 0},
'odroid_esp32': {'LED': 2, 'SDA': 15, 'SCL': 4, 'SS': 22, 'ADC1': 35, 'ADC2': 36},
'onehorse32dev': {'LED': 5, 'BUTTON': 0, 'A1': 37, 'A2': 38},
'pico32': {},
'pocket_32': {'LED': 16},
'quantum': {},
'ttgo-lora32-v1': {'LED': 2, 'BUTTON': 0, 'SS': 18, 'MOSI': 27, 'SCK': 5, 'A1': 37, 'A2': 38,
'T8': 32, 'T9': 33, 'DAC1': 26, 'DAC2': 25, 'OLED_SDA': 4, 'OLED_SCL': 15,
'OLED_RST': 16, 'LORA_SCK': 5, 'LORA_MISO': 19, 'LORA_MOSI': 27,
'LORA_CS': 18, 'LORA_RST': 14, 'LORA_IRQ': 26},
'wemosbat': 'pocket_32',
'widora-air': {'LED': 25, 'BUTTON': 0, 'SDA': 23, 'SCL': 19, 'MOSI': 16, 'MISO': 17, 'A1': 39,
'A2': 35, 'A3': 25, 'A4': 26, 'A5': 14, 'A6': 12, 'A7': 15, 'A8': 13, 'A9': 2,
'D0': 19, 'D1': 23, 'D2': 18, 'D3': 17, 'D4': 16, 'D5': 5, 'D6': 4, 'T0': 19,
'T1': 23, 'T2': 18, 'T3': 17, 'T4': 16, 'T5': 5, 'T6': 4},
'xinabox_cw02': {'LED': 27},
}
def _lookup_pin(platform, board, value):
if platform == ESP_PLATFORM_ESP8266:
board_pins = ESP8266_BOARD_PINS.get(board, {})
base_pins = ESP8266_BASE_PINS
elif platform == ESP_PLATFORM_ESP32:
board_pins = ESP32_BOARD_PINS.get(board, {})
base_pins = ESP32_BASE_PINS
else:
raise NotImplementedError
if isinstance(board_pins, str):
return _lookup_pin(platform, board_pins, value)
if value in board_pins:
return board_pins[value]
if value in base_pins:
return base_pins[value]
raise vol.Invalid(u"Can't find internal pin number for {}.".format(value))
def _translate_pin(value):
if isinstance(value, dict) or value is None:
raise vol.Invalid(u"This variable only supports pin numbers, not full pin schemas "
@ -75,27 +178,7 @@ def _translate_pin(value):
pass
if value.startswith('GPIO'):
return vol.Coerce(int)(value[len('GPIO'):].strip())
if core.ESP_PLATFORM == ESP_PLATFORM_ESP32:
if value in ESP32_PINS:
return ESP32_PINS[value]
if core.BOARD not in ESP32_BOARD_TO_PINS:
raise vol.Invalid(u"ESP32: Unknown board {} with unknown "
u"pin {}.".format(core.BOARD, value))
if value not in ESP32_BOARD_TO_PINS[core.BOARD]:
raise vol.Invalid(u"ESP32: Board {} doesn't have "
u"pin {}".format(core.BOARD, value))
return ESP32_BOARD_TO_PINS[core.BOARD][value]
elif core.ESP_PLATFORM == ESP_PLATFORM_ESP8266:
if value in ESP8266_PINS:
return ESP8266_PINS[value]
if core.BOARD not in ESP8266_BOARD_TO_PINS:
raise vol.Invalid(u"ESP8266: Unknown board {} with unknown "
u"pin {}.".format(core.BOARD, value))
if value not in ESP8266_BOARD_TO_PINS[core.BOARD]:
raise vol.Invalid(u"ESP8266: Board {} doesn't have "
u"pin {}".format(core.BOARD, value))
return ESP8266_BOARD_TO_PINS[core.BOARD][value]
raise vol.Invalid(u"Invalid ESP platform.")
return _lookup_pin(core.ESP_PLATFORM, core.BOARD, value)
def validate_gpio_pin(value):

View file

@ -8,12 +8,13 @@ import voluptuous as vol
import esphomeyaml.config_validation as cv
from esphomeyaml.components import mqtt
from esphomeyaml.const import ESP_BOARDS_FOR_PLATFORM, ESP_PLATFORMS, ESP_PLATFORM_ESP32, \
ESP_PLATFORM_ESP8266
from esphomeyaml.const import ESP_PLATFORMS, ESP_PLATFORM_ESP32, ESP_PLATFORM_ESP8266
from esphomeyaml.helpers import color
# pylint: disable=anomalous-backslash-in-string
from esphomeyaml.pins import ESP32_BOARD_PINS, ESP8266_BOARD_PINS
CORE_BIG = """ _____ ____ _____ ______
/ ____/ __ \| __ \| ____|
| | | | | | |__) | |__
@ -187,11 +188,12 @@ def wizard(path):
# Don't sleep because user needs to copy link
if platform == ESP_PLATFORM_ESP32:
print("For example \"{}\".".format(color("bold_white", 'nodemcu-32s')))
boards = list(ESP32_BOARD_PINS.keys())
else:
print("For example \"{}\".".format(color("bold_white", 'nodemcuv2')))
boards = list(ESP8266_BOARD_PINS.keys())
while True:
board = raw_input(color("bold_white", "(board): "))
boards = ESP_BOARDS_FOR_PLATFORM[platform]
try:
board = vol.All(vol.Lower, vol.Any(*boards))(board)
break

View file

@ -2,14 +2,17 @@ from __future__ import print_function
import codecs
import errno
import json
import os
from esphomeyaml import core
from esphomeyaml.config import iter_components
from esphomeyaml.const import CONF_BOARD, CONF_BOARD_FLASH_MODE, CONF_ESPHOMEYAML, \
CONF_LIBRARY_URI, \
CONF_NAME, CONF_PLATFORM, CONF_USE_BUILD_FLAGS, ESP_PLATFORM_ESP32, ESP_PLATFORM_ESP8266
from esphomeyaml.const import CONF_ARDUINO_VERSION, CONF_BOARD, CONF_BOARD_FLASH_MODE, \
CONF_ESPHOMELIB_VERSION, CONF_ESPHOMEYAML, CONF_LOCAL, CONF_NAME, CONF_USE_CUSTOM_CODE, \
ESP_PLATFORM_ESP32, ARDUINO_VERSION_ESP32_DEV
from esphomeyaml.core import ESPHomeYAMLError
from esphomeyaml.core_config import VERSION_REGEX
from esphomeyaml.helpers import relative_path
CPP_AUTO_GENERATE_BEGIN = u'// ========== AUTO GENERATED CODE BEGIN ==========='
CPP_AUTO_GENERATE_END = u'// =========== AUTO GENERATED CODE END ============'
@ -59,11 +62,6 @@ build_flags =
${{common.build_flags}}
"""
PLATFORM_TO_PLATFORMIO = {
ESP_PLATFORM_ESP32: 'espressif32',
ESP_PLATFORM_ESP8266: 'espressif8266'
}
def get_build_flags(config, key):
build_flags = set()
@ -81,19 +79,16 @@ def get_build_flags(config, key):
return build_flags
def get_ini_content(config):
def get_ini_content(config, path):
version_specific_settings = determine_platformio_version_settings()
platform = config[CONF_ESPHOMEYAML][CONF_PLATFORM]
if platform in PLATFORM_TO_PLATFORMIO:
platform = PLATFORM_TO_PLATFORMIO[platform]
options = {
u'env': config[CONF_ESPHOMEYAML][CONF_NAME],
u'platform': platform,
u'platform': config[CONF_ESPHOMEYAML][CONF_ARDUINO_VERSION],
u'board': config[CONF_ESPHOMEYAML][CONF_BOARD],
u'build_flags': u'',
}
build_flags = set()
if config[CONF_ESPHOMEYAML][CONF_USE_BUILD_FLAGS]:
if not config[CONF_ESPHOMEYAML][CONF_USE_CUSTOM_CODE]:
build_flags |= get_build_flags(config, 'build_flags')
build_flags |= get_build_flags(config, 'BUILD_FLAGS')
build_flags.add(u"-DESPHOMEYAML_USE")
@ -106,13 +101,48 @@ def get_ini_content(config):
options[u'build_flags'] = u'\n '.join(build_flags)
lib_deps = set()
lib_deps.add(config[CONF_ESPHOMEYAML][CONF_LIBRARY_URI])
lib_version = config[CONF_ESPHOMEYAML][CONF_ESPHOMELIB_VERSION]
lib_path = os.path.join(path, 'lib')
dst_path = os.path.join(lib_path, 'esphomelib')
if isinstance(lib_version, (str, unicode)):
lib_deps.add(lib_version)
if os.path.islink(dst_path):
os.unlink(dst_path)
else:
src_path = relative_path(lib_version[CONF_LOCAL])
do_write = True
if os.path.islink(dst_path):
old_path = os.path.join(os.readlink(dst_path), lib_path)
if old_path != lib_path:
os.unlink(dst_path)
else:
do_write = False
if do_write:
mkdir_p(lib_path)
os.symlink(src_path, dst_path)
# Manually add lib_deps because platformio seems to ignore them inside libs/
library_json_path = os.path.join(src_path, 'library.json')
with codecs.open(library_json_path, 'r', encoding='utf-8') as f_handle:
library_json_text = f_handle.read()
library_json = json.loads(library_json_text)
for dep in library_json.get('dependencies', []):
if 'version' in dep and VERSION_REGEX.match(dep['version']) is not None:
lib_deps.add(dep['name'] + '@' + dep['version'])
else:
lib_deps.add(dep['version'])
lib_deps |= get_build_flags(config, 'LIB_DEPS')
lib_deps |= get_build_flags(config, 'lib_deps')
if core.ESP_PLATFORM == ESP_PLATFORM_ESP32:
lib_deps |= {
'Preferences', # Preferences helper
}
# Manual fix for AsyncTCP
if config[CONF_ESPHOMEYAML].get(CONF_ARDUINO_VERSION) == ARDUINO_VERSION_ESP32_DEV:
lib_deps.add('https://github.com/me-no-dev/AsyncTCP.git#idf-update')
# avoid changing build flags order
lib_deps = sorted(x for x in lib_deps if x)
if lib_deps:
@ -178,7 +208,7 @@ def write_platformio_ini(content, path):
def write_platformio_project(config, path):
platformio_ini = os.path.join(path, 'platformio.ini')
content = get_ini_content(config)
content = get_ini_content(config, path)
if 'esp32_ble_beacon' in config or 'esp32_ble_tracker' in config:
content += 'board_build.partitions = partitions.csv\n'
partitions_csv = os.path.join(path, 'partitions.csv')

View file

@ -2,10 +2,12 @@ esphomeyaml:
name: test1
platform: ESP32
board: nodemcu-32s
# Use latest esphomelib git version. TODO: Change this
library_uri: 'https://github.com/OttoWinter/esphomelib.git'
simplify: false
use_build_flags: yes
# Use latest upstream esphomelib git version.
esphomelib_version: dev
# Use this for testing while developing:
# esphomelib_version:
# local: ~/path/to/esphomelib
use_custom_code: false
on_boot:
priority: 150.0
then:
@ -15,6 +17,10 @@ esphomeyaml:
then:
- lambda: >-
ESP_LOGD("main", "ON SHUTDOWN!");
on_loop:
then:
- lambda: >-
ESP_LOGV("main", "ON LOOP!");
build_path: build
wifi:
@ -55,7 +61,7 @@ mqtt:
qos: 0
then:
- lambda: >-
ESP_LOGD("main", "Got message %s", x);
ESP_LOGD("main", "Got message %s", x.c_str());
- topic: livingroom/ota_mode
then:
- deep_sleep.prevent:
@ -905,6 +911,10 @@ time:
- 0.pool.ntp.org
- 1.pool.ntp.org
- 2.pool.ntp.org
on_time:
cron: '/30 0-30,30/5 * ? JAN-DEC MON,SAT-SUN,TUE-FRI'
then:
- lambda: 'ESP_LOGD("main", "time");'
cover:
- platform: template