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.components import binary_sensor
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
DEPENDENCIES = ['spi']
@ -14,7 +14,7 @@ PN532Component = binary_sensor.binary_sensor_ns.PN532Component
CONFIG_SCHEMA = vol.All(cv.ensure_list, [vol.Schema({
cv.GenerateID(): cv.declare_variable_id(PN532Component),
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,
})])
@ -25,7 +25,7 @@ def to_code(config):
for spi in get_variable(conf[CONF_SPI_ID]):
yield
cs = None
for cs in gpio_output_pin_expression(conf[CONF_CS]):
for cs in gpio_output_pin_expression(conf[CONF_CS_PIN]):
yield
rhs = App.make_pn532_component(spi, cs, conf.get(CONF_UPDATE_INTERVAL))
Pvariable(conf[CONF_ID], rhs)

View file

@ -4,7 +4,8 @@ import esphomeyaml.config_validation as cv
from esphomeyaml import pins
from esphomeyaml.components import sensor
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
MakeMAX6675Sensor = Application.MakeMAX6675Sensor
@ -12,7 +13,7 @@ MakeMAX6675Sensor = Application.MakeMAX6675Sensor
PLATFORM_SCHEMA = cv.nameable(sensor.SENSOR_PLATFORM_SCHEMA.extend({
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(MakeMAX6675Sensor),
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,
}))
@ -22,7 +23,7 @@ def to_code(config):
for spi in get_variable(config[CONF_SPI_ID]):
yield
cs = None
for cs in gpio_output_pin_expression(config[CONF_CS]):
for cs in gpio_output_pin_expression(config[CONF_CS_PIN]):
yield
rhs = App.make_max6675_sensor(config[CONF_NAME], spi, cs,
config.get(CONF_UPDATE_INTERVAL))

View file

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

View file

@ -220,10 +220,10 @@ CONF_ON_VALUE = 'on_value'
CONF_ON_RAW_VALUE = 'on_raw_value'
CONF_ON_VALUE_RANGE = 'on_value_range'
CONF_ON_MESSAGE = 'on_message'
CONF_CS = 'cs'
CONF_CLK = 'clk'
CONF_MISO = 'miso'
CONF_MOSI = 'mosi'
CONF_CS_PIN = 'cs_pin'
CONF_CLK_PIN = 'clk_pin'
CONF_MISO_PIN = 'miso_pin'
CONF_MOSI_PIN = 'mosi_pin'
CONF_TURN_ON_ACTION = 'turn_on_action'
CONF_TURN_OFF_ACTION = 'turn_off_action'
CONF_OPEN_ACTION = 'open_action'
@ -289,6 +289,25 @@ CONF_ONE = 'one'
CONF_GROUP = 'group'
CONF_DEVICE = 'device'
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 = [
'featheresp32', 'node32s', 'espea32', 'firebeetle32', 'esp32doit-devkit-v1',

View file

@ -10,6 +10,8 @@ class ESPHomeYAMLError(Exception):
class HexInt(long):
def __str__(self):
if 0 <= self <= 255:
return "0x{:02X}".format(self)
return "0x{:X}".format(self)
@ -181,8 +183,8 @@ class TimePeriodSeconds(TimePeriod):
class Lambda(object):
def __init__(self, value):
self.value = 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.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), 3)]
def __str__(self):
return self.value

View file

@ -226,7 +226,7 @@ class LambdaExpression(Expression):
self.return_type = return_type
if return_type is not None:
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])
def __str__(self):
@ -411,7 +411,11 @@ def process_lambda(value, parameters, capture='=', return_type=None):
var = None
for var in get_variable(id):
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)
return
@ -522,6 +526,13 @@ class MockObj(Expression):
obj.requires.append(self)
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):
return self._has_side_effects