From 262855ff6239e16ac3964b2427f42807a964d0ee Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 14 May 2018 11:50:56 +0200 Subject: [PATCH] Preparations for 1.5.0 --- Dockerfile | 4 - esphomeyaml/__main__.py | 4 + esphomeyaml/components/ads1115.py | 23 +-- .../components/binary_sensor/__init__.py | 15 +- .../components/binary_sensor/esp32_ble.py | 44 +++++ .../components/binary_sensor/esp32_touch.py | 49 ++++++ esphomeyaml/components/dallas.py | 2 +- esphomeyaml/components/deep_sleep.py | 4 +- esphomeyaml/components/esp32_ble.py | 22 +++ esphomeyaml/components/esp32_touch.py | 83 ++++++++++ esphomeyaml/components/i2c.py | 10 +- esphomeyaml/components/ir_transmitter.py | 7 +- esphomeyaml/components/light/__init__.py | 4 +- .../components/light/fastled_clockless.py | 98 +++++++++++ esphomeyaml/components/light/fastled_spi.py | 69 ++++++++ esphomeyaml/components/light/monochromatic.py | 6 +- esphomeyaml/components/light/rgb.py | 4 +- esphomeyaml/components/light/rgbw.py | 6 +- esphomeyaml/components/logger.py | 2 +- esphomeyaml/components/mqtt.py | 2 +- esphomeyaml/components/ota.py | 6 + esphomeyaml/components/pca9685.py | 8 +- esphomeyaml/components/power_supply.py | 4 +- esphomeyaml/components/sensor/__init__.py | 2 +- esphomeyaml/components/sensor/adc.py | 2 +- esphomeyaml/components/sensor/ads1115.py | 30 ++-- esphomeyaml/components/sensor/bh1750.py | 37 +++++ esphomeyaml/components/sensor/bme280.py | 75 +++++++++ esphomeyaml/components/sensor/bme680.py | 84 ++++++++++ esphomeyaml/components/sensor/bmp085.py | 2 +- esphomeyaml/components/sensor/dallas.py | 4 +- esphomeyaml/components/sensor/dht.py | 23 ++- esphomeyaml/components/sensor/hdc1080.py | 2 +- esphomeyaml/components/sensor/htu21d.py | 2 +- esphomeyaml/components/sensor/mpu6050.py | 2 +- .../components/sensor/pulse_counter.py | 2 +- esphomeyaml/components/sensor/tsl2561.py | 52 ++++++ esphomeyaml/components/sensor/ultrasonic.py | 4 +- .../components/switch/ir_transmitter.py | 26 +-- esphomeyaml/components/web_server.py | 9 +- esphomeyaml/components/wifi.py | 10 +- esphomeyaml/config.py | 2 +- esphomeyaml/config_validation.py | 113 ++++++++----- esphomeyaml/const.py | 22 ++- esphomeyaml/core.py | 153 ++++++++++++++++++ esphomeyaml/helpers.py | 46 ++++-- esphomeyaml/pins.py | 66 ++------ esphomeyaml/writer.py | 26 +-- esphomeyaml/yaml_util.py | 31 +++- 49 files changed, 1082 insertions(+), 221 deletions(-) create mode 100644 esphomeyaml/components/binary_sensor/esp32_ble.py create mode 100644 esphomeyaml/components/binary_sensor/esp32_touch.py create mode 100644 esphomeyaml/components/esp32_ble.py create mode 100644 esphomeyaml/components/esp32_touch.py create mode 100644 esphomeyaml/components/light/fastled_clockless.py create mode 100644 esphomeyaml/components/light/fastled_spi.py create mode 100644 esphomeyaml/components/sensor/bh1750.py create mode 100644 esphomeyaml/components/sensor/bme280.py create mode 100644 esphomeyaml/components/sensor/bme680.py create mode 100644 esphomeyaml/components/sensor/tsl2561.py diff --git a/Dockerfile b/Dockerfile index fc58f1ec54..147a473926 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,10 +13,6 @@ COPY docker/platformio.ini /usr/src/app/ RUN platformio settings set enable_telemetry No && \ platformio run -e espressif32 -e espressif8266; exit 0 -# Fix issue with static IP on ESP32: https://github.com/espressif/arduino-esp32/issues/1081 -RUN curl https://github.com/espressif/arduino-esp32/commit/144480637a718844b8f48f4392da8d4f622f2e5e.patch | \ - patch /root/.platformio/packages/framework-arduinoespressif32/libraries/WiFi/src/WiFiGeneric.cpp - COPY . . RUN pip install -e . diff --git a/esphomeyaml/__main__.py b/esphomeyaml/__main__.py index aea995a142..55fe6f3aa1 100644 --- a/esphomeyaml/__main__.py +++ b/esphomeyaml/__main__.py @@ -156,6 +156,10 @@ def upload_program(config, args, port): return run_platformio('platformio', 'run', '-d', get_base_path(config), '-t', 'upload', '--upload-port', port) + if 'ota' not in config: + _LOGGER.error("No serial port found and OTA not enabled. Can't upload!") + return -1 + if CONF_MANUAL_IP in config[CONF_WIFI]: host = str(config[CONF_WIFI][CONF_MANUAL_IP][CONF_STATIC_IP]) elif CONF_HOSTNAME in config[CONF_WIFI]: diff --git a/esphomeyaml/components/ads1115.py b/esphomeyaml/components/ads1115.py index bee0d1191f..4d5b1740c5 100644 --- a/esphomeyaml/components/ads1115.py +++ b/esphomeyaml/components/ads1115.py @@ -2,27 +2,19 @@ import voluptuous as vol import esphomeyaml.config_validation as cv from esphomeyaml.const import CONF_ADDRESS, CONF_ID, CONF_RATE -from esphomeyaml.helpers import App, Pvariable, RawExpression, add, HexIntLiteral +from esphomeyaml.helpers import App, Pvariable DEPENDENCIES = ['i2c'] ADS1115_COMPONENT_CLASS = 'sensor::ADS1115Component' -RATES = { - 8: 'ADS1115_RATE_8', - 16: 'ADS1115_RATE_16', - 32: 'ADS1115_RATE_32', - 64: 'ADS1115_RATE_64', - 128: 'ADS1115_RATE_128', - 250: 'ADS1115_RATE_250', - 475: 'ADS1115_RATE_475', - 860: 'ADS1115_RATE_860', -} +RATE_REMOVE_MESSAGE = """The rate option has been removed in 1.5.0 and is no longer required.""" ADS1115_SCHEMA = vol.Schema({ cv.GenerateID('ads1115'): cv.register_variable_id, vol.Required(CONF_ADDRESS): cv.i2c_address, - vol.Optional(CONF_RATE): vol.All(vol.Coerce(int), vol.Any(*list(RATES.keys()))), + + vol.Optional(CONF_RATE): cv.invalid(RATE_REMOVE_MESSAGE) }) CONFIG_SCHEMA = vol.All(cv.ensure_list, [ADS1115_SCHEMA]) @@ -30,11 +22,8 @@ CONFIG_SCHEMA = vol.All(cv.ensure_list, [ADS1115_SCHEMA]) def to_code(config): for conf in config: - address = HexIntLiteral(conf[CONF_ADDRESS]) - rhs = App.make_ads1115_component(address) - ads1115 = Pvariable(ADS1115_COMPONENT_CLASS, conf[CONF_ID], rhs) - if CONF_RATE in conf: - add(ads1115.set_rate(RawExpression(RATES[conf[CONF_RATE]]))) + rhs = App.make_ads1115_component(conf[CONF_ADDRESS]) + Pvariable(ADS1115_COMPONENT_CLASS, conf[CONF_ID], rhs) BUILD_FLAGS = '-DUSE_ADS1115_SENSOR' diff --git a/esphomeyaml/components/binary_sensor/__init__.py b/esphomeyaml/components/binary_sensor/__init__.py index b8449353c2..bcb396077d 100644 --- a/esphomeyaml/components/binary_sensor/__init__.py +++ b/esphomeyaml/components/binary_sensor/__init__.py @@ -1,8 +1,8 @@ import voluptuous as vol import esphomeyaml.config_validation as cv -from esphomeyaml.const import CONF_DEVICE_CLASS, CONF_INVERTED -from esphomeyaml.helpers import add, setup_mqtt_component +from esphomeyaml.const import CONF_DEVICE_CLASS, CONF_INVERTED, CONF_MQTT_ID +from esphomeyaml.helpers import add, setup_mqtt_component, App, Pvariable DEVICE_CLASSES = [ '', 'battery', 'cold', 'connectivity', 'door', 'garage_door', 'gas', @@ -23,6 +23,10 @@ MQTT_BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({ }) +MQTT_BINARY_SENSOR_ID_SCHEMA = MQTT_BINARY_SENSOR_SCHEMA.extend({ + cv.GenerateID('mqtt_binary_sensor', CONF_MQTT_ID): cv.register_variable_id, +}) + def setup_binary_sensor(obj, config): if CONF_DEVICE_CLASS in config: @@ -35,4 +39,11 @@ def setup_mqtt_binary_sensor(obj, config): setup_mqtt_component(obj, config) +def register_binary_sensor(var, config): + setup_binary_sensor(var, config) + rhs = App.register_binary_sensor(var) + mqtt_sensor = Pvariable('binary_sensor::MQTTBinarySensorComponent', config[CONF_MQTT_ID], rhs) + setup_mqtt_binary_sensor(mqtt_sensor, config) + + BUILD_FLAGS = '-DUSE_BINARY_SENSOR' diff --git a/esphomeyaml/components/binary_sensor/esp32_ble.py b/esphomeyaml/components/binary_sensor/esp32_ble.py new file mode 100644 index 0000000000..f0019d3354 --- /dev/null +++ b/esphomeyaml/components/binary_sensor/esp32_ble.py @@ -0,0 +1,44 @@ +import voluptuous as vol + +import esphomeyaml.config_validation as cv +from esphomeyaml.components import binary_sensor +from esphomeyaml.const import CONF_ID, CONF_MAC_ADDRESS, CONF_NAME, ESP_PLATFORM_ESP32 +from esphomeyaml.core import HexInt, MACAddress +from esphomeyaml.helpers import ArrayInitializer, Pvariable, get_variable + +ESP_PLATFORMS = [ESP_PLATFORM_ESP32] +DEPENDENCIES = ['esp32_ble'] + + +def validate_mac(value): + value = cv.string_strict(value) + parts = value.split(':') + if len(parts) != 6: + raise vol.Invalid("MAC Address must consist of 6 : (colon) separated parts") + parts_int = [] + if any(len(part) != 2 for part in parts): + raise vol.Invalid("MAC Address must be format XX:XX:XX:XX:XX:XX") + for part in parts: + try: + parts_int.append(int(part, 16)) + except ValueError: + raise vol.Invalid("MAC Address parts must be hexadecimal values from 00 to FF") + + return MACAddress(*parts_int) + + +PLATFORM_SCHEMA = binary_sensor.PLATFORM_SCHEMA.extend({ + cv.GenerateID('esp32_ble_device'): cv.register_variable_id, + vol.Required(CONF_MAC_ADDRESS): validate_mac, +}).extend(binary_sensor.MQTT_BINARY_SENSOR_ID_SCHEMA.schema) + + +def to_code(config): + hub = get_variable(None, type='ESP32BLETracker') + addr = [HexInt(i) for i in config[CONF_MAC_ADDRESS].parts] + rhs = hub.make_device(config[CONF_NAME], ArrayInitializer(*addr, multiline=False)) + device = Pvariable('ESP32BLEDevice', config[CONF_ID], rhs) + binary_sensor.register_binary_sensor(device, config) + + +BUILD_FLAGS = '-DUSE_ESP32_BLE_TRACKER' diff --git a/esphomeyaml/components/binary_sensor/esp32_touch.py b/esphomeyaml/components/binary_sensor/esp32_touch.py new file mode 100644 index 0000000000..a864940476 --- /dev/null +++ b/esphomeyaml/components/binary_sensor/esp32_touch.py @@ -0,0 +1,49 @@ +import voluptuous as vol + +import esphomeyaml.config_validation as cv +from esphomeyaml.components import binary_sensor +from esphomeyaml.const import CONF_ID, CONF_NAME, CONF_PIN, CONF_THRESHOLD, ESP_PLATFORM_ESP32 +from esphomeyaml.helpers import Pvariable, RawExpression, get_variable +from esphomeyaml.pins import validate_gpio_pin + +ESP_PLATFORMS = [ESP_PLATFORM_ESP32] + +DEPENDENCIES = ['esp32_touch'] + +TOUCH_PADS = { + 4: 'TOUCH_PAD_NUM0', + 0: 'TOUCH_PAD_NUM1', + 2: 'TOUCH_PAD_NUM2', + 15: 'TOUCH_PAD_NUM3', + 13: 'TOUCH_PAD_NUM4', + 12: 'TOUCH_PAD_NUM5', + 14: 'TOUCH_PAD_NUM6', + 27: 'TOUCH_PAD_NUM7', + 33: 'TOUCH_PAD_NUM8', + 32: 'TOUCH_PAD_NUM9', +} + + +def validate_touch_pad(value): + value = validate_gpio_pin(value) + if value not in TOUCH_PADS: + raise vol.Invalid("Pin {} does not support touch pads.".format(value)) + return value + + +PLATFORM_SCHEMA = binary_sensor.PLATFORM_SCHEMA.extend({ + cv.GenerateID('esp32_touch_pad'): cv.register_variable_id, + vol.Required(CONF_PIN): validate_touch_pad, + vol.Required(CONF_THRESHOLD): cv.uint16_t, +}).extend(binary_sensor.MQTT_BINARY_SENSOR_ID_SCHEMA.schema) + + +def to_code(config): + hub = get_variable(None, type='ESP32TouchComponent') + touch_pad = RawExpression(TOUCH_PADS[config[CONF_PIN]]) + rhs = hub.make_touch_pad(config[CONF_NAME], touch_pad, config[CONF_THRESHOLD]) + device = Pvariable('ESP32TouchBinarySensor', config[CONF_ID], rhs) + binary_sensor.register_binary_sensor(device, config) + + +BUILD_FLAGS = '-DUSE_ESP32_TOUCH_BINARY_SENSOR' diff --git a/esphomeyaml/components/dallas.py b/esphomeyaml/components/dallas.py index 850aaafd15..c4732aa253 100644 --- a/esphomeyaml/components/dallas.py +++ b/esphomeyaml/components/dallas.py @@ -10,7 +10,7 @@ DALLAS_COMPONENT_CLASS = 'sensor::DallasComponent' CONFIG_SCHEMA = vol.All(cv.ensure_list, [vol.Schema({ cv.GenerateID('dallas'): cv.register_variable_id, vol.Required(CONF_PIN): pins.input_output_pin, - vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_not_null_time_period, + vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds, })]) diff --git a/esphomeyaml/components/deep_sleep.py b/esphomeyaml/components/deep_sleep.py index c76205dd8d..2ddf2a8cd3 100644 --- a/esphomeyaml/components/deep_sleep.py +++ b/esphomeyaml/components/deep_sleep.py @@ -16,11 +16,11 @@ def validate_pin_number(value): CONFIG_SCHEMA = vol.Schema({ cv.GenerateID('deep_sleep'): cv.register_variable_id, - vol.Optional(CONF_SLEEP_DURATION): cv.positive_time_period, + vol.Optional(CONF_SLEEP_DURATION): cv.positive_time_period_milliseconds, vol.Optional(CONF_WAKEUP_PIN): vol.All(cv.only_on_esp32, pins.GPIO_INPUT_PIN_SCHEMA, pins.schema_validate_number(validate_pin_number)), vol.Optional(CONF_RUN_CYCLES): cv.positive_int, - vol.Optional(CONF_RUN_DURATION): cv.positive_time_period, + vol.Optional(CONF_RUN_DURATION): cv.positive_time_period_milliseconds, }) diff --git a/esphomeyaml/components/esp32_ble.py b/esphomeyaml/components/esp32_ble.py new file mode 100644 index 0000000000..29de179553 --- /dev/null +++ b/esphomeyaml/components/esp32_ble.py @@ -0,0 +1,22 @@ +import voluptuous as vol + +from esphomeyaml import config_validation as cv +from esphomeyaml.const import CONF_ID, CONF_SCAN_INTERVAL, ESP_PLATFORM_ESP32 +from esphomeyaml.helpers import App, Pvariable, add + +ESP_PLATFORMS = [ESP_PLATFORM_ESP32] + +CONFIG_SCHEMA = vol.Schema({ + cv.GenerateID('esp32_ble'): cv.register_variable_id, + vol.Optional(CONF_SCAN_INTERVAL): cv.positive_time_period_milliseconds, +}) + + +def to_code(config): + rhs = App.make_esp32_ble_tracker() + ble = Pvariable('ESP32BLETracker', config[CONF_ID], rhs) + if CONF_SCAN_INTERVAL in config: + add(ble.set_scan_interval(config[CONF_SCAN_INTERVAL])) + + +BUILD_FLAGS = '-DUSE_ESP32_BLE_TRACKER' diff --git a/esphomeyaml/components/esp32_touch.py b/esphomeyaml/components/esp32_touch.py new file mode 100644 index 0000000000..8aac85815b --- /dev/null +++ b/esphomeyaml/components/esp32_touch.py @@ -0,0 +1,83 @@ +import voluptuous as vol + +from esphomeyaml import config_validation as cv +from esphomeyaml.const import CONF_ID, CONF_SETUP_MODE, CONF_IIR_FILTER, \ + CONF_SLEEP_DURATION, CONF_MEASUREMENT_DURATION, CONF_LOW_VOLTAGE_REFERENCE, \ + CONF_HIGH_VOLTAGE_REFERENCE, CONF_VOLTAGE_ATTENUATION, ESP_PLATFORM_ESP32 +from esphomeyaml.core import TimePeriod +from esphomeyaml.helpers import App, Pvariable, add + +ESP_PLATFORMS = [ESP_PLATFORM_ESP32] + + +def validate_voltage(values): + def validator(value): + if isinstance(value, float) and value.is_integer(): + value = int(value) + value = cv.string(value) + if not value.endswith('V'): + value += 'V' + if value not in values: + raise vol.Invalid('Must be one of {}'.format(values)) + return value + return validator + + +LOW_VOLTAGE_REFERENCE = { + '0.5V': 'TOUCH_LVOLT_0V5', + '0.6V': 'TOUCH_LVOLT_0V6', + '0.7V': 'TOUCH_LVOLT_0V7', + '0.8V': 'TOUCH_LVOLT_0V8', +} +HIGH_VOLTAGE_REFERENCE = { + '2.4V': 'TOUCH_HVOLT_2V4', + '2.5V': 'TOUCH_HVOLT_2V5', + '2.6V': 'TOUCH_HVOLT_2V6', + '2.7V': 'TOUCH_HVOLT_2V7', +} +VOLTAGE_ATTENUATION = { + '1.5V': 'TOUCH_HVOLT_ATTEN_1V5', + '1V': 'TOUCH_HVOLT_ATTEN_1V', + '0.5V': 'TOUCH_HVOLT_ATTEN_0V5', + '0V': 'TOUCH_HVOLT_ATTEN_0V', +} + +CONFIG_SCHEMA = vol.Schema({ + cv.GenerateID('esp32_ble'): cv.register_variable_id, + vol.Optional(CONF_SETUP_MODE): cv.boolean, + vol.Optional(CONF_IIR_FILTER): cv.positive_time_period_milliseconds, + vol.Optional(CONF_SLEEP_DURATION): + vol.All(cv.positive_time_period, vol.Range(max=TimePeriod(microseconds=436900))), + vol.Optional(CONF_MEASUREMENT_DURATION): + vol.All(cv.positive_time_period, vol.Range(max=TimePeriod(microseconds=8192))), + vol.Optional(CONF_LOW_VOLTAGE_REFERENCE): validate_voltage(LOW_VOLTAGE_REFERENCE), + vol.Optional(CONF_HIGH_VOLTAGE_REFERENCE): validate_voltage(HIGH_VOLTAGE_REFERENCE), + vol.Optional(CONF_VOLTAGE_ATTENUATION): validate_voltage(VOLTAGE_ATTENUATION), +}) + + +def to_code(config): + rhs = App.make_esp32_touch_component() + touch = Pvariable('binary_sensor::ESP32TouchComponent', config[CONF_ID], rhs) + if CONF_SETUP_MODE in config: + add(touch.set_setup_mode(config[CONF_SETUP_MODE])) + if CONF_IIR_FILTER in config: + add(touch.set_iir_filter(config[CONF_IIR_FILTER])) + if CONF_SLEEP_DURATION in config: + sleep_duration = int(config[CONF_SLEEP_DURATION].total_microseconds * 6.6667) + add(touch.set_sleep_duration(sleep_duration)) + if CONF_MEASUREMENT_DURATION in config: + measurement_duration = int(config[CONF_MEASUREMENT_DURATION].total_microseconds * 0.125) + add(touch.set_measurement_duration(measurement_duration)) + if CONF_LOW_VOLTAGE_REFERENCE in config: + value = LOW_VOLTAGE_REFERENCE[config[CONF_LOW_VOLTAGE_REFERENCE]] + add(touch.set_low_voltage_reference(value)) + if CONF_HIGH_VOLTAGE_REFERENCE in config: + value = HIGH_VOLTAGE_REFERENCE[config[CONF_HIGH_VOLTAGE_REFERENCE]] + add(touch.set_high_voltage_reference(value)) + if CONF_VOLTAGE_ATTENUATION in config: + value = VOLTAGE_ATTENUATION[config[CONF_VOLTAGE_ATTENUATION]] + add(touch.set_voltage_attenuation(value)) + + +BUILD_FLAGS = '-DUSE_ESP32_TOUCH_BINARY_SENSOR' diff --git a/esphomeyaml/components/i2c.py b/esphomeyaml/components/i2c.py index 870ff6efca..b9dfc5a31c 100644 --- a/esphomeyaml/components/i2c.py +++ b/esphomeyaml/components/i2c.py @@ -2,14 +2,16 @@ import voluptuous as vol import esphomeyaml.config_validation as cv from esphomeyaml import pins -from esphomeyaml.const import CONF_FREQUENCY, CONF_SCL, CONF_SDA, CONF_SCAN, CONF_ID +from esphomeyaml.const import CONF_FREQUENCY, CONF_SCL, CONF_SDA, CONF_SCAN, CONF_ID, \ + CONF_RECEIVE_TIMEOUT from esphomeyaml.helpers import App, add, Pvariable CONFIG_SCHEMA = vol.Schema({ cv.GenerateID('i2c'): cv.register_variable_id, vol.Required(CONF_SDA, default='SDA'): pins.input_output_pin, vol.Required(CONF_SCL, default='SCL'): pins.input_output_pin, - vol.Optional(CONF_FREQUENCY): vol.All(cv.only_on_esp32, cv.positive_int), + vol.Optional(CONF_FREQUENCY): cv.positive_int, + vol.Optional(CONF_RECEIVE_TIMEOUT): cv.positive_time_period_milliseconds, vol.Optional(CONF_SCAN): cv.boolean, }) @@ -19,6 +21,10 @@ def to_code(config): i2c = Pvariable('I2CComponent', config[CONF_ID], rhs) if CONF_FREQUENCY in config: add(i2c.set_frequency(config[CONF_FREQUENCY])) + if CONF_RECEIVE_TIMEOUT in config: + add(i2c.set_receive_timeout(config[CONF_RECEIVE_TIMEOUT])) BUILD_FLAGS = '-DUSE_I2C' + +LIB_DEPS = 'Wire' diff --git a/esphomeyaml/components/ir_transmitter.py b/esphomeyaml/components/ir_transmitter.py index 4d9f972380..9768795807 100644 --- a/esphomeyaml/components/ir_transmitter.py +++ b/esphomeyaml/components/ir_transmitter.py @@ -2,17 +2,16 @@ import voluptuous as vol import esphomeyaml.config_validation as cv from esphomeyaml import pins -from esphomeyaml.const import CONF_CARRIER_DUTY_PERCENT, CONF_ID, CONF_PIN, ESP_PLATFORM_ESP32 +from esphomeyaml.const import CONF_CARRIER_DUTY_PERCENT, CONF_ID, CONF_PIN from esphomeyaml.helpers import App, Pvariable, exp_gpio_output_pin -ESP_PLATFORMS = [ESP_PLATFORM_ESP32] - IR_TRANSMITTER_COMPONENT_CLASS = 'switch_::IRTransmitterComponent' CONFIG_SCHEMA = vol.All(cv.ensure_list, [vol.Schema({ cv.GenerateID('ir_transmitter'): cv.register_variable_id, vol.Required(CONF_PIN): pins.GPIO_OUTPUT_PIN_SCHEMA, - vol.Optional(CONF_CARRIER_DUTY_PERCENT): vol.All(vol.Coerce(int), vol.Range(min=0, max=100)), + vol.Optional(CONF_CARRIER_DUTY_PERCENT): vol.All(vol.Coerce(int), + vol.Range(min=0, max=100, min_included=False)), })]) diff --git a/esphomeyaml/components/light/__init__.py b/esphomeyaml/components/light/__init__.py index 1675a56292..63ef94b229 100644 --- a/esphomeyaml/components/light/__init__.py +++ b/esphomeyaml/components/light/__init__.py @@ -1,5 +1,5 @@ import esphomeyaml.config_validation as cv -from esphomeyaml.const import CONF_DEFAULT_TRANSITION_LENGTH +from esphomeyaml.const import CONF_DEFAULT_TRANSITION_LENGTH, CONF_GAMMA_CORRECT from esphomeyaml.helpers import add PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ @@ -10,6 +10,8 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ def setup_light_component(obj, config): if CONF_DEFAULT_TRANSITION_LENGTH in config: add(obj.set_default_transition_length(config[CONF_DEFAULT_TRANSITION_LENGTH])) + if CONF_GAMMA_CORRECT in config: + add(obj.set_gamma_correct(config[CONF_GAMMA_CORRECT])) BUILD_FLAGS = '-DUSE_LIGHT' diff --git a/esphomeyaml/components/light/fastled_clockless.py b/esphomeyaml/components/light/fastled_clockless.py new file mode 100644 index 0000000000..c6a5d3ad1f --- /dev/null +++ b/esphomeyaml/components/light/fastled_clockless.py @@ -0,0 +1,98 @@ +import voluptuous as vol + +import esphomeyaml.config_validation as cv +from esphomeyaml import pins, core +from esphomeyaml.components import light +from esphomeyaml.const import CONF_CHIPSET, CONF_DEFAULT_TRANSITION_LENGTH, CONF_GAMMA_CORRECT, \ + CONF_ID, CONF_MAX_REFRESH_RATE, CONF_NAME, CONF_NUM_LEDS, CONF_PIN, CONF_RGB_ORDER, \ + ESP_PLATFORM_ESP32 +from esphomeyaml.helpers import App, TemplateArguments, add, setup_mqtt_component, variable, \ + RawExpression + +TYPES = [ + 'NEOPIXEL', + 'TM1829', + 'TM1809', + 'TM1804', + 'TM1803', + 'UCS1903', + 'UCS1903B', + 'UCS1904', + 'UCS2903', + 'WS2812', + 'WS2852', + 'WS2812B', + 'SK6812', + 'SK6822', + 'APA106', + 'PL9823', + 'WS2811', + 'WS2813', + 'APA104', + 'WS2811_400', + 'GW6205', + 'GW6205_400', + 'LPD1886', + 'LPD1886_8BIT', + 'PIXIE', +] + +RGB_ORDERS = [ + 'RGB', + 'RBG', + 'GRB', + 'GBR', + 'BRG', + 'BGR', +] + + +def validate(value): + if value[CONF_CHIPSET] == 'NEOPIXEL' and CONF_RGB_ORDER in value: + raise vol.Invalid("NEOPIXEL doesn't support RGB order") + if value[CONF_CHIPSET] == 'PIXIE' and core.ESP_PLATFORM == ESP_PLATFORM_ESP32: + raise vol.Invalid("PIXIE lights are not supported with the ESP32") + return value + + +PLATFORM_SCHEMA = vol.All(light.PLATFORM_SCHEMA.extend({ + cv.GenerateID('fast_led_clockless_light'): cv.register_variable_id, + + vol.Required(CONF_CHIPSET): vol.All(vol.Upper, vol.Any(*TYPES)), + vol.Required(CONF_PIN): pins.output_pin, + + vol.Required(CONF_NUM_LEDS): cv.positive_not_null_int, + vol.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds, + vol.Optional(CONF_RGB_ORDER): vol.All(vol.Upper, vol.Any(*RGB_ORDERS)), + + vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float, + vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds, +}), validate) + + +def to_code(config): + rhs = App.make_fast_led_light(config[CONF_NAME]) + make = variable('Application::MakeFastLEDLight', config[CONF_ID], rhs) + fast_led = make.Pfast_led + + rgb_order = None + if CONF_RGB_ORDER in config: + rgb_order = RawExpression(config[CONF_RGB_ORDER]) + template_args = TemplateArguments(RawExpression(config[CONF_CHIPSET]), + config[CONF_PIN], rgb_order) + add(fast_led.add_leds(template_args, config[CONF_NUM_LEDS])) + + if CONF_MAX_REFRESH_RATE in config: + add(fast_led.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE])) + + setup_mqtt_component(make.Pmqtt, config) + light.setup_light_component(make.Pstate, config) + + +BUILD_FLAGS = '-DUSE_FAST_LED_LIGHT' + + +def required_build_flags(config): + if config[CONF_CHIPSET] == 'PIXIE': + return '-DUSE_FAST_LED_LIGHT_PIXIE' + return None diff --git a/esphomeyaml/components/light/fastled_spi.py b/esphomeyaml/components/light/fastled_spi.py new file mode 100644 index 0000000000..ea890192c6 --- /dev/null +++ b/esphomeyaml/components/light/fastled_spi.py @@ -0,0 +1,69 @@ +import voluptuous as vol + +import esphomeyaml.config_validation as cv +from esphomeyaml import pins +from esphomeyaml.components import light +from esphomeyaml.const import CONF_CHIPSET, CONF_CLOCK_PIN, CONF_DATA_PIN, \ + CONF_DEFAULT_TRANSITION_LENGTH, CONF_GAMMA_CORRECT, CONF_ID, CONF_MAX_REFRESH_RATE, CONF_NAME, \ + CONF_NUM_LEDS, CONF_RGB_ORDER +from esphomeyaml.helpers import App, TemplateArguments, add, setup_mqtt_component, variable, \ + RawExpression + +CHIPSETS = [ + 'LPD8806', + 'WS2801', + 'WS2803', + 'SM16716', + 'P9813', + 'APA102', + 'SK9822', + 'DOTSTAR', +] + +RGB_ORDERS = [ + 'RGB', + 'RBG', + 'GRB', + 'GBR', + 'BRG', + 'BGR', +] + +PLATFORM_SCHEMA = light.PLATFORM_SCHEMA.extend({ + cv.GenerateID('fast_led_spi_light'): cv.register_variable_id, + + vol.Required(CONF_CHIPSET): vol.All(vol.Upper, vol.Any(*CHIPSETS)), + vol.Required(CONF_DATA_PIN): pins.output_pin, + vol.Required(CONF_CLOCK_PIN): pins.output_pin, + + vol.Required(CONF_NUM_LEDS): cv.positive_not_null_int, + vol.Optional(CONF_RGB_ORDER): vol.All(vol.Upper, vol.Any(*RGB_ORDERS)), + vol.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds, + + vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float, + vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds, +}) + + +def to_code(config): + rhs = App.make_fast_led_light(config[CONF_NAME]) + make = variable('Application::MakeFastLEDLight', config[CONF_ID], rhs) + fast_led = make.Pfast_led + + rgb_order = None + if CONF_RGB_ORDER in config: + rgb_order = RawExpression(config[CONF_RGB_ORDER]) + template_args = TemplateArguments(RawExpression(config[CONF_CHIPSET]), + config[CONF_DATA_PIN], + config[CONF_CLOCK_PIN], + rgb_order) + add(fast_led.add_leds(template_args, config[CONF_NUM_LEDS])) + + if CONF_MAX_REFRESH_RATE in config: + add(fast_led.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE])) + + setup_mqtt_component(make.Pmqtt, config) + light.setup_light_component(make.Pstate, config) + + +BUILD_FLAGS = 'DUSE_FAST_LED_LIGHT' diff --git a/esphomeyaml/components/light/monochromatic.py b/esphomeyaml/components/light/monochromatic.py index d7a0878bf8..901dd6ce74 100644 --- a/esphomeyaml/components/light/monochromatic.py +++ b/esphomeyaml/components/light/monochromatic.py @@ -4,13 +4,13 @@ import esphomeyaml.config_validation as cv from esphomeyaml.components import light from esphomeyaml.const import CONF_DEFAULT_TRANSITION_LENGTH, CONF_GAMMA_CORRECT, CONF_ID, \ CONF_NAME, CONF_OUTPUT -from esphomeyaml.helpers import App, add, get_variable, variable, setup_mqtt_component +from esphomeyaml.helpers import App, get_variable, setup_mqtt_component, variable PLATFORM_SCHEMA = light.PLATFORM_SCHEMA.extend({ cv.GenerateID('monochromatic_light'): cv.register_variable_id, vol.Required(CONF_OUTPUT): cv.variable_id, vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float, - vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period, + vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds, }) @@ -18,7 +18,5 @@ def to_code(config): output = get_variable(config[CONF_OUTPUT]) rhs = App.make_monochromatic_light(config[CONF_NAME], output) light_struct = variable('Application::MakeLight', config[CONF_ID], rhs) - if CONF_GAMMA_CORRECT in config: - add(light_struct.Poutput.set_gamma_correct(config[CONF_GAMMA_CORRECT])) setup_mqtt_component(light_struct.Pmqtt, config) light.setup_light_component(light_struct.Pstate, config) diff --git a/esphomeyaml/components/light/rgb.py b/esphomeyaml/components/light/rgb.py index d94ea09ddf..ada590ed1c 100644 --- a/esphomeyaml/components/light/rgb.py +++ b/esphomeyaml/components/light/rgb.py @@ -12,7 +12,7 @@ PLATFORM_SCHEMA = light.PLATFORM_SCHEMA.extend({ vol.Required(CONF_GREEN): cv.variable_id, vol.Required(CONF_BLUE): cv.variable_id, vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float, - vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period, + vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds, }) @@ -22,7 +22,5 @@ def to_code(config): blue = get_variable(config[CONF_BLUE]) rhs = App.make_rgb_light(config[CONF_NAME], red, green, blue) light_struct = variable('Application::MakeLight', config[CONF_ID], rhs) - if CONF_GAMMA_CORRECT in config: - add(light_struct.Poutput.set_gamma_correct(config[CONF_GAMMA_CORRECT])) setup_mqtt_component(light_struct.Pmqtt, config) light.setup_light_component(light_struct.Pstate, config) diff --git a/esphomeyaml/components/light/rgbw.py b/esphomeyaml/components/light/rgbw.py index 75190141e1..05008cf053 100644 --- a/esphomeyaml/components/light/rgbw.py +++ b/esphomeyaml/components/light/rgbw.py @@ -4,7 +4,7 @@ import esphomeyaml.config_validation as cv from esphomeyaml.components import light from esphomeyaml.const import CONF_BLUE, CONF_DEFAULT_TRANSITION_LENGTH, CONF_GAMMA_CORRECT, \ CONF_GREEN, CONF_ID, CONF_NAME, CONF_RED, CONF_WHITE -from esphomeyaml.helpers import App, add, get_variable, setup_mqtt_component, variable +from esphomeyaml.helpers import App, get_variable, setup_mqtt_component, variable PLATFORM_SCHEMA = light.PLATFORM_SCHEMA.extend({ cv.GenerateID('rgbw_light'): cv.register_variable_id, @@ -13,7 +13,7 @@ PLATFORM_SCHEMA = light.PLATFORM_SCHEMA.extend({ vol.Required(CONF_BLUE): cv.variable_id, vol.Required(CONF_WHITE): cv.variable_id, vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float, - vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period, + vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds, }) @@ -24,7 +24,5 @@ def to_code(config): white = get_variable(config[CONF_WHITE]) rhs = App.make_rgbw_light(config[CONF_NAME], red, green, blue, white) light_struct = variable('Application::MakeLight', config[CONF_ID], rhs) - if CONF_GAMMA_CORRECT in config: - add(light_struct.Poutput.set_gamma_correct(config[CONF_GAMMA_CORRECT])) setup_mqtt_component(light_struct.Pmqtt, config) light.setup_light_component(light_struct.Pstate, config) diff --git a/esphomeyaml/components/logger.py b/esphomeyaml/components/logger.py index 3adb640b37..6c7be146c4 100644 --- a/esphomeyaml/components/logger.py +++ b/esphomeyaml/components/logger.py @@ -6,7 +6,7 @@ from esphomeyaml.const import CONF_BAUD_RATE, CONF_ID, CONF_LEVEL, CONF_LOGGER, from esphomeyaml.core import ESPHomeYAMLError from esphomeyaml.helpers import App, Pvariable, RawExpression, add -LOG_LEVELS = ['NONE', 'ERROR', 'WARN', 'INFO', 'DEBUG', 'VERBOSE'] +LOG_LEVELS = ['NONE', 'ERROR', 'WARN', 'INFO', 'DEBUG', 'VERBOSE', 'VERY_VERBOSE'] # pylint: disable=invalid-name is_log_level = vol.All(vol.Upper, vol.Any(*LOG_LEVELS)) diff --git a/esphomeyaml/components/mqtt.py b/esphomeyaml/components/mqtt.py index 2ba4d42b1e..64b1b0b574 100644 --- a/esphomeyaml/components/mqtt.py +++ b/esphomeyaml/components/mqtt.py @@ -64,7 +64,7 @@ CONFIG_SCHEMA = vol.Schema({ vol.Optional(CONF_LOG_TOPIC): MQTT_MESSAGE_TEMPLATE_SCHEMA, vol.Optional(CONF_SSL_FINGERPRINTS): vol.All(cv.only_on_esp8266, cv.ensure_list, [validate_fingerprint]), - vol.Optional(CONF_KEEPALIVE): vol.All(cv.positive_time_period, cv.time_period_to_seconds) + vol.Optional(CONF_KEEPALIVE): cv.positive_time_period_seconds, }) diff --git a/esphomeyaml/components/ota.py b/esphomeyaml/components/ota.py index 59f14b1970..33deff16af 100644 --- a/esphomeyaml/components/ota.py +++ b/esphomeyaml/components/ota.py @@ -46,3 +46,9 @@ def get_auth(config): BUILD_FLAGS = '-DUSE_OTA' + + +def lib_deps(config): + if core.ESP_PLATFORM == ESP_PLATFORM_ESP32: + return ['ArduinoOTA', 'Update', 'ESPmDNS'] + return ['Hash', 'ESP8266mDNS', 'ArduinoOTA'] diff --git a/esphomeyaml/components/pca9685.py b/esphomeyaml/components/pca9685.py index 5db5c065a1..3d1031e719 100644 --- a/esphomeyaml/components/pca9685.py +++ b/esphomeyaml/components/pca9685.py @@ -10,12 +10,16 @@ PHASE_BALANCERS = ['None', 'Linear', 'Weaved'] PCA9685_COMPONENT_TYPE = 'output::PCA9685OutputComponent' +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({ cv.GenerateID('pca9685'): cv.register_variable_id, vol.Required(CONF_FREQUENCY): vol.All(cv.frequency, - vol.Range(min=24, max=1526)), - vol.Optional(CONF_PHASE_BALANCER): vol.All(vol.Title, vol.Any(*PHASE_BALANCERS)), + 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), }) CONFIG_SCHEMA = vol.All(cv.ensure_list, [PCA9685_SCHEMA]) diff --git a/esphomeyaml/components/power_supply.py b/esphomeyaml/components/power_supply.py index 8e06a16758..c9b240a863 100644 --- a/esphomeyaml/components/power_supply.py +++ b/esphomeyaml/components/power_supply.py @@ -7,8 +7,8 @@ from esphomeyaml.helpers import App, Pvariable, add, exp_gpio_output_pin POWER_SUPPLY_SCHEMA = cv.REQUIRED_ID_SCHEMA.extend({ vol.Required(CONF_PIN): pins.GPIO_OUTPUT_PIN_SCHEMA, - vol.Optional(CONF_ENABLE_TIME): cv.positive_time_period, - vol.Optional(CONF_KEEP_ON_TIME): cv.positive_time_period, + vol.Optional(CONF_ENABLE_TIME): cv.positive_time_period_milliseconds, + vol.Optional(CONF_KEEP_ON_TIME): cv.positive_time_period_milliseconds, }) CONFIG_SCHEMA = vol.All(cv.ensure_list, [POWER_SUPPLY_SCHEMA]) diff --git a/esphomeyaml/components/sensor/__init__.py b/esphomeyaml/components/sensor/__init__.py index c72add513e..bd14c3c744 100644 --- a/esphomeyaml/components/sensor/__init__.py +++ b/esphomeyaml/components/sensor/__init__.py @@ -37,7 +37,7 @@ MQTT_SENSOR_SCHEMA = vol.Schema({ vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string_strict, vol.Optional(CONF_ICON): cv.icon, vol.Optional(CONF_ACCURACY_DECIMALS): vol.Coerce(int), - vol.Optional(CONF_EXPIRE_AFTER): vol.Any(None, cv.positive_time_period), + vol.Optional(CONF_EXPIRE_AFTER): vol.Any(None, cv.positive_time_period_milliseconds), vol.Optional(CONF_FILTERS): FILTERS_SCHEMA }) diff --git a/esphomeyaml/components/sensor/adc.py b/esphomeyaml/components/sensor/adc.py index 14d309eb2e..7aa02426e3 100644 --- a/esphomeyaml/components/sensor/adc.py +++ b/esphomeyaml/components/sensor/adc.py @@ -20,7 +20,7 @@ PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({ cv.GenerateID('adc'): cv.register_variable_id, vol.Required(CONF_PIN): pins.analog_pin, vol.Optional(CONF_ATTENUATION): vol.All(cv.only_on_esp32, ATTENUATION_MODE_SCHEMA), - vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_not_null_time_period, + vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds, }).extend(sensor.MQTT_SENSOR_SCHEMA.schema) diff --git a/esphomeyaml/components/sensor/ads1115.py b/esphomeyaml/components/sensor/ads1115.py index 3b663232d7..ffcdca1687 100644 --- a/esphomeyaml/components/sensor/ads1115.py +++ b/esphomeyaml/components/sensor/ads1115.py @@ -9,23 +9,23 @@ from esphomeyaml.helpers import RawExpression, get_variable, Pvariable DEPENDENCIES = ['ads1115'] MUX = { - 'A0_A1': 'ADS1115_MUX_P0_N1', - 'A0_A3': 'ADS1115_MUX_P0_N3', - 'A1_A3': 'ADS1115_MUX_P1_N3', - 'A2_A3': 'ADS1115_MUX_P2_N3', - 'A0_GND': 'ADS1115_MUX_P0_NG', - 'A1_GND': 'ADS1115_MUX_P1_NG', - 'A2_GND': 'ADS1115_MUX_P2_NG', - 'A3_GND': 'ADS1115_MUX_P3_NG', + 'A0_A1': 'sensor::ADS1115_MULTIPLEXER_P0_N1', + 'A0_A3': 'sensor::ADS1115_MULTIPLEXER_P0_N3', + 'A1_A3': 'sensor::ADS1115_MULTIPLEXER_P1_N3', + 'A2_A3': 'sensor::ADS1115_MULTIPLEXER_P2_N3', + 'A0_GND': 'sensor::ADS1115_MULTIPLEXER_P0_NG', + 'A1_GND': 'sensor::ADS1115_MULTIPLEXER_P1_NG', + 'A2_GND': 'sensor::ADS1115_MULTIPLEXER_P2_NG', + 'A3_GND': 'sensor::ADS1115_MULTIPLEXER_P3_NG', } GAIN = { - '6.144': 'ADS1115_PGA_6P144', - '4.096': 'ADS1115_PGA_6P096', - '2.048': 'ADS1115_PGA_2P048', - '1.024': 'ADS1115_PGA_1P024', - '0.512': 'ADS1115_PGA_0P512', - '0.256': 'ADS1115_PGA_0P256', + '6.144': 'sensor::ADS1115_GAIN_6P144', + '4.096': 'sensor::ADS1115_GAIN_6P096', + '2.048': 'sensor::ADS1115_GAIN_2P048', + '1.024': 'sensor::ADS1115_GAIN_1P024', + '0.512': 'sensor::ADS1115_GAIN_0P512', + '0.256': 'sensor::ADS1115_GAIN_0P256', } @@ -45,7 +45,7 @@ PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({ vol.Required(CONF_MULTIPLEXER): vol.All(vol.Upper, vol.Any(*list(MUX.keys()))), vol.Required(CONF_GAIN): validate_gain, vol.Optional(CONF_ADS1115_ID): cv.variable_id, - vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_not_null_time_period, + vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds, }).extend(sensor.MQTT_SENSOR_ID_SCHEMA.schema) diff --git a/esphomeyaml/components/sensor/bh1750.py b/esphomeyaml/components/sensor/bh1750.py new file mode 100644 index 0000000000..bed6a7803e --- /dev/null +++ b/esphomeyaml/components/sensor/bh1750.py @@ -0,0 +1,37 @@ +import voluptuous as vol + +import esphomeyaml.config_validation as cv +from esphomeyaml.components import sensor +from esphomeyaml.const import CONF_ADDRESS, CONF_ID, CONF_NAME, CONF_RESOLUTION, \ + CONF_UPDATE_INTERVAL +from esphomeyaml.helpers import App, RawExpression, add, variable + +DEPENDENCIES = ['i2c'] + +BH1750_RESOLUTIONS = { + 4.0: 'sensor::BH1750_RESOLUTION_4P0_LX', + 1.0: 'sensor::BH1750_RESOLUTION_1P0_LX', + 0.5: 'sensor::BH1750_RESOLUTION_0P5_LX', +} + +PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({ + cv.GenerateID('bh1750_sensor'): cv.register_variable_id, + vol.Optional(CONF_ADDRESS, default=0x23): cv.i2c_address, + vol.Optional(CONF_RESOLUTION): vol.All(cv.positive_float, vol.Any(*BH1750_RESOLUTIONS)), + vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds, +}).extend(sensor.MQTT_SENSOR_SCHEMA.schema) + + +def to_code(config): + rhs = App.make_bh1750_sensor(config[CONF_NAME], config[CONF_ADDRESS], + config.get(CONF_UPDATE_INTERVAL)) + make_bh1750 = variable('Application::MakeBH1750Sensor', config[CONF_ID], rhs) + bh1750 = make_bh1750.Pbh1750 + if CONF_RESOLUTION in config: + constant = BH1750_RESOLUTIONS[config[CONF_RESOLUTION]] + add(bh1750.set_resolution(RawExpression(constant))) + sensor.setup_sensor(bh1750, config) + sensor.setup_mqtt_sensor_component(make_bh1750.Pmqtt, config) + + +BUILD_FLAGS = '-DUSE_BH1750' diff --git a/esphomeyaml/components/sensor/bme280.py b/esphomeyaml/components/sensor/bme280.py new file mode 100644 index 0000000000..153ad1d3a7 --- /dev/null +++ b/esphomeyaml/components/sensor/bme280.py @@ -0,0 +1,75 @@ +import voluptuous as vol + +import esphomeyaml.config_validation as cv +from esphomeyaml.components import sensor +from esphomeyaml.components.sensor import MQTT_SENSOR_SCHEMA +from esphomeyaml.const import CONF_ADDRESS, CONF_HUMIDITY, CONF_ID, CONF_IIR_FILTER, CONF_NAME, \ + CONF_OVERSAMPLING, CONF_PRESSURE, CONF_TEMPERATURE, CONF_UPDATE_INTERVAL +from esphomeyaml.helpers import App, RawExpression, add, variable + +DEPENDENCIES = ['i2c'] + +OVERSAMPLING_OPTIONS = { + 'NONE': 'sensor::BME280_OVERSAMPLING_NONE', + '1X': 'sensor::BME280_OVERSAMPLING_1X', + '2X': 'sensor::BME280_OVERSAMPLING_2X', + '4X': 'sensor::BME280_OVERSAMPLING_4X', + '8X': 'sensor::BME280_OVERSAMPLING_8X', + '16X': 'sensor::BME280_OVERSAMPLING_16X', +} + +IIR_FILTER_OPTIONS = { + 'OFF': 'sensor::BME280_IIR_FILTER_OFF', + '2X': 'sensor::BME280_IIR_FILTER_2X', + '4X': 'sensor::BME280_IIR_FILTER_4X', + '8X': 'sensor::BME280_IIR_FILTER_8X', + '16X': 'sensor::BME280_IIR_FILTER_16X', +} + +BME280_OVERSAMPLING_SENSOR_SCHEMA = MQTT_SENSOR_SCHEMA.extend({ + vol.Optional(CONF_OVERSAMPLING): vol.All(vol.Upper, vol.Any(*OVERSAMPLING_OPTIONS)), +}) + +PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({ + cv.GenerateID('bme280'): cv.register_variable_id, + vol.Optional(CONF_ADDRESS, default=0x77): cv.i2c_address, + vol.Required(CONF_TEMPERATURE): BME280_OVERSAMPLING_SENSOR_SCHEMA, + vol.Required(CONF_PRESSURE): BME280_OVERSAMPLING_SENSOR_SCHEMA, + vol.Required(CONF_HUMIDITY): BME280_OVERSAMPLING_SENSOR_SCHEMA, + vol.Optional(CONF_IIR_FILTER): vol.All(vol.Upper, vol.Any(*IIR_FILTER_OPTIONS)), + vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds, +}).extend(sensor.MQTT_SENSOR_SCHEMA.schema) + + +def to_code(config): + rhs = App.make_bme280_sensor(config[CONF_TEMPERATURE][CONF_NAME], + config[CONF_PRESSURE][CONF_NAME], + config[CONF_HUMIDITY][CONF_NAME], + config[CONF_ADDRESS], + config.get(CONF_UPDATE_INTERVAL)) + make = variable('Application::MakeBME280Sensor', config[CONF_ID], rhs) + bme280 = make.Pbme280 + if CONF_OVERSAMPLING in config[CONF_TEMPERATURE]: + constant = OVERSAMPLING_OPTIONS[config[CONF_TEMPERATURE][CONF_OVERSAMPLING]] + add(bme280.set_temperature_oversampling(RawExpression(constant))) + if CONF_OVERSAMPLING in config[CONF_PRESSURE]: + constant = OVERSAMPLING_OPTIONS[config[CONF_PRESSURE][CONF_OVERSAMPLING]] + add(bme280.set_pressure_oversampling(RawExpression(constant))) + if CONF_OVERSAMPLING in config[CONF_HUMIDITY]: + constant = OVERSAMPLING_OPTIONS[config[CONF_HUMIDITY][CONF_OVERSAMPLING]] + add(bme280.set_humidity_oversampling(RawExpression(constant))) + if CONF_IIR_FILTER in config: + constant = IIR_FILTER_OPTIONS[config[CONF_IIR_FILTER]] + add(bme280.set_iir_filter(RawExpression(constant))) + + sensor.setup_sensor(bme280.Pget_temperature_sensor(), config[CONF_TEMPERATURE]) + sensor.setup_mqtt_sensor_component(make.Pmqtt_temperature, config[CONF_TEMPERATURE]) + + sensor.setup_sensor(bme280.Pget_pressure_sensor(), config[CONF_PRESSURE]) + sensor.setup_mqtt_sensor_component(make.Pmqtt_pressure, config[CONF_PRESSURE]) + + sensor.setup_sensor(bme280.Pget_humidity_sensor(), config[CONF_HUMIDITY]) + sensor.setup_mqtt_sensor_component(make.Pmqtt_humidity, config[CONF_HUMIDITY]) + + +BUILD_FLAGS = '-DUSE_BME280' diff --git a/esphomeyaml/components/sensor/bme680.py b/esphomeyaml/components/sensor/bme680.py new file mode 100644 index 0000000000..eedf3ae74e --- /dev/null +++ b/esphomeyaml/components/sensor/bme680.py @@ -0,0 +1,84 @@ +import voluptuous as vol + +import esphomeyaml.config_validation as cv +from esphomeyaml.components import sensor +from esphomeyaml.components.sensor import MQTT_SENSOR_SCHEMA +from esphomeyaml.const import CONF_ADDRESS, CONF_HUMIDITY, CONF_ID, CONF_IIR_FILTER, CONF_NAME, \ + CONF_OVERSAMPLING, CONF_PRESSURE, CONF_TEMPERATURE, CONF_UPDATE_INTERVAL, CONF_GAS_RESISTANCE +from esphomeyaml.helpers import App, RawExpression, add, variable + +DEPENDENCIES = ['i2c'] + +OVERSAMPLING_OPTIONS = { + 'NONE': 'sensor::BME680_OVERSAMPLING_NONE', + '1X': 'sensor::BME680_OVERSAMPLING_1X', + '2X': 'sensor::BME680_OVERSAMPLING_2X', + '4X': 'sensor::BME680_OVERSAMPLING_4X', + '8X': 'sensor::BME680_OVERSAMPLING_8X', + '16X': 'sensor::BME680_OVERSAMPLING_16X', +} + +IIR_FILTER_OPTIONS = { + 'OFF': 'sensor::BME680_IIR_FILTER_OFF', + '1X': 'sensor::BME680_IIR_FILTER_1X', + '3X': 'sensor::BME680_IIR_FILTER_3X', + '7X': 'sensor::BME680_IIR_FILTER_7X', + '15X': 'sensor::BME680_IIR_FILTER_15X', + '31X': 'sensor::BME680_IIR_FILTER_31X', + '63X': 'sensor::BME680_IIR_FILTER_63X', + '127X': 'sensor::BME680_IIR_FILTER_127X', +} + +BME680_OVERSAMPLING_SENSOR_SCHEMA = MQTT_SENSOR_SCHEMA.extend({ + vol.Optional(CONF_OVERSAMPLING): vol.All(vol.Upper, vol.Any(*OVERSAMPLING_OPTIONS)), +}) + +PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({ + cv.GenerateID('bme680'): cv.register_variable_id, + vol.Optional(CONF_ADDRESS, default=0x76): cv.i2c_address, + vol.Required(CONF_TEMPERATURE): BME680_OVERSAMPLING_SENSOR_SCHEMA, + vol.Required(CONF_PRESSURE): BME680_OVERSAMPLING_SENSOR_SCHEMA, + vol.Required(CONF_HUMIDITY): BME680_OVERSAMPLING_SENSOR_SCHEMA, + vol.Required(CONF_GAS_RESISTANCE): MQTT_SENSOR_SCHEMA, + vol.Optional(CONF_IIR_FILTER): vol.All(vol.Upper, vol.Any(*IIR_FILTER_OPTIONS)), + # TODO: Heater + vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds, +}).extend(sensor.MQTT_SENSOR_SCHEMA.schema) + + +def to_code(config): + rhs = App.make_bme680_sensor(config[CONF_TEMPERATURE][CONF_NAME], + config[CONF_PRESSURE][CONF_NAME], + config[CONF_HUMIDITY][CONF_NAME], + config[CONF_GAS_RESISTANCE][CONF_NAME], + config[CONF_ADDRESS], + config.get(CONF_UPDATE_INTERVAL)) + make = variable('Application::MakeBME680Sensor', config[CONF_ID], rhs) + bme680 = make.Pbme680 + if CONF_OVERSAMPLING in config[CONF_TEMPERATURE]: + constant = OVERSAMPLING_OPTIONS[config[CONF_TEMPERATURE][CONF_OVERSAMPLING]] + add(bme680.set_temperature_oversampling(RawExpression(constant))) + if CONF_OVERSAMPLING in config[CONF_PRESSURE]: + constant = OVERSAMPLING_OPTIONS[config[CONF_PRESSURE][CONF_OVERSAMPLING]] + add(bme680.set_pressure_oversampling(RawExpression(constant))) + if CONF_OVERSAMPLING in config[CONF_HUMIDITY]: + constant = OVERSAMPLING_OPTIONS[config[CONF_HUMIDITY][CONF_OVERSAMPLING]] + add(bme680.set_humidity_oversampling(RawExpression(constant))) + if CONF_IIR_FILTER in config: + constant = IIR_FILTER_OPTIONS[config[CONF_IIR_FILTER]] + add(bme680.set_iir_filter(RawExpression(constant))) + + sensor.setup_sensor(bme680.Pget_temperature_sensor(), config[CONF_TEMPERATURE]) + sensor.setup_mqtt_sensor_component(make.Pmqtt_temperature, config[CONF_TEMPERATURE]) + + sensor.setup_sensor(bme680.Pget_pressure_sensor(), config[CONF_PRESSURE]) + sensor.setup_mqtt_sensor_component(make.Pmqtt_pressure, config[CONF_PRESSURE]) + + sensor.setup_sensor(bme680.Pget_humidity_sensor(), config[CONF_HUMIDITY]) + sensor.setup_mqtt_sensor_component(make.Pmqtt_humidity, config[CONF_HUMIDITY]) + + sensor.setup_sensor(bme680.Pget_gas_resistance_sensor(), config[CONF_GAS_RESISTANCE]) + sensor.setup_mqtt_sensor_component(make.Pmqtt_gas_resistance, config[CONF_GAS_RESISTANCE]) + + +BUILD_FLAGS = '-DUSE_BME680' diff --git a/esphomeyaml/components/sensor/bmp085.py b/esphomeyaml/components/sensor/bmp085.py index 3dc9b135be..5f6c47ccbe 100644 --- a/esphomeyaml/components/sensor/bmp085.py +++ b/esphomeyaml/components/sensor/bmp085.py @@ -14,7 +14,7 @@ PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({ vol.Required(CONF_TEMPERATURE): MQTT_SENSOR_SCHEMA, vol.Required(CONF_PRESSURE): MQTT_SENSOR_SCHEMA, vol.Optional(CONF_ADDRESS): cv.i2c_address, - vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_not_null_time_period, + vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds, }) diff --git a/esphomeyaml/components/sensor/dallas.py b/esphomeyaml/components/sensor/dallas.py index 3bf4127815..2be1e2b80b 100644 --- a/esphomeyaml/components/sensor/dallas.py +++ b/esphomeyaml/components/sensor/dallas.py @@ -8,13 +8,13 @@ from esphomeyaml.const import CONF_ADDRESS, CONF_DALLAS_ID, CONF_INDEX, CONF_NAM CONF_UPDATE_INTERVAL, CONF_ID from esphomeyaml.helpers import HexIntLiteral, get_variable, Pvariable -PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({ +PLATFORM_SCHEMA = vol.All(sensor.PLATFORM_SCHEMA.extend({ cv.GenerateID('dallas_sensor'): cv.register_variable_id, vol.Exclusive(CONF_ADDRESS, 'dallas'): cv.hex_int, vol.Exclusive(CONF_INDEX, 'dallas'): cv.positive_int, vol.Optional(CONF_DALLAS_ID): cv.variable_id, vol.Optional(CONF_RESOLUTION): vol.All(vol.Coerce(int), vol.Range(min=8, max=12)), -}).extend(sensor.MQTT_SENSOR_ID_SCHEMA.schema) +}).extend(sensor.MQTT_SENSOR_ID_SCHEMA.schema), cv.has_at_least_one_key(CONF_ADDRESS, CONF_INDEX)) def to_code(config): diff --git a/esphomeyaml/components/sensor/dht.py b/esphomeyaml/components/sensor/dht.py index 9712040c3c..0aef34ac7e 100644 --- a/esphomeyaml/components/sensor/dht.py +++ b/esphomeyaml/components/sensor/dht.py @@ -1,33 +1,40 @@ import voluptuous as vol import esphomeyaml.config_validation as cv -from esphomeyaml import pins from esphomeyaml.components import sensor from esphomeyaml.components.sensor import MQTT_SENSOR_SCHEMA from esphomeyaml.const import CONF_HUMIDITY, CONF_ID, CONF_MODEL, CONF_NAME, CONF_PIN, \ CONF_TEMPERATURE, CONF_UPDATE_INTERVAL -from esphomeyaml.helpers import App, RawExpression, add, variable +from esphomeyaml.helpers import App, RawExpression, add, variable, exp_gpio_output_pin +from esphomeyaml.pins import GPIO_OUTPUT_PIN_SCHEMA -DHT_MODELS = ['AUTO_DETECT', 'DHT11', 'DHT22', 'AM2302', 'RHT03'] +DHT_MODELS = { + 'AUTO_DETECT': 'sensor::DHT_MODEL_AUTO_DETECT', + 'DHT11': 'sensor::DHT_MODEL_DHT11', + 'DHT22': 'sensor::DHT_MODEL_DHT22', + 'AM2302': 'sensor::DHT_MODEL_AM2302', + 'RHT03': 'sensor::DHT_MODEL_RHT03', +} PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({ cv.GenerateID('dht_sensor'): cv.register_variable_id, - vol.Required(CONF_PIN): pins.input_output_pin, + vol.Required(CONF_PIN): GPIO_OUTPUT_PIN_SCHEMA, vol.Required(CONF_TEMPERATURE): MQTT_SENSOR_SCHEMA, vol.Required(CONF_HUMIDITY): MQTT_SENSOR_SCHEMA, vol.Optional(CONF_MODEL): vol.All(vol.Upper, vol.Any(*DHT_MODELS)), - vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_not_null_time_period, + vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds, }) def to_code(config): + pin = exp_gpio_output_pin(config[CONF_PIN]) rhs = App.make_dht_sensor(config[CONF_TEMPERATURE][CONF_NAME], config[CONF_HUMIDITY][CONF_NAME], - config[CONF_PIN], config.get(CONF_UPDATE_INTERVAL)) + pin, config.get(CONF_UPDATE_INTERVAL)) dht = variable('Application::MakeDHTSensor', config[CONF_ID], rhs) if CONF_MODEL in config: - model = RawExpression('DHT::{}'.format(config[CONF_MODEL])) - add(dht.Pdht.set_dht_model(model)) + constant = DHT_MODELS[config[CONF_MODEL]] + add(dht.Pdht.set_dht_model(RawExpression(constant))) sensor.setup_sensor(dht.Pdht.Pget_temperature_sensor(), config[CONF_TEMPERATURE]) sensor.setup_mqtt_sensor_component(dht.Pmqtt_temperature, config[CONF_TEMPERATURE]) sensor.setup_sensor(dht.Pdht.Pget_humidity_sensor(), config[CONF_HUMIDITY]) diff --git a/esphomeyaml/components/sensor/hdc1080.py b/esphomeyaml/components/sensor/hdc1080.py index 3b29bf7e24..25112b1cf4 100644 --- a/esphomeyaml/components/sensor/hdc1080.py +++ b/esphomeyaml/components/sensor/hdc1080.py @@ -13,7 +13,7 @@ PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({ cv.GenerateID('dht_sensor'): cv.register_variable_id, vol.Required(CONF_TEMPERATURE): MQTT_SENSOR_SCHEMA, vol.Required(CONF_HUMIDITY): MQTT_SENSOR_SCHEMA, - vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_not_null_time_period, + vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds, }) diff --git a/esphomeyaml/components/sensor/htu21d.py b/esphomeyaml/components/sensor/htu21d.py index 3ec4e50b32..aa7d2e639f 100644 --- a/esphomeyaml/components/sensor/htu21d.py +++ b/esphomeyaml/components/sensor/htu21d.py @@ -13,7 +13,7 @@ PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({ cv.GenerateID('htu21d'): cv.register_variable_id, vol.Required(CONF_TEMPERATURE): MQTT_SENSOR_SCHEMA, vol.Required(CONF_HUMIDITY): MQTT_SENSOR_SCHEMA, - vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_not_null_time_period, + vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds, }) diff --git a/esphomeyaml/components/sensor/mpu6050.py b/esphomeyaml/components/sensor/mpu6050.py index 38d63c3861..f936a1b2fe 100644 --- a/esphomeyaml/components/sensor/mpu6050.py +++ b/esphomeyaml/components/sensor/mpu6050.py @@ -26,7 +26,7 @@ PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({ vol.Optional(CONF_GYRO_Y): MQTT_SENSOR_ID_SCHEMA, vol.Optional(CONF_GYRO_Z): MQTT_SENSOR_ID_SCHEMA, vol.Optional(CONF_TEMPERATURE): MQTT_SENSOR_ID_SCHEMA, - vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_not_null_time_period, + vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds, }) diff --git a/esphomeyaml/components/sensor/pulse_counter.py b/esphomeyaml/components/sensor/pulse_counter.py index 3329f3ce25..86736fdc78 100644 --- a/esphomeyaml/components/sensor/pulse_counter.py +++ b/esphomeyaml/components/sensor/pulse_counter.py @@ -36,7 +36,7 @@ PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({ vol.Required(CONF_FALLING_EDGE): COUNT_MODE_SCHEMA, }), vol.Optional(CONF_INTERNAL_FILTER): vol.All(vol.Coerce(int), vol.Range(min=0, max=1023)), - vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_not_null_time_period, + vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds, }).extend(sensor.MQTT_SENSOR_SCHEMA.schema) diff --git a/esphomeyaml/components/sensor/tsl2561.py b/esphomeyaml/components/sensor/tsl2561.py new file mode 100644 index 0000000000..d636ed06a6 --- /dev/null +++ b/esphomeyaml/components/sensor/tsl2561.py @@ -0,0 +1,52 @@ +import voluptuous as vol + +import esphomeyaml.config_validation as cv +from esphomeyaml.components import sensor +from esphomeyaml.const import CONF_ADDRESS, CONF_GAIN, CONF_ID, CONF_INTEGRATION_TIME, CONF_NAME, \ + CONF_UPDATE_INTERVAL +from esphomeyaml.core import TimePeriod +from esphomeyaml.helpers import App, RawExpression, add, variable + +DEPENDENCIES = ['i2c'] + +INTEGRATION_TIMES = { + TimePeriod(milliseconds=14): 'sensor::TSL2561_INTEGRATION_14MS', + TimePeriod(milliseconds=101): 'sensor::TSL2561_INTEGRATION_101MS', + TimePeriod(milliseconds=402): 'sensor::TSL2561_INTEGRATION_402MS', +} +GAINS = { + '1X': 'sensor::TSL2561_GAIN_1X', + '16X': 'sensor::TSL2561_GAIN_16X', +} + +CONF_IS_CS_PACKAGE = 'is_cs_package' + +PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({ + cv.GenerateID('tsl2561_sensor'): cv.register_variable_id, + vol.Optional(CONF_ADDRESS, default=0x39): cv.i2c_address, + vol.Optional(CONF_INTEGRATION_TIME): vol.All(cv.positive_time_period_milliseconds, + vol.Any(*INTEGRATION_TIMES)), + vol.Optional(CONF_GAIN): vol.All(vol.Upper, vol.Any(*GAINS)), + vol.Optional(CONF_IS_CS_PACKAGE): cv.boolean, + vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds, +}).extend(sensor.MQTT_SENSOR_SCHEMA.schema) + + +def to_code(config): + rhs = App.make_tsl2561_sensor(config[CONF_NAME], config[CONF_ADDRESS], + config.get(CONF_UPDATE_INTERVAL)) + make_tsl = variable('Application::MakeTSL2561Sensor', config[CONF_ID], rhs) + tsl2561 = make_tsl.Ptsl2561 + if CONF_INTEGRATION_TIME in config: + constant = INTEGRATION_TIMES[config[CONF_INTEGRATION_TIME]] + add(tsl2561.set_integration_time(RawExpression(constant))) + if CONF_GAIN in config: + constant = GAINS[config[CONF_GAIN]] + add(tsl2561.set_gain(RawExpression(constant))) + if CONF_IS_CS_PACKAGE in config: + add(tsl2561.set_is_cs_package(config[CONF_IS_CS_PACKAGE])) + sensor.setup_sensor(tsl2561, config) + sensor.setup_mqtt_sensor_component(make_tsl.Pmqtt, config) + + +BUILD_FLAGS = '-DUSE_TSL2561' diff --git a/esphomeyaml/components/sensor/ultrasonic.py b/esphomeyaml/components/sensor/ultrasonic.py index 357b771e5f..557e2989bd 100644 --- a/esphomeyaml/components/sensor/ultrasonic.py +++ b/esphomeyaml/components/sensor/ultrasonic.py @@ -13,8 +13,8 @@ PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({ vol.Required(CONF_TRIGGER_PIN): pins.GPIO_OUTPUT_PIN_SCHEMA, vol.Required(CONF_ECHO_PIN): pins.GPIO_INPUT_PIN_SCHEMA, vol.Exclusive(CONF_TIMEOUT_METER, 'timeout'): cv.positive_float, - vol.Exclusive(CONF_TIMEOUT_TIME, 'timeout'): cv.positive_int, - vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_not_null_time_period, + vol.Exclusive(CONF_TIMEOUT_TIME, 'timeout'): cv.positive_time_period_microseconds, + vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds, }).extend(sensor.MQTT_SENSOR_SCHEMA.schema) diff --git a/esphomeyaml/components/switch/ir_transmitter.py b/esphomeyaml/components/switch/ir_transmitter.py index d73e4e83dc..4614168c14 100644 --- a/esphomeyaml/components/switch/ir_transmitter.py +++ b/esphomeyaml/components/switch/ir_transmitter.py @@ -3,13 +3,20 @@ import voluptuous as vol import esphomeyaml.config_validation as cv from esphomeyaml.components import switch from esphomeyaml.components.ir_transmitter import IR_TRANSMITTER_COMPONENT_CLASS -from esphomeyaml.const import CONF_ADDRESS, CONF_COMMAND, CONF_DATA, CONF_IR_TRANSMITTER_ID, \ - CONF_LG, CONF_NBITS, CONF_NEC, CONF_PANASONIC, CONF_REPEAT, CONF_SONY, CONF_TIMES, \ - CONF_WAIT_TIME_US, CONF_RAW, CONF_CARRIER_FREQUENCY, CONF_NAME, CONF_ID +from esphomeyaml.const import CONF_ADDRESS, CONF_CARRIER_FREQUENCY, CONF_COMMAND, CONF_DATA, \ + CONF_ID, CONF_IR_TRANSMITTER_ID, CONF_LG, CONF_NAME, CONF_NBITS, CONF_NEC, CONF_PANASONIC, \ + CONF_RAW, CONF_REPEAT, CONF_SONY, CONF_TIMES, CONF_WAIT_TIME from esphomeyaml.core import ESPHomeYAMLError -from esphomeyaml.helpers import HexIntLiteral, MockObj, get_variable, ArrayInitializer, Pvariable +from esphomeyaml.helpers import ArrayInitializer, HexIntLiteral, MockObj, Pvariable, get_variable -PLATFORM_SCHEMA = switch.PLATFORM_SCHEMA.extend({ +DEPENDENCIES = ['ir_transmitter'] + +IR_KEYS = [CONF_NEC, CONF_LG, CONF_SONY, CONF_PANASONIC, CONF_RAW] + +WAIT_TIME_MESSAGE = "The wait_time_us option has been renamed to wait_time in order to decrease " \ + "ambiguity. " + +PLATFORM_SCHEMA = vol.All(switch.PLATFORM_SCHEMA.extend({ cv.GenerateID('ir_transmitter_switch'): cv.register_variable_id, vol.Exclusive(CONF_NEC, 'code'): vol.Schema({ vol.Required(CONF_ADDRESS): cv.hex_uint16_t, @@ -33,11 +40,12 @@ PLATFORM_SCHEMA = switch.PLATFORM_SCHEMA.extend({ }), vol.Optional(CONF_REPEAT): vol.Any(cv.positive_not_null_int, vol.Schema({ vol.Required(CONF_TIMES): cv.positive_not_null_int, - vol.Required(CONF_WAIT_TIME_US): cv.uint32_t, + vol.Required(CONF_WAIT_TIME): cv.positive_time_period_microseconds, + + vol.Optional('wait_time_us'): cv.invalid(WAIT_TIME_MESSAGE), })), vol.Optional(CONF_IR_TRANSMITTER_ID): cv.variable_id, -}).extend(switch.MQTT_SWITCH_ID_SCHEMA.schema) - +}).extend(switch.MQTT_SWITCH_ID_SCHEMA.schema), cv.has_at_least_one_key(*IR_KEYS)) # pylint: disable=invalid-name SendData = MockObj('switch_::ir::SendData', '::') @@ -77,7 +85,7 @@ def exp_send_data(config): wait_us = None else: times = config[CONF_REPEAT][CONF_TIMES] - wait_us = config[CONF_REPEAT][CONF_WAIT_TIME_US] + wait_us = config[CONF_REPEAT][CONF_WAIT_TIME] base = MockObj(unicode(base), u'.') base = base.repeat(times, wait_us) return base diff --git a/esphomeyaml/components/web_server.py b/esphomeyaml/components/web_server.py index 486ebfdc85..54b1c54fe1 100644 --- a/esphomeyaml/components/web_server.py +++ b/esphomeyaml/components/web_server.py @@ -3,7 +3,8 @@ import logging import voluptuous as vol import esphomeyaml.config_validation as cv -from esphomeyaml.const import CONF_PORT, CONF_JS_URL, CONF_CSS_URL, CONF_ID +from esphomeyaml import core +from esphomeyaml.const import CONF_PORT, CONF_JS_URL, CONF_CSS_URL, CONF_ID, ESP_PLATFORM_ESP32 from esphomeyaml.helpers import App, add, Pvariable _LOGGER = logging.getLogger(__name__) @@ -26,3 +27,9 @@ def to_code(config): BUILD_FLAGS = '-DUSE_WEB_SERVER' + + +def lib_deps(config): + if core.ESP_PLATFORM == ESP_PLATFORM_ESP32: + return 'FS' + return '' diff --git a/esphomeyaml/components/wifi.py b/esphomeyaml/components/wifi.py index 8d4ef405f4..f58ffcdbb6 100644 --- a/esphomeyaml/components/wifi.py +++ b/esphomeyaml/components/wifi.py @@ -1,8 +1,10 @@ import voluptuous as vol import esphomeyaml.config_validation as cv +from esphomeyaml import core from esphomeyaml.const import CONF_AP, CONF_CHANNEL, CONF_DNS1, CONF_DNS2, CONF_GATEWAY, \ - CONF_HOSTNAME, CONF_ID, CONF_MANUAL_IP, CONF_PASSWORD, CONF_SSID, CONF_STATIC_IP, CONF_SUBNET + CONF_HOSTNAME, CONF_ID, CONF_MANUAL_IP, CONF_PASSWORD, CONF_SSID, CONF_STATIC_IP, CONF_SUBNET, \ + ESP_PLATFORM_ESP8266 from esphomeyaml.helpers import App, MockObj, Pvariable, StructInitializer, add @@ -87,3 +89,9 @@ def to_code(config): if CONF_HOSTNAME in config: add(wifi.set_hostname(config[CONF_HOSTNAME])) + + +def lib_deps(config): + if core.ESP_PLATFORM == ESP_PLATFORM_ESP8266: + return 'ESP8266WiFi' + return None diff --git a/esphomeyaml/config.py b/esphomeyaml/config.py index 91afe65ab6..e2f9ddac44 100644 --- a/esphomeyaml/config.py +++ b/esphomeyaml/config.py @@ -25,7 +25,7 @@ CORE_SCHEMA = vol.Schema({ 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=False): cv.boolean, + vol.Optional(CONF_USE_BUILD_FLAGS, default=True): cv.boolean, }) REQUIRED_COMPONENTS = [ diff --git a/esphomeyaml/config_validation.py b/esphomeyaml/config_validation.py index 02395a72b0..af39a4738d 100644 --- a/esphomeyaml/config_validation.py +++ b/esphomeyaml/config_validation.py @@ -4,7 +4,6 @@ from __future__ import print_function import logging import re -from datetime import timedelta import voluptuous as vol @@ -13,7 +12,8 @@ from esphomeyaml.const import CONF_AVAILABILITY, CONF_COMMAND_TOPIC, CONF_DISCOV CONF_NAME, CONF_PAYLOAD_AVAILABLE, \ CONF_PAYLOAD_NOT_AVAILABLE, CONF_PLATFORM, CONF_RETAIN, CONF_STATE_TOPIC, CONF_TOPIC, \ ESP_PLATFORM_ESP32, ESP_PLATFORM_ESP8266 -from esphomeyaml.core import HexInt, IPAddress +from esphomeyaml.core import HexInt, IPAddress, TimePeriod, TimePeriodMilliseconds, \ + TimePeriodMicroseconds, TimePeriodSeconds from esphomeyaml.helpers import ensure_unique_string _LOGGER = logging.getLogger(__name__) @@ -178,19 +178,28 @@ def has_at_least_one_key(*keys): return validate -TIME_PERIOD_ERROR = "Time period {} should be format 5ms, 5s, 5min, 5h" +TIME_PERIOD_ERROR = "Time period {} should be format number + unit, for example 5ms, 5s, 5min, 5h" time_period_dict = vol.All( dict, vol.Schema({ - 'days': vol.Coerce(int), - 'hours': vol.Coerce(int), - 'minutes': vol.Coerce(int), - 'seconds': vol.Coerce(int), - 'milliseconds': vol.Coerce(int), + 'days': vol.Coerce(float), + 'hours': vol.Coerce(float), + 'minutes': vol.Coerce(float), + 'seconds': vol.Coerce(float), + 'milliseconds': vol.Coerce(float), + 'microseconds': vol.Coerce(float), }), has_at_least_one_key('days', 'hours', 'minutes', - 'seconds', 'milliseconds'), - lambda value: timedelta(**value)) + 'seconds', 'milliseconds', 'microseconds'), + lambda value: TimePeriod(**value)) + + +TIME_PERIOD_EXPLICIT_MESSAGE = ("The old way of being able to write time values without a " + "time unit (like \"1000\" for 1000 milliseconds) has been " + "removed in 1.5.0 as it was ambiguous in some places. Please " + "now explicitly specify the time unit (like \"1000ms\"). See " + "https://esphomelib.com/esphomeyaml/configuration-types.html#time " + "for more information.") def time_period_str_colon(value): @@ -220,7 +229,7 @@ def time_period_str_colon(value): else: raise vol.Invalid(TIME_PERIOD_ERROR.format(value)) - offset = timedelta(hours=hour, minutes=minute, seconds=second) + offset = TimePeriod(hours=hour, minutes=minute, seconds=second) if negative_offset: offset *= -1 @@ -232,10 +241,19 @@ def time_period_str_unit(value): """Validate and transform time period with time unit and integer value.""" if isinstance(value, int): value = str(value) - elif not isinstance(value, str): + elif not isinstance(value, (str, unicode)): raise vol.Invalid("Expected string for time period with unit.") + try: + float(value) + except ValueError: + pass + else: + raise vol.Invalid(TIME_PERIOD_EXPLICIT_MESSAGE) + unit_to_kwarg = { + 'us': 'microseconds', + 'microseconds': 'microseconds', 'ms': 'milliseconds', 'milliseconds': 'milliseconds', 's': 'seconds', @@ -256,51 +274,56 @@ def time_period_str_unit(value): u"got {}".format(value)) kwarg = unit_to_kwarg[match.group(2)] - return timedelta(**{kwarg: float(match.group(1))}) + return TimePeriod(**{kwarg: float(match.group(1))}) -def time_period_to_milliseconds(value): - if isinstance(value, (int, long)): - return value - if isinstance(value, float): - return int(value) - return int(value.total_seconds() * 1000) +def time_period_in_milliseconds(value): + if value.microseconds is not None and value.microseconds != 0: + raise vol.Invalid("Maximum precision is milliseconds") + return TimePeriodMilliseconds(**value.as_dict()) -def time_period_to_seconds(value): - if value / 1000 != value // 1000: - raise vol.Invalid("Fractions of seconds are not supported here.") - return value / 1000 +def time_period_in_microseconds(value): + return TimePeriodMicroseconds(**value.as_dict()) -time_period = vol.All(vol.Any(time_period_str_colon, time_period_str_unit, timedelta, - time_period_dict), - time_period_to_milliseconds) -positive_time_period = vol.All(time_period, vol.Range(min=0)) -positive_not_null_time_period = vol.All(time_period, vol.Range(min=0, min_included=False)) +def time_period_in_seconds(value): + if value.microseconds is not None and value.microseconds != 0: + raise vol.Invalid("Maximum precision is seconds") + if value.milliseconds is not None and value.milliseconds != 0: + raise vol.Invalid("Maximum precision is seconds") + return TimePeriodSeconds(**value.as_dict()) + + +time_period = vol.Any(time_period_str_unit, time_period_str_colon, time_period_dict) +positive_time_period = vol.All(time_period, vol.Range(min=TimePeriod())) +positive_time_period_milliseconds = vol.All(positive_time_period, time_period_in_milliseconds) +positive_time_period_seconds = vol.All(positive_time_period, time_period_in_seconds) +positive_time_period_microseconds = vol.All(positive_time_period, time_period_in_microseconds) +positive_not_null_time_period = vol.All(time_period, + vol.Range(min=TimePeriod(), min_included=False)) METRIC_SUFFIXES = { 'E': 1e18, 'P': 1e15, 'T': 1e12, 'G': 1e9, 'M': 1e6, 'k': 1e3, 'da': 10, 'd': 1e-1, 'c': 1e-2, 'm': 0.001, u'ยต': 1e-6, 'u': 1e-6, 'n': 1e-9, 'p': 1e-12, 'f': 1e-15, 'a': 1e-18, + '': 1 } def frequency(value): - value = string(value).replace(' ', '').lower() - if value.endswith('Hz') or value.endswith('hz') or value.endswith('HZ'): - value = value[:-2] - if not value: - raise vol.Invalid(u"Frequency must have value") - multiplier = 1 - if value[:-1] in METRIC_SUFFIXES: - multiplier = METRIC_SUFFIXES[value[:-1]] - value = value[:-1] - elif len(value) >= 2 and value[:-2] in METRIC_SUFFIXES: - multiplier = METRIC_SUFFIXES[value[:-2]] - value = value[:-2] - float_val = vol.Coerce(float)(value) - return float_val * multiplier + match = re.match(r"^([-+]?[0-9]*\.?[0-9]*)\s*(\w*?)(?:Hz|HZ|hz)?$", value) + + if match is None: + raise vol.Invalid(u"Expected frequency with unit, " + u"got {}".format(value)) + + mantissa = float(match.group(1)) + if match.group(2) not in METRIC_SUFFIXES: + raise vol.Invalid(u"Invalid frequency suffix {}".format(match.group(2))) + + multiplier = METRIC_SUFFIXES[match.group(2)] + return mantissa * multiplier def hostname(value): @@ -407,8 +430,10 @@ hex_uint32_t = vol.All(hex_int, vol.Range(min=0, max=4294967295)) i2c_address = hex_uint8_t -def invalid(_): - raise vol.Invalid("This shouldn't happen.") +def invalid(message): + def validator(value): + raise vol.Invalid(message) + return validator def valid(value): diff --git a/esphomeyaml/const.py b/esphomeyaml/const.py index 825a8cd309..f34b06a22d 100644 --- a/esphomeyaml/const.py +++ b/esphomeyaml/const.py @@ -135,7 +135,7 @@ CONF_SONY = 'sony' CONF_PANASONIC = 'panasonic' CONF_REPEAT = 'repeat' CONF_TIMES = 'times' -CONF_WAIT_TIME_US = 'wait_time_us' +CONF_WAIT_TIME = 'wait_time' CONF_OSCILLATION_OUTPUT = 'oscillation_output' CONF_SPEED = 'speed' CONF_OSCILLATION_STATE_TOPIC = 'oscillation_state_topic' @@ -161,9 +161,29 @@ CONF_AP = 'ap' CONF_CSS_URL = 'css_url' CONF_JS_URL = 'js_url' CONF_SSL_FINGERPRINTS = 'ssl_fingerprints' +CONF_PCF8574 = 'pcf8574' CONF_PCF8575 = 'pcf8575' CONF_SCAN = 'scan' CONF_KEEPALIVE = 'keepalive' +CONF_INTEGRATION_TIME = 'integration_time' +CONF_RECEIVE_TIMEOUT = 'receive_timeout' +CONF_SCAN_INTERVAL = 'scan_interval' +CONF_MAC_ADDRESS = 'mac_address' +CONF_SETUP_MODE = 'setup_mode' +CONF_IIR_FILTER = 'iir_filter' +CONF_MEASUREMENT_DURATION = 'measurement_duration' +CONF_LOW_VOLTAGE_REFERENCE = 'low_voltage_reference' +CONF_HIGH_VOLTAGE_REFERENCE = 'high_voltage_reference' +CONF_VOLTAGE_ATTENUATION = 'voltage_attenuation' +CONF_THRESHOLD = 'threshold' +CONF_OVERSAMPLING = 'oversampling' +CONF_GAS_RESISTANCE = 'gas_resistance' +CONF_NUM_LEDS = 'num_leds' +CONF_MAX_REFRESH_RATE = 'max_refresh_rate' +CONF_CHIPSET = 'chipset' +CONF_DATA_PIN = 'data_pin' +CONF_CLOCK_PIN = 'clock_pin' +CONF_RGB_ORDER = 'rgb_order' ESP32_BOARDS = [ 'featheresp32', 'node32s', 'espea32', 'firebeetle32', 'esp32doit-devkit-v1', diff --git a/esphomeyaml/core.py b/esphomeyaml/core.py index 2e696c285a..2475e5b09c 100644 --- a/esphomeyaml/core.py +++ b/esphomeyaml/core.py @@ -1,3 +1,7 @@ +import math +from collections import OrderedDict + + class ESPHomeYAMLError(Exception): """General esphomeyaml exception occurred.""" pass @@ -18,6 +22,155 @@ class IPAddress(object): return '.'.join(str(x) for x in self.args) +class MACAddress(object): + def __init__(self, *parts): + if len(parts) != 6: + raise ValueError(u"MAC Address must consist of 6 items") + self.parts = parts + + def __str__(self): + return ':'.join('{:02X}'.format(part) for part in self.parts) + + +def is_approximately_integer(value): + if isinstance(value, (int, long)): + return True + return abs(value - round(value)) < 0.001 + + +class TimePeriod(object): + def __init__(self, microseconds=None, milliseconds=None, seconds=None, + minutes=None, hours=None, days=None): + if days is not None: + if not is_approximately_integer(days): + frac_days, days = math.modf(days) + hours = (hours or 0) + frac_days * 24 + self.days = int(round(days)) + else: + self.days = None + + if hours is not None: + if not is_approximately_integer(hours): + frac_hours, hours = math.modf(hours) + minutes = (minutes or 0) + frac_hours * 60 + self.hours = int(round(hours)) + else: + self.hours = None + + if minutes is not None: + if not is_approximately_integer(minutes): + frac_minutes, minutes = math.modf(minutes) + seconds = (seconds or 0) + frac_minutes * 60 + self.minutes = int(round(minutes)) + else: + self.minutes = None + + if seconds is not None: + if not is_approximately_integer(seconds): + frac_seconds, seconds = math.modf(seconds) + milliseconds = (milliseconds or 0) + frac_seconds * 1000 + self.seconds = int(round(seconds)) + else: + self.seconds = None + + if milliseconds is not None: + if not is_approximately_integer(milliseconds): + frac_milliseconds, milliseconds = math.modf(milliseconds) + microseconds = (microseconds or 0) + frac_milliseconds * 1000 + self.milliseconds = int(round(milliseconds)) + else: + self.milliseconds = None + + if microseconds is not None: + if not is_approximately_integer(microseconds): + raise ValueError("Maximum precision is microseconds") + self.microseconds = int(round(microseconds)) + else: + self.microseconds = None + + def as_dict(self): + out = OrderedDict() + if self.microseconds is not None: + out['microseconds'] = self.microseconds + if self.milliseconds is not None: + out['milliseconds'] = self.milliseconds + if self.seconds is not None: + out['seconds'] = self.seconds + if self.minutes is not None: + out['minutes'] = self.minutes + if self.hours is not None: + out['hours'] = self.hours + if self.days is not None: + out['days'] = self.days + return out + + @property + def total_microseconds(self): + return self.total_milliseconds * 1000 + (self.microseconds or 0) + + @property + def total_milliseconds(self): + return self.total_seconds * 1000 + (self.milliseconds or 0) + + @property + def total_seconds(self): + return self.total_minutes * 60 + (self.seconds or 0) + + @property + def total_minutes(self): + return self.total_hours * 60 + (self.minutes or 0) + + @property + def total_hours(self): + return self.total_days * 24 + (self.hours or 0) + + @property + def total_days(self): + return self.days or 0 + + def __eq__(self, other): + if not isinstance(other, TimePeriod): + raise ValueError("other must be TimePeriod") + return self.total_microseconds == other.total_microseconds + + def __ne__(self, other): + if not isinstance(other, TimePeriod): + raise ValueError("other must be TimePeriod") + return self.total_microseconds != other.total_microseconds + + def __lt__(self, other): + if not isinstance(other, TimePeriod): + raise ValueError("other must be TimePeriod") + return self.total_microseconds < other.total_microseconds + + def __gt__(self, other): + if not isinstance(other, TimePeriod): + raise ValueError("other must be TimePeriod") + return self.total_microseconds > other.total_microseconds + + def __le__(self, other): + if not isinstance(other, TimePeriod): + raise ValueError("other must be TimePeriod") + return self.total_microseconds <= other.total_microseconds + + def __ge__(self, other): + if not isinstance(other, TimePeriod): + raise ValueError("other must be TimePeriod") + return self.total_microseconds >= other.total_microseconds + + +class TimePeriodMicroseconds(TimePeriod): + pass + + +class TimePeriodMilliseconds(TimePeriod): + pass + + +class TimePeriodSeconds(TimePeriod): + pass + + CONFIG_PATH = None SIMPLIFY = True ESP_PLATFORM = '' diff --git a/esphomeyaml/helpers.py b/esphomeyaml/helpers.py index bc69279f6a..dfb3f900f9 100644 --- a/esphomeyaml/helpers.py +++ b/esphomeyaml/helpers.py @@ -8,8 +8,9 @@ from esphomeyaml import core from esphomeyaml.const import CONF_AVAILABILITY, CONF_COMMAND_TOPIC, CONF_DISCOVERY, \ CONF_INVERTED, \ CONF_MODE, CONF_NUMBER, CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_RETAIN, \ - CONF_STATE_TOPIC, CONF_TOPIC -from esphomeyaml.core import ESPHomeYAMLError, HexInt + CONF_STATE_TOPIC, CONF_TOPIC, CONF_PCF8574 +from esphomeyaml.core import ESPHomeYAMLError, HexInt, TimePeriodMicroseconds, \ + TimePeriodMilliseconds, TimePeriodSeconds _LOGGER = logging.getLogger(__name__) @@ -103,14 +104,32 @@ class ExpressionList(Expression): return indent_all_but_first_and_last(text) -class CallExpression(Expression): - def __init__(self, base, *args): - super(CallExpression, self).__init__() - self.base = base +class TemplateArguments(Expression): + def __init__(self, *args): + super(TemplateArguments, self).__init__() self.args = ExpressionList(*args) self.requires.append(self.args) def __str__(self): + return u'<{}>'.format(self.args) + + +class CallExpression(Expression): + def __init__(self, base, *args): + super(CallExpression, self).__init__() + self.base = base + if args and isinstance(args[0], TemplateArguments): + self.template_args = args[0] + self.requires.append(self.template_args) + args = args[1:] + else: + self.template_args = None + self.args = ExpressionList(*args) + self.requires.append(self.args) + + def __str__(self): + if self.template_args is not None: + return u'{}{}({})'.format(self.base, self.template_args, self.args) return u'{}({})'.format(self.base, self.args) @@ -218,10 +237,18 @@ def safe_exp(obj): return BoolLiteral(obj) elif isinstance(obj, (str, unicode)): return StringLiteral(obj) + elif isinstance(obj, HexInt): + return HexIntLiteral(obj) elif isinstance(obj, (int, long)): return IntLiteral(obj) elif isinstance(obj, float): return FloatLiteral(obj) + elif isinstance(obj, TimePeriodMicroseconds): + return IntLiteral(int(obj.total_microseconds)) + elif isinstance(obj, TimePeriodMilliseconds): + return IntLiteral(int(obj.total_milliseconds)) + elif isinstance(obj, TimePeriodSeconds): + return IntLiteral(int(obj.total_seconds)) raise ValueError(u"Object is not an expression", obj) @@ -300,7 +327,8 @@ def get_variable(id, type=None): return None if result is None: if id is not None: - result = _VARIABLES[id][0] + if id in _VARIABLES: + result = _VARIABLES[id][0] elif type is not None: result = next((x[0] for x in _VARIABLES.itervalues() if x[1] == type), None) @@ -371,8 +399,8 @@ def exp_gpio_pin_(obj, conf, default_mode): if isinstance(conf, int): return conf - if 'pcf8574' in conf: - hub = get_variable(conf['pcf8574']) + if CONF_PCF8574 in conf: + hub = get_variable(conf[CONF_PCF8574], 'io::PCF8574Component') if default_mode == u'INPUT': return hub.make_input_pin(conf[CONF_NUMBER], RawExpression('PCF8574_' + conf[CONF_MODE]), diff --git a/esphomeyaml/pins.py b/esphomeyaml/pins.py index 99ccfa97f1..b32a615aa9 100644 --- a/esphomeyaml/pins.py +++ b/esphomeyaml/pins.py @@ -4,8 +4,8 @@ import voluptuous as vol import esphomeyaml.config_validation as cv from esphomeyaml import core -from esphomeyaml.const import ESP_PLATFORM_ESP32, ESP_PLATFORM_ESP8266, CONF_NUMBER, CONF_MODE, \ - CONF_INVERTED, CONF_ID, CONF_PCF8575 +from esphomeyaml.const import CONF_INVERTED, CONF_MODE, CONF_NUMBER, CONF_PCF8574, \ + ESP_PLATFORM_ESP32, ESP_PLATFORM_ESP8266 _LOGGER = logging.getLogger(__name__) @@ -96,7 +96,7 @@ def _translate_pin(value): raise vol.Invalid(u"Invalid ESP platform.") -def _validate_gpio_pin(value): +def validate_gpio_pin(value): value = _translate_pin(value) if core.ESP_PLATFORM == ESP_PLATFORM_ESP32: if value < 0 or value > 39: @@ -119,7 +119,7 @@ def _validate_gpio_pin(value): def input_pin(value): - value = _validate_gpio_pin(value) + value = validate_gpio_pin(value) if core.ESP_PLATFORM == ESP_PLATFORM_ESP32: return value elif core.ESP_PLATFORM == ESP_PLATFORM_ESP8266: @@ -128,7 +128,7 @@ def input_pin(value): def output_pin(value): - value = _validate_gpio_pin(value) + value = validate_gpio_pin(value) if core.ESP_PLATFORM == ESP_PLATFORM_ESP32: if 34 <= value <= 39: raise vol.Invalid(u"ESP32: Pin {} (34-39) can only be used as " @@ -142,7 +142,7 @@ def output_pin(value): def analog_pin(value): - value = _validate_gpio_pin(value) + value = validate_gpio_pin(value) if core.ESP_PLATFORM == ESP_PLATFORM_ESP32: if 32 <= value <= 39: # ADC1 return value @@ -179,57 +179,23 @@ def pin_mode(value): raise vol.Invalid(u"Invalid ESP platform.") -def pcf8574_pin(value, default_mode): - if 'pcf8574' not in core.RAW_CONFIG: - raise vol.Invalid("PCF8574 not loaded, ignore this.") +PCF8574_OUTPUT_PIN_SCHEMA = vol.Schema({ + vol.Required(CONF_PCF8574): cv.variable_id, + vol.Required(CONF_NUMBER): vol.Coerce(int), + vol.Optional(CONF_INVERTED): cv.boolean, +}) - if isinstance(value, (str, unicode)): - value = {CONF_NUMBER: value} +PCF8574_INPUT_PIN_SCHEMA = PCF8574_OUTPUT_PIN_SCHEMA.extend({ + vol.Optional(CONF_MODE, default='INPUT'): vol.All(vol.Upper, vol.Any("INPUT", "INPUT_PULLUP")), +}) - if not isinstance(value, dict) or not isinstance(value.get(CONF_NUMBER), (str, unicode)) or \ - value[CONF_NUMBER].count('.') != 1: - raise vol.Invalid("Not PCF8574 pin") - - pcf_id, pin = value[CONF_NUMBER].split('.') - pin = vol.Coerce(int)(pin) - - pcf_conf = cv.ensure_list(core.RAW_CONFIG['pcf8574']) - pcf = next((conf for conf in pcf_conf if conf[CONF_ID] == pcf_id), None) - if pcf is None: - raise vol.Invalid("Unknown PCF8574 id: {}".format(pcf_id)) - - if pcf.get(CONF_PCF8575, False): - pin = vol.Range(min=0, max=15)(pin) - else: - pin = vol.Range(min=0, max=7)(pin) - - mode = vol.All(vol.Coerce(str), vol.Upper)(value.get(CONF_MODE, default_mode)) - if mode not in ['INPUT', 'INPUT_PULLUP', 'OUTPUT']: - raise vol.Invalid("Invalid pin mode for PCF8575: {}".format(mode)) - - return { - 'pcf8574': pcf[CONF_ID], - CONF_NUMBER: pin, - CONF_INVERTED: value.get(CONF_INVERTED, False), - CONF_MODE: mode - } - - -def pcf8574_output_pin(value): - return pcf8574_pin(value, 'OUTPUT') - - -def pcf8574_input_pin(value): - return pcf8574_pin(value, 'INPUT') - - -GPIO_OUTPUT_PIN_SCHEMA = vol.Any(output_pin, pcf8574_output_pin, vol.Schema({ +GPIO_OUTPUT_PIN_SCHEMA = vol.Any(output_pin, PCF8574_OUTPUT_PIN_SCHEMA, vol.Schema({ vol.Required(CONF_NUMBER): output_pin, vol.Optional(CONF_MODE): pin_mode, vol.Optional(CONF_INVERTED): cv.boolean, })) -GPIO_INPUT_PIN_SCHEMA = vol.Any(input_pin, pcf8574_input_pin, vol.Schema({ +GPIO_INPUT_PIN_SCHEMA = vol.Any(input_pin, PCF8574_INPUT_PIN_SCHEMA, vol.Schema({ vol.Required(CONF_NUMBER): input_pin, vol.Optional(CONF_MODE): pin_mode, vol.Optional(CONF_INVERTED): cv.boolean, diff --git a/esphomeyaml/writer.py b/esphomeyaml/writer.py index 769dbc628c..fd03bfa59f 100644 --- a/esphomeyaml/writer.py +++ b/esphomeyaml/writer.py @@ -96,6 +96,7 @@ def get_ini_content(config): build_flags |= get_build_flags(config, 'BUILD_FLAGS') build_flags.add(u"-DESPHOMEYAML_USE") build_flags |= get_build_flags(config, 'required_build_flags') + build_flags |= get_build_flags(config, 'REQUIRED_BUILD_FLAGS') # avoid changing build flags order build_flags = sorted(list(build_flags)) @@ -104,27 +105,16 @@ def get_ini_content(config): lib_deps = set() lib_deps.add(config[CONF_ESPHOMEYAML][CONF_LIBRARY_URI]) + 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 |= { - 'ArduinoOTA', - 'Update', - 'ESPmDNS', - 'Wire', - 'FS', - 'Preferences', - + 'Preferences', # Preferences helper } - elif core.ESP_PLATFORM == ESP_PLATFORM_ESP8266: - lib_deps |= { - 'ESP8266WiFi', - 'Wire', - 'Hash', - 'ESP8266mDNS', - 'ArduinoOTA', - } - else: - raise ESPHomeYAMLError("Unsupported platform {}".format(core.ESP_PLATFORM)) - options[u'lib_deps'] = u'\n '.join(sorted(list(lib_deps))) + # avoid changing build flags order + lib_deps = sorted(x for x in lib_deps if x) + if lib_deps: + options[u'lib_deps'] = u'\n '.join(lib_deps) return INI_CONTENT_FORMAT.format(**options) diff --git a/esphomeyaml/yaml_util.py b/esphomeyaml/yaml_util.py index 0c1290a4ac..c949f4d3a6 100644 --- a/esphomeyaml/yaml_util.py +++ b/esphomeyaml/yaml_util.py @@ -8,7 +8,8 @@ from collections import OrderedDict import yaml -from esphomeyaml.core import ESPHomeYAMLError, HexInt, IPAddress +from esphomeyaml.core import ESPHomeYAMLError, HexInt, IPAddress, MACAddress, TimePeriod, \ + TimePeriodMicroseconds, TimePeriodMilliseconds, TimePeriodSeconds _LOGGER = logging.getLogger(__name__) @@ -245,11 +246,30 @@ def hex_int_representer(_, data): return node -def ipaddress_representer(_, data): +def stringify_representer(_, data): node = yaml.ScalarNode(tag=u'tag:yaml.org,2002:str', value=str(data)) return node +TIME_PERIOD_UNIT_MAP = { + 'microseconds': 'us', + 'milliseconds': 'ms', + 'seconds': 's', + 'minutes': 'min', + 'hours': 'h', + 'days': 'd', +} + + +def represent_time_period(dumper, data): + dictionary = data.as_dict() + if len(dictionary) == 1: + unit, value = dictionary.popitem() + out = '{}{}'.format(value, TIME_PERIOD_UNIT_MAP[unit]) + return yaml.ScalarNode(tag=u'tag:yaml.org,2002:str', value=out) + return represent_odict(dumper, 'tag:yaml.org,2002:map', dictionary) + + yaml.SafeDumper.add_representer( OrderedDict, lambda dumper, value: @@ -264,4 +284,9 @@ yaml.SafeDumper.add_representer( yaml.SafeDumper.add_representer(unicode, unicode_representer) yaml.SafeDumper.add_representer(HexInt, hex_int_representer) -yaml.SafeDumper.add_representer(IPAddress, ipaddress_representer) +yaml.SafeDumper.add_representer(IPAddress, stringify_representer) +yaml.SafeDumper.add_representer(MACAddress, stringify_representer) +yaml.SafeDumper.add_representer(TimePeriod, represent_time_period) +yaml.SafeDumper.add_representer(TimePeriodMicroseconds, represent_time_period) +yaml.SafeDumper.add_representer(TimePeriodMilliseconds, represent_time_period) +yaml.SafeDumper.add_representer(TimePeriodSeconds, represent_time_period)