This commit is contained in:
Otto Winter 2018-08-18 21:40:59 +02:00
parent 5170a7cdf4
commit 03249780fd
No known key found for this signature in database
GPG Key ID: DB66C0BE6013F97E
18 changed files with 823 additions and 31 deletions

View File

@ -0,0 +1,56 @@
# coding=utf-8
import voluptuous as vol
import esphomeyaml.config_validation as cv
from esphomeyaml.const import CONF_LAMBDA, CONF_ROTATION, CONF_UPDATE_INTERVAL
from esphomeyaml.helpers import add, add_job, esphomelib_ns
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
})
display_ns = esphomelib_ns.namespace('display')
DisplayBuffer = display_ns.DisplayBuffer
DisplayBufferRef = DisplayBuffer.operator('ref')
DISPLAY_ROTATIONS = {
0: display_ns.DISPLAY_ROTATION_0_DEGREES,
90: display_ns.DISPLAY_ROTATION_90_DEGREES,
180: display_ns.DISPLAY_ROTATION_180_DEGREES,
270: display_ns.DISPLAY_ROTATION_270_DEGREES,
}
def validate_rotation(value):
value = cv.string(value)
if value.endswith(u"°"):
value = value[:-1]
try:
value = int(value)
except ValueError:
raise vol.Invalid(u"Expected integer for rotation")
return cv.one_of(*DISPLAY_ROTATIONS)(value)
BASIC_DISPLAY_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds,
})
FULL_DISPLAY_PLATFORM_SCHEMA = BASIC_DISPLAY_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_LAMBDA): cv.lambda_,
vol.Optional(CONF_ROTATION): validate_rotation,
})
def setup_display_core_(display_var, config):
if CONF_UPDATE_INTERVAL in config:
add(display_var.set_update_interval(config[CONF_UPDATE_INTERVAL]))
if CONF_ROTATION in config:
add(display_var.set_rotation(DISPLAY_ROTATIONS[config[CONF_ROTATION]]))
def setup_display(display_var, config):
add_job(setup_display_core_, display_var, config)
BUILD_FLAGS = '-DUSE_DISPLAY'

View File

@ -0,0 +1,74 @@
import voluptuous as vol
import esphomeyaml.config_validation as cv
from esphomeyaml import pins
from esphomeyaml.components import display
from esphomeyaml.const import CONF_DIMENSIONS, CONF_ENABLE_PIN, CONF_ID, CONF_LAMBDA, CONF_PINS, \
CONF_RS_PIN, CONF_RW_PIN
from esphomeyaml.helpers import App, Pvariable, add, gpio_output_pin_expression, process_lambda
GPIOLCDDisplay = display.display_ns.GPIOLCDDisplay
LCDDisplay = display.display_ns.LCDDisplay
LCDDisplayRef = LCDDisplay.operator('ref')
def validate_lcd_dimensions(value):
value = cv.dimensions(value)
if value[0] > 0x40:
raise vol.Invalid("LCD displays can't have more than 64 columns")
if value[1] > 4:
raise vol.Invalid("LCD displays can't have more than 4 rows")
return value
def validate_pin_length(value):
if len(value) != 4 and len(value) != 8:
raise vol.Invalid("LCD Displays can either operate in 4-pin or 8-pin mode,"
"not {}-pin mode".format(len(value)))
return value
PLATFORM_SCHEMA = display.BASIC_DISPLAY_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(GPIOLCDDisplay),
vol.Required(CONF_DIMENSIONS): validate_lcd_dimensions,
vol.Required(CONF_PINS): vol.All([pins.gpio_output_pin_schema], validate_pin_length),
vol.Required(CONF_ENABLE_PIN): pins.gpio_output_pin_schema,
vol.Required(CONF_RS_PIN): pins.gpio_output_pin_schema,
vol.Optional(CONF_RW_PIN): pins.gpio_output_pin_schema,
vol.Optional(CONF_LAMBDA): cv.lambda_,
})
def to_code(config):
rhs = App.make_gpio_lcd_display(config[CONF_DIMENSIONS][0], config[CONF_DIMENSIONS][1])
lcd = Pvariable(config[CONF_ID], rhs)
pins_ = []
for conf in config[CONF_PINS]:
for pin in gpio_output_pin_expression(conf):
yield
pins_.append(pin)
add(lcd.set_data_pins(*pins_))
for enable in gpio_output_pin_expression(config[CONF_ENABLE_PIN]):
yield
add(lcd.set_enable_pin(enable))
for rs in gpio_output_pin_expression(config[CONF_RS_PIN]):
yield
add(lcd.set_rs_pin(rs))
if CONF_RW_PIN in config:
for rw in gpio_output_pin_expression(config[CONF_RW_PIN]):
yield
add(lcd.set_rw_pin(rw))
if CONF_LAMBDA in config:
for lambda_ in process_lambda(config[CONF_LAMBDA], [(LCDDisplayRef, 'it')]):
yield
add(lcd.set_writer(lambda_))
display.setup_display(lcd, config)
BUILD_FLAGS = '-DUSE_LCD_DISPLAY'

View File

@ -0,0 +1,37 @@
import voluptuous as vol
import esphomeyaml.config_validation as cv
from esphomeyaml.components import display
from esphomeyaml.components.display.lcd_gpio import LCDDisplayRef, validate_lcd_dimensions
from esphomeyaml.const import CONF_ADDRESS, CONF_DIMENSIONS, CONF_ID, CONF_LAMBDA
from esphomeyaml.helpers import App, Pvariable, add, process_lambda
DEPENDENCIES = ['i2c']
PCF8574LCDDisplay = display.display_ns.PCF8574LCDDisplay
PLATFORM_SCHEMA = display.BASIC_DISPLAY_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(PCF8574LCDDisplay),
vol.Required(CONF_DIMENSIONS): validate_lcd_dimensions,
vol.Optional(CONF_ADDRESS): cv.i2c_address,
vol.Optional(CONF_LAMBDA): cv.lambda_,
})
def to_code(config):
rhs = App.make_pcf8574_lcd_display(config[CONF_DIMENSIONS][0], config[CONF_DIMENSIONS][1])
lcd = Pvariable(config[CONF_ID], rhs)
if CONF_ADDRESS in config:
add(lcd.set_address(config[CONF_ADDRESS]))
if CONF_LAMBDA in config:
for lambda_ in process_lambda(config[CONF_LAMBDA], [(LCDDisplayRef, 'it')]):
yield
add(lcd.set_writer(lambda_))
display.setup_display(lcd, config)
BUILD_FLAGS = ['-DUSE_LCD_DISPLAY', '-DUSE_LCD_DISPLAY_PCF8574']

View File

@ -0,0 +1,49 @@
import voluptuous as vol
import esphomeyaml.config_validation as cv
from esphomeyaml import pins
from esphomeyaml.components import display
from esphomeyaml.components.spi import SPIComponent
from esphomeyaml.const import CONF_CS_PIN, CONF_ID, CONF_INTENSITY, CONF_LAMBDA, CONF_NUM_CHIPS, \
CONF_SPI_ID
from esphomeyaml.helpers import App, Pvariable, add, get_variable, gpio_output_pin_expression, \
process_lambda
DEPENDENCIES = ['spi']
MAX7219Component = display.display_ns.MAX7219Component
MAX7219ComponentRef = MAX7219Component.operator('ref')
PLATFORM_SCHEMA = display.BASIC_DISPLAY_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(MAX7219Component),
cv.GenerateID(CONF_SPI_ID): cv.use_variable_id(SPIComponent),
vol.Required(CONF_CS_PIN): pins.gpio_output_pin_schema,
vol.Optional(CONF_NUM_CHIPS): vol.All(cv.uint8_t, vol.Range(min=1)),
vol.Optional(CONF_INTENSITY): vol.All(cv.uint8_t, vol.Range(min=0, max=15)),
vol.Optional(CONF_LAMBDA): cv.lambda_,
})
def to_code(config):
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_max7219(spi, cs)
max7219 = Pvariable(config[CONF_ID], rhs)
if CONF_NUM_CHIPS in config:
add(max7219.set_num_chips(config[CONF_NUM_CHIPS]))
if CONF_INTENSITY in config:
add(max7219.set_intensity(config[CONF_INTENSITY]))
if CONF_LAMBDA in config:
for lambda_ in process_lambda(config[CONF_LAMBDA], [(MAX7219ComponentRef, 'it')]):
yield
add(max7219.set_writer(lambda_))
display.setup_display(max7219, config)
BUILD_FLAGS = '-DUSE_MAX7219'

View File

@ -0,0 +1,39 @@
import voluptuous as vol
import esphomeyaml.config_validation as cv
from esphomeyaml import pins
from esphomeyaml.components import display
from esphomeyaml.components.display import ssd1306_spi
from esphomeyaml.const import CONF_ADDRESS, CONF_EXTERNAL_VCC, CONF_ID, CONF_MODEL, CONF_RESET_PIN
from esphomeyaml.helpers import App, Pvariable, add, gpio_output_pin_expression
DEPENDENCIES = ['i2c']
I2CSSD1306 = display.display_ns.I2CSSD1306
PLATFORM_SCHEMA = display.FULL_DISPLAY_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(I2CSSD1306),
vol.Required(CONF_MODEL): cv.one_of(*ssd1306_spi.MODELS),
vol.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
vol.Optional(CONF_EXTERNAL_VCC): cv.boolean,
vol.Optional(CONF_ADDRESS): cv.i2c_address,
})
def to_code(config):
ssd = Pvariable(config[CONF_ID], App.make_i2c_ssd1306())
add(ssd.set_model(ssd1306_spi.MODELS[config[CONF_MODEL]]))
if CONF_RESET_PIN in config:
for reset in gpio_output_pin_expression(config[CONF_RESET_PIN]):
yield
add(ssd.set_reset_pin(reset))
if CONF_EXTERNAL_VCC in config:
add(ssd.set_external_vcc(config[CONF_EXTERNAL_VCC]))
if CONF_ADDRESS in config:
add(ssd.set_address(config[CONF_ADDRESS]))
display.setup_display(ssd, config)
BUILD_FLAGS = '-DUSE_SSD1306'

View File

@ -0,0 +1,57 @@
import voluptuous as vol
import esphomeyaml.config_validation as cv
from esphomeyaml import pins
from esphomeyaml.components import display
from esphomeyaml.components.spi import SPIComponent
from esphomeyaml.const import CONF_CS_PIN, CONF_DC_PIN, CONF_EXTERNAL_VCC, CONF_ID, CONF_MODEL, \
CONF_RESET_PIN, CONF_SPI_ID
from esphomeyaml.helpers import App, Pvariable, add, get_variable, gpio_output_pin_expression
DEPENDENCIES = ['spi']
SPISSD1306 = display.display_ns.SPISSD1306
MODELS = {
'SSD1306_128X32': display.display_ns.SSD1306_MODEL_128_32,
'SSD1306_128X64': display.display_ns.SSD1306_MODEL_128_64,
'SSD1306_96X16': display.display_ns.SSD1306_MODEL_96_16,
'SH1106_128X32': display.display_ns.SH1106_MODEL_128_32,
'SH1106_128X64': display.display_ns.SH1106_MODEL_128_64,
'SH1106_96X16': display.display_ns.SH1106_MODEL_96_16,
}
PLATFORM_SCHEMA = display.FULL_DISPLAY_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(SPISSD1306),
cv.GenerateID(CONF_SPI_ID): cv.use_variable_id(SPIComponent),
vol.Required(CONF_CS_PIN): pins.gpio_output_pin_schema,
vol.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
vol.Required(CONF_MODEL): cv.one_of(*MODELS),
vol.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
vol.Optional(CONF_EXTERNAL_VCC): cv.boolean,
})
def to_code(config):
for spi in get_variable(config[CONF_SPI_ID]):
yield
for cs in gpio_output_pin_expression(config[CONF_CS_PIN]):
yield
for dc in gpio_output_pin_expression(config[CONF_DC_PIN]):
yield
rhs = App.make_spi_ssd1306(spi, cs, dc)
ssd = Pvariable(config[CONF_ID], rhs)
add(ssd.set_model(MODELS[config[CONF_MODEL]]))
if CONF_RESET_PIN in config:
for reset in gpio_output_pin_expression(config[CONF_RESET_PIN]):
yield
add(ssd.set_reset_pin(reset))
if CONF_EXTERNAL_VCC in config:
add(ssd.set_external_vcc(config[CONF_EXTERNAL_VCC]))
display.setup_display(ssd, config)
BUILD_FLAGS = '-DUSE_SSD1306'

View File

@ -0,0 +1,84 @@
import voluptuous as vol
import esphomeyaml.config_validation as cv
from esphomeyaml import pins
from esphomeyaml.components import display
from esphomeyaml.components.spi import SPIComponent
from esphomeyaml.const import CONF_BUSY_PIN, CONF_CS_PIN, CONF_DC_PIN, CONF_FULL_UPDATE_EVERY, \
CONF_ID, CONF_LAMBDA, CONF_MODEL, CONF_RESET_PIN, CONF_SPI_ID
from esphomeyaml.helpers import App, Pvariable, add, get_variable, gpio_input_pin_expression, \
gpio_output_pin_expression, process_lambda
DEPENDENCIES = ['spi']
WaveshareEPaperTypeA = display.display_ns.WaveshareEPaperTypeA
WaveshareEPaper = display.display_ns.WaveshareEPaper
MODELS = {
'1.54in': ('a', display.display_ns.WAVESHARE_EPAPER_1_54_IN),
'2.13in': ('a', display.display_ns.WAVESHARE_EPAPER_2_13_IN),
'2.90in': ('a', display.display_ns.WAVESHARE_EPAPER_2_9_IN),
'2.70in': ('b', display.display_ns.WAVESHARE_EPAPER_2_7_IN),
'4.20in': ('b', display.display_ns.WAVESHARE_EPAPER_4_2_IN),
'7.50in': ('b', display.display_ns.WAVESHARE_EPAPER_7_5_IN),
}
def validate_full_update_every_only_type_a(value):
if CONF_FULL_UPDATE_EVERY not in value:
return value
if MODELS[value[CONF_MODEL]][0] != 'a':
raise vol.Invalid("The 'full_update_every' option is only available for models "
"'1.54in', '2.13in' and '2.90in'.")
return value
PLATFORM_SCHEMA = vol.All(display.FULL_DISPLAY_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(None),
cv.GenerateID(CONF_SPI_ID): cv.use_variable_id(SPIComponent),
vol.Required(CONF_CS_PIN): pins.gpio_output_pin_schema,
vol.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
vol.Required(CONF_MODEL): cv.one_of(*MODELS),
vol.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
vol.Optional(CONF_BUSY_PIN): pins.gpio_input_pin_schema,
vol.Optional(CONF_FULL_UPDATE_EVERY): cv.uint32_t,
}), validate_full_update_every_only_type_a)
def to_code(config):
for spi in get_variable(config[CONF_SPI_ID]):
yield
for cs in gpio_output_pin_expression(config[CONF_CS_PIN]):
yield
for dc in gpio_output_pin_expression(config[CONF_DC_PIN]):
yield
model_type, model = MODELS[config[CONF_MODEL]]
if model_type == 'a':
rhs = App.make_waveshare_epaper_type_a(spi, cs, dc, model)
epaper = Pvariable(config[CONF_ID], rhs, type=WaveshareEPaperTypeA)
elif model_type == 'b':
rhs = App.make_waveshare_epaper_type_b(spi, cs, dc, model)
epaper = Pvariable(config[CONF_ID], rhs, type=WaveshareEPaper)
else:
raise NotImplementedError()
if CONF_LAMBDA in config:
for lambda_ in process_lambda(config[CONF_LAMBDA], [(display.DisplayBufferRef, 'it')]):
yield
add(epaper.set_writer(lambda_))
if CONF_RESET_PIN in config:
for reset in gpio_output_pin_expression(config[CONF_RESET_PIN]):
yield
add(epaper.set_reset_pin(reset))
if CONF_BUSY_PIN in config:
for reset in gpio_input_pin_expression(config[CONF_BUSY_PIN]):
yield
add(epaper.set_busy_pin(reset))
if CONF_FULL_UPDATE_EVERY in config:
add(epaper.set_full_update_every(config[CONF_FULL_UPDATE_EVERY]))
display.setup_display(epaper, config)
BUILD_FLAGS = '-DUSE_WAVESHARE_EPAPER'

View File

@ -0,0 +1,126 @@
# coding=utf-8
import os.path
import voluptuous as vol
import esphomeyaml.config_validation as cv
from esphomeyaml import core
from esphomeyaml.components import display
from esphomeyaml.const import CONF_FILE, CONF_GLYPHS, CONF_ID, CONF_SIZE
from esphomeyaml.core import HexInt
from esphomeyaml.helpers import App, ArrayInitializer, MockObj, Pvariable, RawExpression, add
DEPENDENCIES = ['display']
Font = display.display_ns.Font
Glyph = display.display_ns.Glyph
def validate_glyphs(value):
if isinstance(value, list):
value = vol.Schema([cv.string])(value)
value = vol.Schema([cv.string])(list(value))
def comparator(x, y):
x_ = x.encode('utf-8')
y_ = y.encode('utf-8')
for c in range(min(len(x_), len(y_))):
if x_[c] < y_[c]:
return -1
if x_[c] > y_[c]:
return 1
if len(x_) < len(y_):
return -1
elif len(x_) > len(y_):
return 1
else:
raise vol.Invalid(u"Found duplicate glyph {}".format(x))
value.sort(cmp=comparator)
return value
def validate_pillow_installed(value):
try:
import PIL
except ImportError:
raise vol.Invalid("Please install the pillow python package to use fonts. "
"(pip2 install pillow)")
if PIL.__version__[0] < '4':
raise vol.Invalid("Please update your pillow installation to at least 4.0.x. "
"(pip2 install -U pillow)")
return value
def validate_truetype_file(value):
value = cv.string(value)
path = os.path.join(core.CONFIG_PATH, value)
if not os.path.isfile(path):
raise vol.Invalid(u"Could not find file '{}'. Please make sure it exists.".format(path))
if value.endswith('.zip'): # for Google Fonts downloads
raise vol.Invalid(u"Please unzip the font archive '{}' first and then use the .ttf files "
u"inside.".format(value))
if not value.endswith('.ttf'):
raise vol.Invalid(u"Only truetype (.ttf) files are supported. Please make sure you're "
u"using the correct format or rename the extension to .ttf")
return value
DEFAULT_GLYPHS = u' !"%()+,-.:0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°'
CONF_RAW_DATA_ID = 'raw_data_id'
FONT_SCHEMA = vol.Schema({
vol.Required(CONF_ID): cv.declare_variable_id(Font),
vol.Required(CONF_FILE): validate_truetype_file,
vol.Optional(CONF_GLYPHS, default=DEFAULT_GLYPHS): validate_glyphs,
vol.Optional(CONF_SIZE, default=12): vol.All(cv.int_, vol.Range(min=1)),
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_variable_id(None),
})
CONFIG_SCHEMA = vol.All(validate_pillow_installed, cv.ensure_list, [FONT_SCHEMA])
def to_code(config):
from PIL import ImageFont
for conf in config:
path = os.path.join(core.CONFIG_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))
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)]
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))))
glyphs = []
for glyph in conf[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)

View File

@ -0,0 +1,85 @@
# coding=utf-8
import logging
import os.path
import voluptuous as vol
import esphomeyaml.config_validation as cv
from esphomeyaml import core
from esphomeyaml.components import display
from esphomeyaml.const import CONF_FILE, CONF_ID, CONF_RESIZE
from esphomeyaml.core import HexInt
from esphomeyaml.helpers import App, ArrayInitializer, MockObj, Pvariable, RawExpression, add
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['display']
Image_ = display.display_ns.Image
def validate_pillow_installed(value):
try:
# pylint: disable=unused-variable
import PIL
except ImportError:
raise vol.Invalid("Please install the pillow python package to use images. "
"(pip2 install pillow)")
return value
def validate_image_file(value):
value = cv.string(value)
path = os.path.join(core.CONFIG_PATH, value)
if not os.path.isfile(path):
raise vol.Invalid(u"Could not find file '{}'. Please make sure it exists.".format(path))
return value
CONF_RAW_DATA_ID = 'raw_data_id'
FONT_SCHEMA = vol.Schema({
vol.Required(CONF_ID): cv.declare_variable_id(Image_),
vol.Required(CONF_FILE): validate_image_file,
vol.Optional(CONF_RESIZE): cv.dimensions,
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_variable_id(None),
})
CONFIG_SCHEMA = vol.All(validate_pillow_installed, cv.ensure_list, [FONT_SCHEMA])
def to_code(config):
from PIL import Image
for conf in config:
path = os.path.join(core.CONFIG_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))
if CONF_RESIZE in conf:
image.thumbnail(conf[CONF_RESIZE])
image = image.convert('1')
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))))
rhs = App.make_image(raw_data, width, height)
Pvariable(conf[CONF_ID], rhs)

View File

@ -4,7 +4,7 @@ import esphomeyaml.config_validation as cv
from esphomeyaml import pins from esphomeyaml import pins
from esphomeyaml.components import binary_sensor from esphomeyaml.components import binary_sensor
from esphomeyaml.components.spi import SPIComponent from esphomeyaml.components.spi import SPIComponent
from esphomeyaml.const import CONF_CS, CONF_ID, CONF_SPI_ID, CONF_UPDATE_INTERVAL from esphomeyaml.const import CONF_CS_PIN, CONF_ID, CONF_SPI_ID, CONF_UPDATE_INTERVAL
from esphomeyaml.helpers import App, Pvariable, get_variable, gpio_output_pin_expression from esphomeyaml.helpers import App, Pvariable, get_variable, gpio_output_pin_expression
DEPENDENCIES = ['spi'] DEPENDENCIES = ['spi']
@ -14,7 +14,7 @@ PN532Component = binary_sensor.binary_sensor_ns.PN532Component
CONFIG_SCHEMA = vol.All(cv.ensure_list, [vol.Schema({ CONFIG_SCHEMA = vol.All(cv.ensure_list, [vol.Schema({
cv.GenerateID(): cv.declare_variable_id(PN532Component), cv.GenerateID(): cv.declare_variable_id(PN532Component),
cv.GenerateID(CONF_SPI_ID): cv.use_variable_id(SPIComponent), cv.GenerateID(CONF_SPI_ID): cv.use_variable_id(SPIComponent),
vol.Required(CONF_CS): pins.gpio_output_pin_schema, vol.Required(CONF_CS_PIN): pins.gpio_output_pin_schema,
vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds, vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds,
})]) })])
@ -25,7 +25,7 @@ def to_code(config):
for spi in get_variable(conf[CONF_SPI_ID]): for spi in get_variable(conf[CONF_SPI_ID]):
yield yield
cs = None cs = None
for cs in gpio_output_pin_expression(conf[CONF_CS]): for cs in gpio_output_pin_expression(conf[CONF_CS_PIN]):
yield yield
rhs = App.make_pn532_component(spi, cs, conf.get(CONF_UPDATE_INTERVAL)) rhs = App.make_pn532_component(spi, cs, conf.get(CONF_UPDATE_INTERVAL))
Pvariable(conf[CONF_ID], rhs) Pvariable(conf[CONF_ID], rhs)

View File

@ -4,7 +4,8 @@ import esphomeyaml.config_validation as cv
from esphomeyaml import pins from esphomeyaml import pins
from esphomeyaml.components import sensor from esphomeyaml.components import sensor
from esphomeyaml.components.spi import SPIComponent from esphomeyaml.components.spi import SPIComponent
from esphomeyaml.const import CONF_CS, CONF_MAKE_ID, CONF_NAME, CONF_SPI_ID, CONF_UPDATE_INTERVAL from esphomeyaml.const import CONF_CS_PIN, CONF_MAKE_ID, CONF_NAME, CONF_SPI_ID, \
CONF_UPDATE_INTERVAL
from esphomeyaml.helpers import App, Application, get_variable, gpio_output_pin_expression, variable from esphomeyaml.helpers import App, Application, get_variable, gpio_output_pin_expression, variable
MakeMAX6675Sensor = Application.MakeMAX6675Sensor MakeMAX6675Sensor = Application.MakeMAX6675Sensor
@ -12,7 +13,7 @@ MakeMAX6675Sensor = Application.MakeMAX6675Sensor
PLATFORM_SCHEMA = cv.nameable(sensor.SENSOR_PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = cv.nameable(sensor.SENSOR_PLATFORM_SCHEMA.extend({
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(MakeMAX6675Sensor), cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(MakeMAX6675Sensor),
cv.GenerateID(CONF_SPI_ID): cv.use_variable_id(SPIComponent), cv.GenerateID(CONF_SPI_ID): cv.use_variable_id(SPIComponent),
vol.Required(CONF_CS): pins.gpio_output_pin_schema, vol.Required(CONF_CS_PIN): pins.gpio_output_pin_schema,
vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds, vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds,
})) }))
@ -22,7 +23,7 @@ def to_code(config):
for spi in get_variable(config[CONF_SPI_ID]): for spi in get_variable(config[CONF_SPI_ID]):
yield yield
cs = None cs = None
for cs in gpio_output_pin_expression(config[CONF_CS]): for cs in gpio_output_pin_expression(config[CONF_CS_PIN]):
yield yield
rhs = App.make_max6675_sensor(config[CONF_NAME], spi, cs, rhs = App.make_max6675_sensor(config[CONF_NAME], spi, cs,
config.get(CONF_UPDATE_INTERVAL)) config.get(CONF_UPDATE_INTERVAL))

View File

@ -2,18 +2,18 @@ import voluptuous as vol
import esphomeyaml.config_validation as cv import esphomeyaml.config_validation as cv
from esphomeyaml import pins from esphomeyaml import pins
from esphomeyaml.const import CONF_CLK, CONF_ID, CONF_MISO, CONF_MOSI from esphomeyaml.const import CONF_CLK_PIN, CONF_ID, CONF_MISO_PIN, CONF_MOSI_PIN
from esphomeyaml.helpers import App, Pvariable, esphomelib_ns, gpio_input_pin_expression, \ from esphomeyaml.helpers import App, Pvariable, esphomelib_ns, gpio_input_pin_expression, \
gpio_output_pin_expression gpio_output_pin_expression, add
SPIComponent = esphomelib_ns.SPIComponent SPIComponent = esphomelib_ns.SPIComponent
SPI_SCHEMA = vol.Schema({ SPI_SCHEMA = vol.All(vol.Schema({
cv.GenerateID(): cv.declare_variable_id(SPIComponent), cv.GenerateID(): cv.declare_variable_id(SPIComponent),
vol.Required(CONF_CLK): pins.gpio_output_pin_schema, vol.Required(CONF_CLK_PIN): pins.gpio_output_pin_schema,
vol.Required(CONF_MISO): pins.gpio_input_pin_schema, vol.Optional(CONF_MISO_PIN): pins.gpio_input_pin_schema,
vol.Optional(CONF_MOSI): pins.gpio_output_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]) CONFIG_SCHEMA = vol.All(cv.ensure_list, [SPI_SCHEMA])
@ -21,17 +21,18 @@ CONFIG_SCHEMA = vol.All(cv.ensure_list, [SPI_SCHEMA])
def to_code(config): def to_code(config):
for conf in config: for conf in config:
clk = None clk = None
for clk in gpio_output_pin_expression(conf[CONF_CLK]): for clk in gpio_output_pin_expression(conf[CONF_CLK_PIN]):
yield yield
miso = None rhs = App.init_spi(clk)
for miso in gpio_input_pin_expression(conf[CONF_MISO]): spi = Pvariable(conf[CONF_ID], rhs)
yield if CONF_MISO_PIN in conf:
mosi = None for miso in gpio_input_pin_expression(conf[CONF_MISO_PIN]):
if CONF_MOSI in conf:
for mosi in gpio_output_pin_expression(conf[CONF_MOSI]):
yield yield
rhs = App.init_spi(clk, miso, mosi) add(spi.set_miso(miso))
Pvariable(conf[CONF_ID], rhs) if CONF_MOSI_PIN in conf:
for mosi in gpio_input_pin_expression(conf[CONF_MOSI_PIN]):
yield
add(spi.set_mosi(mosi))
BUILD_FLAGS = '-DUSE_SPI' BUILD_FLAGS = '-DUSE_SPI'

View File

@ -0,0 +1,109 @@
import datetime
import logging
import math
import voluptuous as vol
import esphomeyaml.config_validation as cv
from esphomeyaml.const import CONF_TIMEZONE
from esphomeyaml.helpers import add, add_job, esphomelib_ns
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
})
time_ns = esphomelib_ns.namespace('time')
def _tz_timedelta(td):
offset_hour = int(td.total_seconds() / (60 * 60))
offset_minute = int(abs(td.total_seconds() / 60)) % 60
offset_second = int(abs(td.total_seconds())) % 60
if offset_hour == 0 and offset_minute == 0 and offset_second == 0:
return '0'
elif offset_minute == 0 and offset_second == 0:
return '{}'.format(offset_hour)
elif offset_second == 0:
return '{}:{}'.format(offset_hour, offset_minute)
return '{}:{}:{}'.format(offset_hour, offset_minute, offset_second)
# https://stackoverflow.com/a/16804556/8924614
def _week_of_month(dt):
first_day = dt.replace(day=1)
dom = dt.day
adjusted_dom = dom + first_day.weekday()
return int(math.ceil(adjusted_dom / 7.0))
def _tz_dst_str(dt):
td = datetime.timedelta(hours=dt.hour, minutes=dt.minute, seconds=dt.second)
return 'M{}.{}.{}/{}'.format(dt.month, _week_of_month(dt), dt.isoweekday() % 7,
_tz_timedelta(td))
def detect_tz():
try:
import tzlocal
except ImportError:
raise vol.Invalid("No timezone specified and 'tzlocal' not installed. To automatically "
"detect the timezone please install tzlocal (pip2 install tzlocal)")
tz = tzlocal.get_localzone()
dst_begins = None
dst_tzname = None
dst_utcoffset = None
dst_ends = None
norm_tzname = None
norm_utcoffset = None
hour = datetime.timedelta(hours=1)
this_year = datetime.datetime.now().year
dt = datetime.datetime(year=this_year, month=1, day=1)
last_dst = None
while dt.year == this_year:
current_dst = tz.dst(dt, is_dst=not last_dst)
is_dst = bool(current_dst)
if is_dst != last_dst:
if is_dst:
dst_begins = dt
dst_tzname = tz.tzname(dt, is_dst=True)
dst_utcoffset = tz.utcoffset(dt, is_dst=True)
else:
dst_ends = dt + hour
norm_tzname = tz.tzname(dt, is_dst=False)
norm_utcoffset = tz.utcoffset(dt, is_dst=False)
last_dst = is_dst
dt += hour
tzbase = '{}{}'.format(norm_tzname, _tz_timedelta(-1 * norm_utcoffset))
if dst_begins is None:
# No DST in this timezone
_LOGGER.info("Auto-detected timezone '%s' with UTC offset %s",
norm_tzname, _tz_timedelta(norm_utcoffset))
return tzbase
tzext = '{}{},{},{}'.format(dst_tzname, _tz_timedelta(-1 * dst_utcoffset),
_tz_dst_str(dst_begins), _tz_dst_str(dst_ends))
_LOGGER.info("Auto-detected timezone '%s' with UTC offset %s and daylight savings time from "
"%s to %s",
norm_tzname, _tz_timedelta(norm_utcoffset), dst_begins.strftime("%x %X"),
dst_ends.strftime("%x %X"))
return tzbase + tzext
TIME_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_TIMEZONE, default=detect_tz): cv.string,
})
def setup_time_core_(time_var, config):
add(time_var.set_timezone(config[CONF_TIMEZONE]))
def setup_time(time_var, config):
add_job(setup_time_core_, time_var, config)
BUILD_FLAGS = '-DUSE_TIME'

View File

@ -0,0 +1,24 @@
import voluptuous as vol
import esphomeyaml.config_validation as cv
from esphomeyaml.components import time as time_
from esphomeyaml.const import CONF_ID, CONF_LAMBDA, CONF_SERVERS
from esphomeyaml.helpers import App, Pvariable
SNTPComponent = time_.time_ns.SNTPComponent
PLATFORM_SCHEMA = time_.TIME_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(SNTPComponent),
vol.Optional(CONF_SERVERS): vol.All(cv.ensure_list, [cv.string], vol.Length(max=3)),
vol.Optional(CONF_LAMBDA): cv.lambda_,
})
def to_code(config):
rhs = App.make_sntp_component(*config.get(CONF_SERVERS, []))
sntp = Pvariable(config[CONF_ID], rhs)
time_.setup_time(sntp, config)
BUILD_FLAGS = '-DUSE_SNTP_COMPONENT'

View File

@ -102,7 +102,7 @@ def boolean(value):
def ensure_list(value): def ensure_list(value):
"""Wrap value in list if it is not one.""" """Wrap value in list if it is not one."""
if value is None: if value is None or (isinstance(value, dict) and not value):
return [] return []
if isinstance(value, list): if isinstance(value, list):
return value return value
@ -566,6 +566,24 @@ def lambda_(value):
return Lambda(string_strict(value)) return Lambda(string_strict(value))
def dimensions(value):
if isinstance(value, list):
if len(value) != 2:
raise vol.Invalid(u"Dimensions must have a length of two, not {}".format(len(value)))
try:
width, height = int(value[0]), int(value[1])
except ValueError:
raise vol.Invalid(u"Width and height dimensions must be integers")
if width <= 0 or height <= 0:
raise vol.Invalid(u"Width and height must at least be 1")
return [width, height]
value = string(value)
match = re.match(r"\s*([0-9]+)\s*[xX]\s*([0-9]+)\s*", value)
if not match:
raise vol.Invalid(u"Invalid value '{}' for dimensions. Only WIDTHxHEIGHT is allowed.")
return dimensions([match.group(1), match.group(2)])
REGISTERED_IDS = set() REGISTERED_IDS = set()

View File

@ -220,10 +220,10 @@ CONF_ON_VALUE = 'on_value'
CONF_ON_RAW_VALUE = 'on_raw_value' CONF_ON_RAW_VALUE = 'on_raw_value'
CONF_ON_VALUE_RANGE = 'on_value_range' CONF_ON_VALUE_RANGE = 'on_value_range'
CONF_ON_MESSAGE = 'on_message' CONF_ON_MESSAGE = 'on_message'
CONF_CS = 'cs' CONF_CS_PIN = 'cs_pin'
CONF_CLK = 'clk' CONF_CLK_PIN = 'clk_pin'
CONF_MISO = 'miso' CONF_MISO_PIN = 'miso_pin'
CONF_MOSI = 'mosi' CONF_MOSI_PIN = 'mosi_pin'
CONF_TURN_ON_ACTION = 'turn_on_action' CONF_TURN_ON_ACTION = 'turn_on_action'
CONF_TURN_OFF_ACTION = 'turn_off_action' CONF_TURN_OFF_ACTION = 'turn_off_action'
CONF_OPEN_ACTION = 'open_action' CONF_OPEN_ACTION = 'open_action'
@ -289,6 +289,25 @@ CONF_ONE = 'one'
CONF_GROUP = 'group' CONF_GROUP = 'group'
CONF_DEVICE = 'device' CONF_DEVICE = 'device'
CONF_FAMILY = 'family' CONF_FAMILY = 'family'
CONF_FILE = 'file'
CONF_GLYPHS = 'glyphs'
CONF_SIZE = 'size'
CONF_RESIZE = 'resize'
CONF_ROTATION = 'rotation'
CONF_DC_PIN = 'dc_pin'
CONF_RESET_PIN = 'reset_pin'
CONF_BUSY_PIN = 'busy_pin'
CONF_FULL_UPDATE_EVERY = 'full_update_every'
CONF_PINS = 'pins'
CONF_ENABLE_PIN = 'enable_pin'
CONF_RS_PIN = 'rs_pin'
CONF_RW_PIN = 'rw_pin'
CONF_DIMENSIONS = 'dimensions'
CONF_NUM_CHIPS = 'num_chips'
CONF_INTENSITY = 'intensity'
CONF_EXTERNAL_VCC = 'external_vcc'
CONF_TIMEZONE = 'timezone'
CONF_SERVERS = 'servers'
ESP32_BOARDS = [ ESP32_BOARDS = [
'featheresp32', 'node32s', 'espea32', 'firebeetle32', 'esp32doit-devkit-v1', 'featheresp32', 'node32s', 'espea32', 'firebeetle32', 'esp32doit-devkit-v1',

View File

@ -10,6 +10,8 @@ class ESPHomeYAMLError(Exception):
class HexInt(long): class HexInt(long):
def __str__(self): def __str__(self):
if 0 <= self <= 255:
return "0x{:02X}".format(self)
return "0x{:X}".format(self) return "0x{:X}".format(self)
@ -181,8 +183,8 @@ class TimePeriodSeconds(TimePeriod):
class Lambda(object): class Lambda(object):
def __init__(self, value): def __init__(self, value):
self.value = value self.value = value
self.parts = re.split(r'id\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)\.', value) self.parts = re.split(r'id\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)(\.?)', value)
self.requires_ids = [ID(self.parts[i]) for i in range(1, len(self.parts), 2)] self.requires_ids = [ID(self.parts[i]) for i in range(1, len(self.parts), 3)]
def __str__(self): def __str__(self):
return self.value return self.value

View File

@ -226,7 +226,7 @@ class LambdaExpression(Expression):
self.return_type = return_type self.return_type = return_type
if return_type is not None: if return_type is not None:
self.requires.append(return_type) self.requires.append(return_type)
for i in range(1, len(parts), 2): for i in range(1, len(parts), 3):
self.requires.append(parts[i]) self.requires.append(parts[i])
def __str__(self): def __str__(self):
@ -411,7 +411,11 @@ def process_lambda(value, parameters, capture='=', return_type=None):
var = None var = None
for var in get_variable(id): for var in get_variable(id):
yield yield
parts[i*2 + 1] = var._ if parts[i * 3 + 2] == '.':
parts[i * 3 + 1] = var._
else:
parts[i * 3 + 1] = var
parts[i * 3 + 2] = ''
yield LambdaExpression(parts, parameters, capture, return_type) yield LambdaExpression(parts, parameters, capture, return_type)
return return
@ -522,6 +526,13 @@ class MockObj(Expression):
obj.requires.append(self) obj.requires.append(self)
return obj return obj
def operator(self, name):
if name == 'ref':
obj = MockObj(u'{} &'.format(self.base), u'')
obj.requires.append(self)
return obj
raise NotImplementedError()
def has_side_effects(self): def has_side_effects(self):
return self._has_side_effects return self._has_side_effects