From fb1013e885a0502be8eebb37ac7d9cf8da06ece7 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 5 Dec 2018 08:57:39 +0100 Subject: [PATCH] Multi Conf --- esphomeyaml/__main__.py | 23 +----- esphomeyaml/components/ads1115.py | 12 ++- esphomeyaml/components/custom_component.py | 20 +++-- esphomeyaml/components/dallas.py | 12 +-- esphomeyaml/components/font.py | 66 ++++++++-------- esphomeyaml/components/globals.py | 31 ++++---- esphomeyaml/components/image.py | 56 ++++++------- esphomeyaml/components/my9231.py | 36 ++++----- esphomeyaml/components/pca9685.py | 23 ++---- esphomeyaml/components/pcf8574.py | 12 ++- esphomeyaml/components/pn532.py | 26 +++---- esphomeyaml/components/power_supply.py | 25 +++--- esphomeyaml/components/rdm6300.py | 15 ++-- esphomeyaml/components/remote_receiver.py | 36 ++++----- esphomeyaml/components/remote_transmitter.py | 21 ++--- esphomeyaml/components/spi.py | 30 ++++--- esphomeyaml/components/uart.py | 16 ++-- esphomeyaml/config.py | 82 +++++++++++++++----- esphomeyaml/dashboard/static/esphomeyaml.js | 1 - esphomeyaml/yaml_util.py | 4 +- 20 files changed, 274 insertions(+), 273 deletions(-) diff --git a/esphomeyaml/__main__.py b/esphomeyaml/__main__.py index ba173dd533..719c77f3a4 100644 --- a/esphomeyaml/__main__.py +++ b/esphomeyaml/__main__.py @@ -9,7 +9,7 @@ import random import sys from esphomeyaml import const, core, core_config, mqtt, platformio_api, wizard, writer, yaml_util -from esphomeyaml.config import get_component, iter_components, read_config +from esphomeyaml.config import get_component, iter_components, read_config, strip_default_ids from esphomeyaml.const import CONF_BAUD_RATE, CONF_DOMAIN, CONF_ESPHOMEYAML, \ CONF_HOSTNAME, CONF_LOGGER, CONF_MANUAL_IP, CONF_NAME, CONF_STATIC_IP, CONF_USE_CUSTOM_CODE, \ CONF_WIFI @@ -240,25 +240,6 @@ def command_wizard(args): return wizard.wizard(args.configuration) -def strip_default_ids(config): - value = config - if isinstance(config, list): - value = type(config)() - for x in config: - if isinstance(x, core.ID) and not x.is_manual: - continue - value.append(strip_default_ids(x)) - return value - elif isinstance(config, dict): - value = type(config)() - for k, v in config.iteritems(): - if isinstance(v, core.ID) and not v.is_manual: - continue - value[k] = strip_default_ids(v) - return value - return value - - def command_config(args, config): _LOGGER.info("Configuration is valid!") if not args.verbose: @@ -475,7 +456,7 @@ def run_esphomeyaml(argv): CORE.config_path = args.configuration - config = read_config() + config = read_config(args.verbose) if config is None: return 1 CORE.config = config diff --git a/esphomeyaml/components/ads1115.py b/esphomeyaml/components/ads1115.py index d3c2a80de9..70be84ae7f 100644 --- a/esphomeyaml/components/ads1115.py +++ b/esphomeyaml/components/ads1115.py @@ -8,22 +8,20 @@ from esphomeyaml.cpp_helpers import setup_component from esphomeyaml.cpp_types import App, Component DEPENDENCIES = ['i2c'] +MULTI_CONF = True ADS1115Component = sensor.sensor_ns.class_('ADS1115Component', Component, i2c.I2CDevice) -ADS1115_SCHEMA = vol.Schema({ +CONFIG_SCHEMA = vol.Schema({ cv.GenerateID(): cv.declare_variable_id(ADS1115Component), vol.Required(CONF_ADDRESS): cv.i2c_address, }).extend(cv.COMPONENT_SCHEMA.schema) -CONFIG_SCHEMA = vol.All(cv.ensure_list, [ADS1115_SCHEMA]) - def to_code(config): - for conf in config: - rhs = App.make_ads1115_component(conf[CONF_ADDRESS]) - var = Pvariable(conf[CONF_ID], rhs) - setup_component(var, conf) + rhs = App.make_ads1115_component(config[CONF_ADDRESS]) + var = Pvariable(config[CONF_ID], rhs) + setup_component(var, config) BUILD_FLAGS = '-DUSE_ADS1115_SENSOR' diff --git a/esphomeyaml/components/custom_component.py b/esphomeyaml/components/custom_component.py index 93364bf491..abcedf5ca4 100644 --- a/esphomeyaml/components/custom_component.py +++ b/esphomeyaml/components/custom_component.py @@ -7,8 +7,9 @@ from esphomeyaml.cpp_helpers import setup_component from esphomeyaml.cpp_types import Component, ComponentPtr, esphomelib_ns, std_vector CustomComponentConstructor = esphomelib_ns.class_('CustomComponentConstructor') +MULTI_CONF = True -CUSTOM_COMPONENT_SCHEMA = vol.Schema({ +CONFIG_SCHEMA = vol.Schema({ cv.GenerateID(): cv.declare_variable_id(CustomComponentConstructor), vol.Required(CONF_LAMBDA): cv.lambda_, vol.Optional(CONF_COMPONENTS): vol.All(cv.ensure_list, [vol.Schema({ @@ -16,19 +17,16 @@ CUSTOM_COMPONENT_SCHEMA = vol.Schema({ }).extend(cv.COMPONENT_SCHEMA.schema)]), }) -CONFIG_SCHEMA = vol.All(cv.ensure_list, [CUSTOM_COMPONENT_SCHEMA]) - def to_code(config): - for conf in config: - for template_ in process_lambda(conf[CONF_LAMBDA], [], - return_type=std_vector.template(ComponentPtr)): - yield + for template_ in process_lambda(config[CONF_LAMBDA], [], + return_type=std_vector.template(ComponentPtr)): + yield - rhs = CustomComponentConstructor(template_) - custom = variable(conf[CONF_ID], rhs) - for i, comp in enumerate(conf.get(CONF_COMPONENTS, [])): - setup_component(custom.get_component(i), comp) + rhs = CustomComponentConstructor(template_) + custom = variable(config[CONF_ID], rhs) + for i, comp in enumerate(config.get(CONF_COMPONENTS, [])): + setup_component(custom.get_component(i), comp) BUILD_FLAGS = '-DUSE_CUSTOM_COMPONENT' diff --git a/esphomeyaml/components/dallas.py b/esphomeyaml/components/dallas.py index 68d1c3d86a..5fc99f8288 100644 --- a/esphomeyaml/components/dallas.py +++ b/esphomeyaml/components/dallas.py @@ -9,19 +9,19 @@ from esphomeyaml.cpp_helpers import setup_component from esphomeyaml.cpp_types import App, PollingComponent DallasComponent = sensor.sensor_ns.class_('DallasComponent', PollingComponent) +MULTI_CONF = True -CONFIG_SCHEMA = vol.All(cv.ensure_list, [vol.Schema({ +CONFIG_SCHEMA = vol.Schema({ cv.GenerateID(): cv.declare_variable_id(DallasComponent), vol.Required(CONF_PIN): pins.input_pullup_pin, vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval, -}).extend(cv.COMPONENT_SCHEMA.schema)]) +}).extend(cv.COMPONENT_SCHEMA.schema) def to_code(config): - for conf in config: - rhs = App.make_dallas_component(conf[CONF_PIN], conf.get(CONF_UPDATE_INTERVAL)) - var = Pvariable(conf[CONF_ID], rhs) - setup_component(var, conf) + rhs = App.make_dallas_component(config[CONF_PIN], config.get(CONF_UPDATE_INTERVAL)) + var = Pvariable(config[CONF_ID], rhs) + setup_component(var, config) BUILD_FLAGS = '-DUSE_DALLAS_SENSOR' diff --git a/esphomeyaml/components/font.py b/esphomeyaml/components/font.py index f93abf0d48..10cb91efac 100644 --- a/esphomeyaml/components/font.py +++ b/esphomeyaml/components/font.py @@ -10,6 +10,7 @@ from esphomeyaml.cpp_generator import ArrayInitializer, MockObj, Pvariable, RawE from esphomeyaml.cpp_types import App DEPENDENCIES = ['display'] +MULTI_CONF = True Font = display.display_ns.class_('Font') Glyph = display.display_ns.class_('Glyph') @@ -76,46 +77,45 @@ FONT_SCHEMA = vol.Schema({ cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_variable_id(None), }) -CONFIG_SCHEMA = vol.All(validate_pillow_installed, cv.ensure_list, [FONT_SCHEMA]) +CONFIG_SCHEMA = vol.All(validate_pillow_installed, FONT_SCHEMA) def to_code(config): from PIL import ImageFont - for conf in config: - path = CORE.relative_path(conf[CONF_FILE]) - try: - font = ImageFont.truetype(path, conf[CONF_SIZE]) - except Exception as e: - raise core.EsphomeyamlError(u"Could not load truetype file {}: {}".format(path, e)) + path = CORE.relative_path(config[CONF_FILE]) + try: + font = ImageFont.truetype(path, config[CONF_SIZE]) + except Exception as e: + raise core.EsphomeyamlError(u"Could not load truetype file {}: {}".format(path, e)) - ascent, descent = font.getmetrics() + ascent, descent = font.getmetrics() - glyph_args = {} - data = [] - for glyph in conf[CONF_GLYPHS]: - mask = font.getmask(glyph, mode='1') - _, (offset_x, offset_y) = font.font.getsize(glyph) - width, height = mask.size - width8 = ((width + 7) // 8) * 8 - glyph_data = [0 for _ in range(height * width8 // 8)] # noqa: F812 - for y in range(height): - for x in range(width): - if not mask.getpixel((x, y)): - continue - pos = x + y * width8 - glyph_data[pos // 8] |= 0x80 >> (pos % 8) - glyph_args[glyph] = (len(data), offset_x, offset_y, width, height) - data += glyph_data + glyph_args = {} + data = [] + for glyph in config[CONF_GLYPHS]: + mask = font.getmask(glyph, mode='1') + _, (offset_x, offset_y) = font.font.getsize(glyph) + width, height = mask.size + width8 = ((width + 7) // 8) * 8 + glyph_data = [0 for _ in range(height * width8 // 8)] # noqa: F812 + for y in range(height): + for x in range(width): + if not mask.getpixel((x, y)): + continue + pos = x + y * width8 + glyph_data[pos // 8] |= 0x80 >> (pos % 8) + glyph_args[glyph] = (len(data), offset_x, offset_y, width, height) + data += glyph_data - raw_data = MockObj(conf[CONF_RAW_DATA_ID]) - add(RawExpression('static const uint8_t {}[{}] PROGMEM = {}'.format( - raw_data, len(data), - ArrayInitializer(*[HexInt(x) for x in data], multiline=False)))) + raw_data = MockObj(config[CONF_RAW_DATA_ID]) + add(RawExpression('static const uint8_t {}[{}] PROGMEM = {}'.format( + raw_data, len(data), + ArrayInitializer(*[HexInt(x) for x in data], multiline=False)))) - glyphs = [] - for glyph in conf[CONF_GLYPHS]: - glyphs.append(Glyph(glyph, raw_data, *glyph_args[glyph])) + glyphs = [] + for glyph in config[CONF_GLYPHS]: + glyphs.append(Glyph(glyph, raw_data, *glyph_args[glyph])) - rhs = App.make_font(ArrayInitializer(*glyphs), ascent, ascent + descent) - Pvariable(conf[CONF_ID], rhs) + rhs = App.make_font(ArrayInitializer(*glyphs), ascent, ascent + descent) + Pvariable(config[CONF_ID], rhs) diff --git a/esphomeyaml/components/globals.py b/esphomeyaml/components/globals.py index dd4e5c2160..03da1a11ec 100644 --- a/esphomeyaml/components/globals.py +++ b/esphomeyaml/components/globals.py @@ -8,29 +8,28 @@ from esphomeyaml.cpp_types import App, Component, esphomelib_ns GlobalVariableComponent = esphomelib_ns.class_('GlobalVariableComponent', Component) -GLOBAL_VAR_SCHEMA = vol.Schema({ +MULTI_CONF = True + +CONFIG_SCHEMA = vol.Schema({ vol.Required(CONF_ID): cv.declare_variable_id(GlobalVariableComponent), vol.Required(CONF_TYPE): cv.string_strict, vol.Optional(CONF_INITIAL_VALUE): cv.string_strict, vol.Optional(CONF_RESTORE_VALUE): cv.boolean, }).extend(cv.COMPONENT_SCHEMA.schema) -CONFIG_SCHEMA = vol.All(cv.ensure_list, [GLOBAL_VAR_SCHEMA]) - def to_code(config): - for conf in config: - type_ = RawExpression(conf[CONF_TYPE]) - template_args = TemplateArguments(type_) - res_type = GlobalVariableComponent.template(template_args) - initial_value = None - if CONF_INITIAL_VALUE in conf: - initial_value = RawExpression(conf[CONF_INITIAL_VALUE]) - rhs = App.Pmake_global_variable(template_args, initial_value) - glob = Pvariable(conf[CONF_ID], rhs, type=res_type) + type_ = RawExpression(config[CONF_TYPE]) + template_args = TemplateArguments(type_) + res_type = GlobalVariableComponent.template(template_args) + initial_value = None + if CONF_INITIAL_VALUE in config: + initial_value = RawExpression(config[CONF_INITIAL_VALUE]) + rhs = App.Pmake_global_variable(template_args, initial_value) + glob = Pvariable(config[CONF_ID], rhs, type=res_type) - if conf.get(CONF_RESTORE_VALUE, False): - hash_ = hash(conf[CONF_ID].id) % 2**32 - add(glob.set_restore_value(hash_)) + if config.get(CONF_RESTORE_VALUE, False): + hash_ = hash(config[CONF_ID].id) % 2**32 + add(glob.set_restore_value(hash_)) - setup_component(glob, conf) + setup_component(glob, config) diff --git a/esphomeyaml/components/image.py b/esphomeyaml/components/image.py index 3d321be243..adfeffe7d3 100644 --- a/esphomeyaml/components/image.py +++ b/esphomeyaml/components/image.py @@ -14,6 +14,7 @@ from esphomeyaml.cpp_types import App _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['display'] +MULTI_CONF = True Image_ = display.display_ns.class_('Image') @@ -26,40 +27,39 @@ IMAGE_SCHEMA = vol.Schema({ cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_variable_id(None), }) -CONFIG_SCHEMA = vol.All(font.validate_pillow_installed, cv.ensure_list, [IMAGE_SCHEMA]) +CONFIG_SCHEMA = vol.All(font.validate_pillow_installed, IMAGE_SCHEMA) def to_code(config): from PIL import Image - for conf in config: - path = CORE.relative_path(conf[CONF_FILE]) - try: - image = Image.open(path) - except Exception as e: - raise core.EsphomeyamlError(u"Could not load image file {}: {}".format(path, e)) + path = CORE.relative_path(config[CONF_FILE]) + try: + image = Image.open(path) + except Exception as e: + raise core.EsphomeyamlError(u"Could not load image file {}: {}".format(path, e)) - if CONF_RESIZE in conf: - image.thumbnail(conf[CONF_RESIZE]) + if CONF_RESIZE in config: + image.thumbnail(config[CONF_RESIZE]) - image = image.convert('1', dither=Image.NONE) - width, height = image.size - if width > 500 or height > 500: - _LOGGER.warning("The image you requested is very big. Please consider using the resize " - "parameter") - width8 = ((width + 7) // 8) * 8 - data = [0 for _ in range(height * width8 // 8)] - for y in range(height): - for x in range(width): - if image.getpixel((x, y)): - continue - pos = x + y * width8 - data[pos // 8] |= 0x80 >> (pos % 8) + image = image.convert('1', dither=Image.NONE) + width, height = image.size + if width > 500 or height > 500: + _LOGGER.warning("The image you requested is very big. Please consider using the resize " + "parameter") + width8 = ((width + 7) // 8) * 8 + data = [0 for _ in range(height * width8 // 8)] + for y in range(height): + for x in range(width): + if image.getpixel((x, y)): + continue + pos = x + y * width8 + data[pos // 8] |= 0x80 >> (pos % 8) - raw_data = MockObj(conf[CONF_RAW_DATA_ID]) - add(RawExpression('static const uint8_t {}[{}] PROGMEM = {}'.format( - raw_data, len(data), - ArrayInitializer(*[HexInt(x) for x in data], multiline=False)))) + raw_data = MockObj(config[CONF_RAW_DATA_ID]) + add(RawExpression('static const uint8_t {}[{}] PROGMEM = {}'.format( + raw_data, len(data), + ArrayInitializer(*[HexInt(x) for x in data], multiline=False)))) - rhs = App.make_image(raw_data, width, height) - Pvariable(conf[CONF_ID], rhs) + rhs = App.make_image(raw_data, width, height) + Pvariable(config[CONF_ID], rhs) diff --git a/esphomeyaml/components/my9231.py b/esphomeyaml/components/my9231.py index d8188d3378..602d1f52ad 100644 --- a/esphomeyaml/components/my9231.py +++ b/esphomeyaml/components/my9231.py @@ -10,8 +10,9 @@ from esphomeyaml.cpp_helpers import gpio_output_pin_expression, setup_component from esphomeyaml.cpp_types import App, Component MY9231OutputComponent = output.output_ns.class_('MY9231OutputComponent', Component) +MULTI_CONF = True -MY9231_SCHEMA = vol.Schema({ +CONFIG_SCHEMA = vol.Schema({ cv.GenerateID(): cv.declare_variable_id(MY9231OutputComponent), vol.Required(CONF_DATA_PIN): pins.gpio_output_pin_schema, vol.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema, @@ -23,26 +24,23 @@ MY9231_SCHEMA = vol.Schema({ vol.Optional(CONF_UPDATE_ON_BOOT): vol.Coerce(bool), }).extend(cv.COMPONENT_SCHEMA.schema) -CONFIG_SCHEMA = vol.All(cv.ensure_list, [MY9231_SCHEMA]) - def to_code(config): - for conf in config: - for di in gpio_output_pin_expression(conf[CONF_DATA_PIN]): - yield - for dcki in gpio_output_pin_expression(conf[CONF_CLOCK_PIN]): - yield - rhs = App.make_my9231_component(di, dcki) - my9231 = Pvariable(conf[CONF_ID], rhs) - if CONF_NUM_CHANNELS in conf: - add(my9231.set_num_channels(conf[CONF_NUM_CHANNELS])) - if CONF_NUM_CHIPS in conf: - add(my9231.set_num_chips(conf[CONF_NUM_CHIPS])) - if CONF_BIT_DEPTH in conf: - add(my9231.set_bit_depth(conf[CONF_BIT_DEPTH])) - if CONF_UPDATE_ON_BOOT in conf: - add(my9231.set_update(conf[CONF_UPDATE_ON_BOOT])) - setup_component(my9231, conf) + for di in gpio_output_pin_expression(config[CONF_DATA_PIN]): + yield + for dcki in gpio_output_pin_expression(config[CONF_CLOCK_PIN]): + yield + rhs = App.make_my9231_component(di, dcki) + my9231 = Pvariable(config[CONF_ID], rhs) + if CONF_NUM_CHANNELS in config: + add(my9231.set_num_channels(config[CONF_NUM_CHANNELS])) + if CONF_NUM_CHIPS in config: + add(my9231.set_num_chips(config[CONF_NUM_CHIPS])) + if CONF_BIT_DEPTH in config: + add(my9231.set_bit_depth(config[CONF_BIT_DEPTH])) + if CONF_UPDATE_ON_BOOT in config: + add(my9231.set_update(config[CONF_UPDATE_ON_BOOT])) + setup_component(my9231, config) BUILD_FLAGS = '-DUSE_MY9231_OUTPUT' diff --git a/esphomeyaml/components/pca9685.py b/esphomeyaml/components/pca9685.py index 06ec49ced8..e30758b310 100644 --- a/esphomeyaml/components/pca9685.py +++ b/esphomeyaml/components/pca9685.py @@ -2,38 +2,31 @@ import voluptuous as vol from esphomeyaml.components import i2c, output import esphomeyaml.config_validation as cv -from esphomeyaml.const import CONF_ADDRESS, CONF_FREQUENCY, CONF_ID, CONF_PHASE_BALANCER +from esphomeyaml.const import CONF_ADDRESS, CONF_FREQUENCY, CONF_ID from esphomeyaml.cpp_generator import Pvariable, add from esphomeyaml.cpp_helpers import setup_component from esphomeyaml.cpp_types import App, Component DEPENDENCIES = ['i2c'] +MULTI_CONF = True PCA9685OutputComponent = output.output_ns.class_('PCA9685OutputComponent', Component, i2c.I2CDevice) -PHASE_BALANCER_MESSAGE = ("The phase_balancer option has been removed in version 1.5.0. " - "esphomelib will now automatically choose a suitable phase balancer.") - -PCA9685_SCHEMA = vol.Schema({ +CONFIG_SCHEMA = vol.Schema({ cv.GenerateID(): cv.declare_variable_id(PCA9685OutputComponent), vol.Required(CONF_FREQUENCY): vol.All(cv.frequency, vol.Range(min=23.84, max=1525.88)), vol.Optional(CONF_ADDRESS): cv.i2c_address, - - vol.Optional(CONF_PHASE_BALANCER): cv.invalid(PHASE_BALANCER_MESSAGE), }).extend(cv.COMPONENT_SCHEMA.schema) -CONFIG_SCHEMA = vol.All(cv.ensure_list, [PCA9685_SCHEMA]) - def to_code(config): - for conf in config: - rhs = App.make_pca9685_component(conf.get(CONF_FREQUENCY)) - pca9685 = Pvariable(conf[CONF_ID], rhs) - if CONF_ADDRESS in conf: - add(pca9685.set_address(conf[CONF_ADDRESS])) - setup_component(pca9685, conf) + rhs = App.make_pca9685_component(config.get(CONF_FREQUENCY)) + pca9685 = Pvariable(config[CONF_ID], rhs) + if CONF_ADDRESS in config: + add(pca9685.set_address(config[CONF_ADDRESS])) + setup_component(pca9685, config) BUILD_FLAGS = '-DUSE_PCA9685_OUTPUT' diff --git a/esphomeyaml/components/pcf8574.py b/esphomeyaml/components/pcf8574.py index 24ac80c671..ef68413552 100644 --- a/esphomeyaml/components/pcf8574.py +++ b/esphomeyaml/components/pcf8574.py @@ -8,6 +8,7 @@ from esphomeyaml.cpp_helpers import setup_component from esphomeyaml.cpp_types import App, GPIOInputPin, GPIOOutputPin, io_ns DEPENDENCIES = ['i2c'] +MULTI_CONF = True PCF8574GPIOMode = io_ns.enum('PCF8574GPIOMode') PCF8675_GPIO_MODES = { @@ -19,20 +20,17 @@ PCF8675_GPIO_MODES = { PCF8574GPIOInputPin = io_ns.class_('PCF8574GPIOInputPin', GPIOInputPin) PCF8574GPIOOutputPin = io_ns.class_('PCF8574GPIOOutputPin', GPIOOutputPin) -PCF8574_SCHEMA = vol.Schema({ +CONFIG_SCHEMA = vol.Schema({ vol.Required(CONF_ID): cv.declare_variable_id(pins.PCF8574Component), vol.Optional(CONF_ADDRESS, default=0x21): cv.i2c_address, vol.Optional(CONF_PCF8575, default=False): cv.boolean, }).extend(cv.COMPONENT_SCHEMA.schema) -CONFIG_SCHEMA = vol.All(cv.ensure_list, [PCF8574_SCHEMA]) - def to_code(config): - for conf in config: - rhs = App.make_pcf8574_component(conf[CONF_ADDRESS], conf[CONF_PCF8575]) - var = Pvariable(conf[CONF_ID], rhs) - setup_component(var, conf) + rhs = App.make_pcf8574_component(config[CONF_ADDRESS], config[CONF_PCF8575]) + var = Pvariable(config[CONF_ID], rhs) + setup_component(var, config) BUILD_FLAGS = '-DUSE_PCF8574' diff --git a/esphomeyaml/components/pn532.py b/esphomeyaml/components/pn532.py index a7562cc943..9d2afbf0e5 100644 --- a/esphomeyaml/components/pn532.py +++ b/esphomeyaml/components/pn532.py @@ -11,12 +11,13 @@ from esphomeyaml.cpp_helpers import gpio_output_pin_expression, setup_component from esphomeyaml.cpp_types import App, PollingComponent, Trigger, std_string DEPENDENCIES = ['spi'] +MULTI_CONF = True PN532Component = binary_sensor.binary_sensor_ns.class_('PN532Component', PollingComponent, spi.SPIDevice) PN532Trigger = binary_sensor.binary_sensor_ns.class_('PN532Trigger', Trigger.template(std_string)) -CONFIG_SCHEMA = vol.All(cv.ensure_list, [vol.Schema({ +CONFIG_SCHEMA = vol.Schema({ cv.GenerateID(): cv.declare_variable_id(PN532Component), cv.GenerateID(CONF_SPI_ID): cv.use_variable_id(SPIComponent), vol.Required(CONF_CS_PIN): pins.gpio_output_pin_schema, @@ -24,23 +25,22 @@ CONFIG_SCHEMA = vol.All(cv.ensure_list, [vol.Schema({ vol.Optional(CONF_ON_TAG): automation.validate_automation({ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(PN532Trigger), }), -}).extend(cv.COMPONENT_SCHEMA.schema)]) +}).extend(cv.COMPONENT_SCHEMA.schema) def to_code(config): - for conf in config: - for spi_ in get_variable(conf[CONF_SPI_ID]): - yield - for cs in gpio_output_pin_expression(conf[CONF_CS_PIN]): - yield - rhs = App.make_pn532_component(spi_, cs, conf.get(CONF_UPDATE_INTERVAL)) - pn532 = Pvariable(conf[CONF_ID], rhs) + for spi_ in get_variable(config[CONF_SPI_ID]): + yield + for cs in gpio_output_pin_expression(config[CONF_CS_PIN]): + yield + rhs = App.make_pn532_component(spi_, cs, config.get(CONF_UPDATE_INTERVAL)) + pn532 = Pvariable(config[CONF_ID], rhs) - for conf_ in conf.get(CONF_ON_TAG, []): - trigger = Pvariable(conf_[CONF_TRIGGER_ID], pn532.make_trigger()) - automation.build_automation(trigger, std_string, conf_) + for conf_ in config.get(CONF_ON_TAG, []): + trigger = Pvariable(conf_[CONF_TRIGGER_ID], pn532.make_trigger()) + automation.build_automation(trigger, std_string, conf_) - setup_component(pn532, conf) + setup_component(pn532, config) BUILD_FLAGS = '-DUSE_PN532' diff --git a/esphomeyaml/components/power_supply.py b/esphomeyaml/components/power_supply.py index bb69d443f6..de02c708e5 100644 --- a/esphomeyaml/components/power_supply.py +++ b/esphomeyaml/components/power_supply.py @@ -9,29 +9,28 @@ from esphomeyaml.cpp_types import App, Component, esphomelib_ns PowerSupplyComponent = esphomelib_ns.class_('PowerSupplyComponent', Component) -POWER_SUPPLY_SCHEMA = vol.Schema({ +MULTI_CONF = True + +CONFIG_SCHEMA = vol.Schema({ vol.Required(CONF_ID): cv.declare_variable_id(PowerSupplyComponent), vol.Required(CONF_PIN): pins.gpio_output_pin_schema, vol.Optional(CONF_ENABLE_TIME): cv.positive_time_period_milliseconds, vol.Optional(CONF_KEEP_ON_TIME): cv.positive_time_period_milliseconds, }).extend(cv.COMPONENT_SCHEMA.schema) -CONFIG_SCHEMA = vol.All(cv.ensure_list, [POWER_SUPPLY_SCHEMA]) - def to_code(config): - for conf in config: - for pin in gpio_output_pin_expression(conf[CONF_PIN]): - yield + for pin in gpio_output_pin_expression(config[CONF_PIN]): + yield - rhs = App.make_power_supply(pin) - psu = Pvariable(conf[CONF_ID], rhs) - if CONF_ENABLE_TIME in conf: - add(psu.set_enable_time(conf[CONF_ENABLE_TIME])) - if CONF_KEEP_ON_TIME in conf: - add(psu.set_keep_on_time(conf[CONF_KEEP_ON_TIME])) + rhs = App.make_power_supply(pin) + psu = Pvariable(config[CONF_ID], rhs) + if CONF_ENABLE_TIME in config: + add(psu.set_enable_time(config[CONF_ENABLE_TIME])) + if CONF_KEEP_ON_TIME in config: + add(psu.set_keep_on_time(config[CONF_KEEP_ON_TIME])) - setup_component(psu, conf) + setup_component(psu, config) BUILD_FLAGS = '-DUSE_OUTPUT' diff --git a/esphomeyaml/components/rdm6300.py b/esphomeyaml/components/rdm6300.py index c880ed7f3f..7f38895e98 100644 --- a/esphomeyaml/components/rdm6300.py +++ b/esphomeyaml/components/rdm6300.py @@ -12,19 +12,18 @@ DEPENDENCIES = ['uart'] RDM6300Component = binary_sensor.binary_sensor_ns.class_('RDM6300Component', Component, uart.UARTDevice) -CONFIG_SCHEMA = vol.All(cv.ensure_list_not_empty, [vol.Schema({ +CONFIG_SCHEMA = vol.Schema({ cv.GenerateID(): cv.declare_variable_id(RDM6300Component), cv.GenerateID(CONF_UART_ID): cv.use_variable_id(uart.UARTComponent), -}).extend(cv.COMPONENT_SCHEMA.schema)]) +}).extend(cv.COMPONENT_SCHEMA.schema) def to_code(config): - for conf in config: - for uart_ in get_variable(conf[CONF_UART_ID]): - yield - rhs = App.make_rdm6300_component(uart_) - var = Pvariable(conf[CONF_ID], rhs) - setup_component(var, conf) + for uart_ in get_variable(config[CONF_UART_ID]): + yield + rhs = App.make_rdm6300_component(uart_) + var = Pvariable(config[CONF_ID], rhs) + setup_component(var, config) BUILD_FLAGS = '-DUSE_RDM6300' diff --git a/esphomeyaml/components/remote_receiver.py b/esphomeyaml/components/remote_receiver.py index 6e063673a5..a9253b5a77 100644 --- a/esphomeyaml/components/remote_receiver.py +++ b/esphomeyaml/components/remote_receiver.py @@ -9,6 +9,7 @@ from esphomeyaml.cpp_helpers import gpio_input_pin_expression, setup_component from esphomeyaml.cpp_types import App, Component, esphomelib_ns remote_ns = esphomelib_ns.namespace('remote') +MULTI_CONF = True RemoteControlComponentBase = remote_ns.class_('RemoteControlComponentBase') RemoteReceiverComponent = remote_ns.class_('RemoteReceiverComponent', @@ -36,7 +37,7 @@ def validate_dumpers_all(value): raise vol.Invalid("Not valid dumpers") -CONFIG_SCHEMA = vol.All(cv.ensure_list, [vol.Schema({ +CONFIG_SCHEMA = vol.Schema({ cv.GenerateID(): cv.declare_variable_id(RemoteReceiverComponent), vol.Required(CONF_PIN): pins.gpio_input_pin_schema, vol.Optional(CONF_DUMP, default=[]): @@ -46,28 +47,27 @@ CONFIG_SCHEMA = vol.All(cv.ensure_list, [vol.Schema({ vol.Optional(CONF_BUFFER_SIZE): cv.validate_bytes, vol.Optional(CONF_FILTER): cv.positive_time_period_microseconds, vol.Optional(CONF_IDLE): cv.positive_time_period_microseconds, -}).extend(cv.COMPONENT_SCHEMA.schema)]) +}).extend(cv.COMPONENT_SCHEMA.schema) def to_code(config): - for conf in config: - for pin in gpio_input_pin_expression(conf[CONF_PIN]): - yield - rhs = App.make_remote_receiver_component(pin) - receiver = Pvariable(conf[CONF_ID], rhs) + for pin in gpio_input_pin_expression(config[CONF_PIN]): + yield + rhs = App.make_remote_receiver_component(pin) + receiver = Pvariable(config[CONF_ID], rhs) - for dumper in conf[CONF_DUMP]: - add(receiver.add_dumper(DUMPERS[dumper].new())) - if CONF_TOLERANCE in conf: - add(receiver.set_tolerance(conf[CONF_TOLERANCE])) - if CONF_BUFFER_SIZE in conf: - add(receiver.set_buffer_size(conf[CONF_BUFFER_SIZE])) - if CONF_FILTER in conf: - add(receiver.set_filter_us(conf[CONF_FILTER])) - if CONF_IDLE in conf: - add(receiver.set_idle_us(conf[CONF_IDLE])) + for dumper in config[CONF_DUMP]: + add(receiver.add_dumper(DUMPERS[dumper].new())) + if CONF_TOLERANCE in config: + add(receiver.set_tolerance(config[CONF_TOLERANCE])) + if CONF_BUFFER_SIZE in config: + add(receiver.set_buffer_size(config[CONF_BUFFER_SIZE])) + if CONF_FILTER in config: + add(receiver.set_filter_us(config[CONF_FILTER])) + if CONF_IDLE in config: + add(receiver.set_idle_us(config[CONF_IDLE])) - setup_component(receiver, conf) + setup_component(receiver, config) BUILD_FLAGS = '-DUSE_REMOTE_RECEIVER' diff --git a/esphomeyaml/components/remote_transmitter.py b/esphomeyaml/components/remote_transmitter.py index 50a41f8c86..5502a900e6 100644 --- a/esphomeyaml/components/remote_transmitter.py +++ b/esphomeyaml/components/remote_transmitter.py @@ -16,6 +16,8 @@ RemoteTransmitterComponent = remote_ns.class_('RemoteTransmitterComponent', RCSwitchProtocol = remote_ns.class_('RCSwitchProtocol') rc_switch_protocols = remote_ns.rc_switch_protocols +MULTI_CONF = True + def validate_rc_switch_code(value): if not isinstance(value, (str, unicode)): @@ -76,12 +78,12 @@ RC_SWITCH_TYPE_D_SCHEMA = vol.Schema({ vol.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA, }) -CONFIG_SCHEMA = vol.All(cv.ensure_list, [vol.Schema({ +CONFIG_SCHEMA = vol.Schema({ cv.GenerateID(): cv.declare_variable_id(RemoteTransmitterComponent), vol.Required(CONF_PIN): pins.gpio_output_pin_schema, vol.Optional(CONF_CARRIER_DUTY_PERCENT): vol.All(cv.percentage_int, vol.Range(min=1, max=100)), -}).extend(cv.COMPONENT_SCHEMA.schema)]) +}).extend(cv.COMPONENT_SCHEMA.schema) def build_rc_switch_protocol(config): @@ -103,16 +105,15 @@ def binary_code(value): def to_code(config): - for conf in config: - for pin in gpio_output_pin_expression(conf[CONF_PIN]): - yield - rhs = App.make_remote_transmitter_component(pin) - transmitter = Pvariable(conf[CONF_ID], rhs) + for pin in gpio_output_pin_expression(config[CONF_PIN]): + yield + rhs = App.make_remote_transmitter_component(pin) + transmitter = Pvariable(config[CONF_ID], rhs) - if CONF_CARRIER_DUTY_PERCENT in conf: - add(transmitter.set_carrier_duty_percent(conf[CONF_CARRIER_DUTY_PERCENT])) + if CONF_CARRIER_DUTY_PERCENT in config: + add(transmitter.set_carrier_duty_percent(config[CONF_CARRIER_DUTY_PERCENT])) - setup_component(transmitter, conf) + setup_component(transmitter, config) BUILD_FLAGS = '-DUSE_REMOTE_TRANSMITTER' diff --git a/esphomeyaml/components/spi.py b/esphomeyaml/components/spi.py index ad6fe1e69e..7171c6754a 100644 --- a/esphomeyaml/components/spi.py +++ b/esphomeyaml/components/spi.py @@ -10,33 +10,31 @@ from esphomeyaml.cpp_types import App, Component, esphomelib_ns SPIComponent = esphomelib_ns.class_('SPIComponent', Component) SPIDevice = esphomelib_ns.class_('SPIDevice') +MULTI_CONF = True -SPI_SCHEMA = vol.All(vol.Schema({ +CONFIG_SCHEMA = vol.All(vol.Schema({ cv.GenerateID(): cv.declare_variable_id(SPIComponent), vol.Required(CONF_CLK_PIN): pins.gpio_output_pin_schema, vol.Optional(CONF_MISO_PIN): pins.gpio_input_pin_schema, vol.Optional(CONF_MOSI_PIN): pins.gpio_output_pin_schema, }), cv.has_at_least_one_key(CONF_MISO_PIN, CONF_MOSI_PIN)) -CONFIG_SCHEMA = vol.All(cv.ensure_list, [SPI_SCHEMA]) - def to_code(config): - for conf in config: - for clk in gpio_output_pin_expression(conf[CONF_CLK_PIN]): + for clk in gpio_output_pin_expression(config[CONF_CLK_PIN]): + yield + rhs = App.init_spi(clk) + spi = Pvariable(config[CONF_ID], rhs) + if CONF_MISO_PIN in config: + for miso in gpio_input_pin_expression(config[CONF_MISO_PIN]): yield - rhs = App.init_spi(clk) - spi = Pvariable(conf[CONF_ID], rhs) - if CONF_MISO_PIN in conf: - for miso in gpio_input_pin_expression(conf[CONF_MISO_PIN]): - yield - add(spi.set_miso(miso)) - if CONF_MOSI_PIN in conf: - for mosi in gpio_input_pin_expression(conf[CONF_MOSI_PIN]): - yield - add(spi.set_mosi(mosi)) + add(spi.set_miso(miso)) + if CONF_MOSI_PIN in config: + for mosi in gpio_input_pin_expression(config[CONF_MOSI_PIN]): + yield + add(spi.set_mosi(mosi)) - setup_component(spi, conf) + setup_component(spi, config) BUILD_FLAGS = '-DUSE_SPI' diff --git a/esphomeyaml/components/uart.py b/esphomeyaml/components/uart.py index cdb2581d6b..2e4d5b7704 100644 --- a/esphomeyaml/components/uart.py +++ b/esphomeyaml/components/uart.py @@ -9,25 +9,23 @@ from esphomeyaml.cpp_types import App, Component, esphomelib_ns UARTComponent = esphomelib_ns.class_('UARTComponent', Component) UARTDevice = esphomelib_ns.class_('UARTDevice') +MULTI_CONF = True -UART_SCHEMA = vol.All(vol.Schema({ +CONFIG_SCHEMA = vol.All(vol.Schema({ cv.GenerateID(): cv.declare_variable_id(UARTComponent), vol.Optional(CONF_TX_PIN): pins.output_pin, vol.Optional(CONF_RX_PIN): pins.input_pin, vol.Required(CONF_BAUD_RATE): cv.positive_int, }).extend(cv.COMPONENT_SCHEMA.schema), cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN)) -CONFIG_SCHEMA = vol.All(cv.ensure_list, [UART_SCHEMA]) - def to_code(config): - for conf in config: - tx = conf.get(CONF_TX_PIN, -1) - rx = conf.get(CONF_RX_PIN, -1) - rhs = App.init_uart(tx, rx, conf[CONF_BAUD_RATE]) - var = Pvariable(conf[CONF_ID], rhs) + tx = config.get(CONF_TX_PIN, -1) + rx = config.get(CONF_RX_PIN, -1) + rhs = App.init_uart(tx, rx, config[CONF_BAUD_RATE]) + var = Pvariable(config[CONF_ID], rhs) - setup_component(var, conf) + setup_component(var, config) BUILD_FLAGS = '-DUSE_UART' diff --git a/esphomeyaml/config.py b/esphomeyaml/config.py index 97cca2245f..c8d194a346 100644 --- a/esphomeyaml/config.py +++ b/esphomeyaml/config.py @@ -52,7 +52,11 @@ def iter_components(config): yield CONF_ESPHOMEYAML, core_config, conf continue component = get_component(domain) - yield domain, component, conf + if getattr(component, 'MULTI_CONF', False): + for conf_ in conf: + yield domain, component, conf_ + else: + yield domain, component, conf if is_platform_component(component): for p_config in conf: p_name = u"{}.{}".format(domain, p_config[CONF_PLATFORM]) @@ -180,7 +184,7 @@ def do_id_pass(result): # type: (Config) -> None match = next((v[0] for v in declare_ids if v[0].id == id.id), None) if match is None: # No declared ID with this name - result.add_error("Couldn't find ID {}".format(id.id), path) + result.add_error("Couldn't find ID '{}'".format(id.id), path) continue if not isinstance(match.type, MockObjClass) or not isinstance(id.type, MockObjClass): continue @@ -198,7 +202,7 @@ def do_id_pass(result): # type: (Config) -> None id.id = v[0].id break else: - result.add_error("Couldn't resolve ID for type {}".format(id.type), path) + result.add_error("Couldn't resolve ID for type '{}'".format(id.type), path) def validate_config(config): @@ -220,6 +224,7 @@ def validate_config(config): # Step 1: Load everything result.add_domain([CONF_ESPHOMEYAML], CONF_ESPHOMEYAML) + result[CONF_ESPHOMEYAML] = config[CONF_ESPHOMEYAML] for domain, conf in config.iteritems(): domain = str(domain) @@ -229,13 +234,16 @@ def validate_config(config): result.add_domain([domain], domain) result[domain] = conf if conf is None: - config[domain] = conf = {} + result[domain] = conf = {} component = get_component(domain) if component is None: result.add_error(u"Component not found: {}".format(domain), [domain]) skip_paths.append([domain]) continue + if not isinstance(conf, list) and getattr(component, 'MULTI_CONF', False): + result[domain] = conf = [conf] + success = True dependencies = getattr(component, 'DEPENDENCIES', []) for dependency in dependencies: @@ -320,24 +328,33 @@ def validate_config(config): # Step 2: Validate configuration try: - result[CONF_ESPHOMEYAML] = config[CONF_ESPHOMEYAML] - result[CONF_ESPHOMEYAML] = core_config.CONFIG_SCHEMA(config[CONF_ESPHOMEYAML]) + result[CONF_ESPHOMEYAML] = core_config.CONFIG_SCHEMA(result[CONF_ESPHOMEYAML]) except vol.Invalid as ex: _comp_error(ex, [CONF_ESPHOMEYAML]) - for domain, conf in config.iteritems(): + for domain, conf in result.iteritems(): domain = str(domain) if [domain] in skip_paths: continue component = get_component(domain) if hasattr(component, 'CONFIG_SCHEMA'): - try: - validated = component.CONFIG_SCHEMA(conf) - result[domain] = validated - except vol.Invalid as ex: - _comp_error(ex, [domain]) - continue + multi_conf = getattr(component, 'MULTI_CONF', False) + + if multi_conf: + for i, conf_ in enumerate(conf): + try: + validated = component.CONFIG_SCHEMA(conf_) + result[domain][i] = validated + except vol.Invalid as ex: + _comp_error(ex, [domain, i]) + else: + try: + validated = component.CONFIG_SCHEMA(conf) + result[domain] = validated + except vol.Invalid as ex: + _comp_error(ex, [domain]) + continue if not hasattr(component, 'PLATFORM_SCHEMA'): continue @@ -377,12 +394,14 @@ def humanize_error(config, validation_error): except (TypeError, ValueError): pass validation_error = unicode(validation_error) - m = re.match(r'^(.*)\s*for dictionary value @.*$', validation_error) + m = re.match(r'^(.*?)\s*(?:for dictionary value )?@ data\[.*$', validation_error) if m is not None: validation_error = m.group(1) validation_error = validation_error.strip() if not validation_error.endswith(u'.'): validation_error += u'.' + if offending_item_summary is None: + return validation_error return u"{} Got '{}'".format(validation_error, offending_item_summary) @@ -402,7 +421,7 @@ def _format_vol_invalid(ex, config, path, domain): paren = domain message += u"'{}' is a required option for [{}].".format(ex.path[-1], paren) else: - message += u'{}.'.format(humanize_error(_nested_getitem(config, path), ex)) + message += humanize_error(_nested_getitem(config, path), ex) return message @@ -427,10 +446,10 @@ def load_config(): return result -def line_info(obj): +def line_info(obj, highlight=True): """Display line config source.""" if hasattr(obj, '__config_file__'): - return color('cyan', "[source {}:{}]" + return color('cyan' if highlight else 'white', "[source {}:{}]" .format(obj.__config_file__, obj.__line__ or '?')) return None @@ -473,7 +492,7 @@ def dump_dict(config, path, at_root=True): sep = color('red', sep) msg, _ = dump_dict(config, path_, at_root=False) msg = indent(msg) - inf = line_info(config.nested_item(path_)) + inf = line_info(config.nested_item(path_), highlight=config.is_in_error_path(path_)) if inf is not None: msg = inf + u'\n' + msg elif msg: @@ -496,7 +515,7 @@ def dump_dict(config, path, at_root=True): st = color('red', st) msg, m = dump_dict(config, path_, at_root=False) - inf = line_info(config.nested_item(path_)) + inf = line_info(config.nested_item(path_), highlight=config.is_in_error_path(path_)) if m: msg = u'\n' + indent(msg) @@ -531,7 +550,27 @@ def dump_dict(config, path, at_root=True): return ret, multiline -def read_config(): +def strip_default_ids(config): + if isinstance(config, list): + to_remove = [] + for i, x in enumerate(config): + x = config[i] = strip_default_ids(x) + if isinstance(x, core.ID) and not x.is_manual: + to_remove.append(x) + for x in to_remove: + config.remove(x) + elif isinstance(config, dict): + to_remove = [] + for k, v in config.iteritems(): + v = config[k] = strip_default_ids(v) + if isinstance(v, core.ID) and not v.is_manual: + to_remove.append(k) + for k in to_remove: + config.pop(k) + return config + + +def read_config(verbose): _LOGGER.info("Reading configuration...") try: res = load_config() @@ -539,6 +578,9 @@ def read_config(): _LOGGER.error(u"Error while reading config: %s", err) return None if res.errors: + if not verbose: + res = strip_default_ids(res) + safe_print(color('bold_red', u"Failed config")) safe_print('') for path, domain in res.domains: diff --git a/esphomeyaml/dashboard/static/esphomeyaml.js b/esphomeyaml/dashboard/static/esphomeyaml.js index 38f5f7345b..4760712365 100644 --- a/esphomeyaml/dashboard/static/esphomeyaml.js +++ b/esphomeyaml/dashboard/static/esphomeyaml.js @@ -101,7 +101,6 @@ const colorReplace = (pre, state, text) => { break; case 31: state.foregroundColor = "red"; - state.bold = true; break; case 32: state.foregroundColor = "green"; diff --git a/esphomeyaml/yaml_util.py b/esphomeyaml/yaml_util.py index 8d971635de..be2c8cd558 100644 --- a/esphomeyaml/yaml_util.py +++ b/esphomeyaml/yaml_util.py @@ -171,7 +171,7 @@ def _add_reference(obj, loader, node): if isinstance(obj, (str, unicode)): obj = NodeStrClass(obj) if isinstance(obj, list): - return obj + obj = NodeListClass(obj) setattr(obj, '__config_file__', loader.name) setattr(obj, '__line__', node.start_mark.line) return obj @@ -369,7 +369,7 @@ yaml.SafeDumper.add_representer( yaml.SafeDumper.add_representer( NodeListClass, lambda dumper, value: - dumper.represent_sequence(dumper, 'tag:yaml.org,2002:map', value) + dumper.represent_sequence('tag:yaml.org,2002:seq', value) ) yaml.SafeDumper.add_representer(unicode, unicode_representer)