esphome-wordclock/components/wordclock/__init__.py

447 lines
16 KiB
Python

import logging
from esphome import core
from esphome.components import display, font, time, light
import esphome.config_validation as cv
import esphome.codegen as cg
import esphome.cpp_generator as cpp
from esphome.const import (
CONF_ID,
CONF_NAME,
CONF_RAW_DATA_ID,
CONF_TYPE,
CONF_TIME_ID,
CONF_SEGMENTS,
CONF_ADDRESSABLE_LIGHT_ID,
CONF_HOUR,
CONF_HOURS,
CONF_MINUTE,
CONF_MINUTES,
)
from esphome.core import CORE, HexInt
_LOGGER = logging.getLogger(__name__)
CONF_DISPLAY_ID = "display_id"
CONF_LINE_START_X = "x1"
CONF_LINE_START_Y = "y1"
CONF_LINE_END_X = "x2"
CONF_LINE_END_Y = "y2"
CONF_LINE = "line"
CONF_WORDCLOCK_STATIC_SEGMENTS = "static_segments"
CONF_HOUR_OFFSET = "hour_offset"
# CONF_HOURS_MIDNIGHT = "0"
# CONF_HOURS_ONE = "1"
# CONF_HOURS_TWO = "2"
# CONF_HOURS_THREE = "3"
# CONF_HOURS_FOUR = "4"
# CONF_HOURS_FIVE = "5"
# CONF_HOURS_SIX = "6"
# CONF_HOURS_SEVEN = "7"
# CONF_HOURS_EIGHT = "8"
# CONF_HOURS_NINE = "9"
# CONF_HOURS_TEN = "10"
# CONF_HOURS_ELEVEN = "11"
# CONF_HOURS_TWELVE = "12"
# CONF_HOURS_THIRTEEN = "13"
# CONF_HOURS_FOURTEEN = "14"
# CONF_HOURS_FIFTEEN = "15"
# CONF_HOURS_SIXTEEN = "16"
# CONF_HOURS_SEVENTEEN = "17"
# CONF_HOURS_EIGHTTEEN = "18"
# CONF_HOURS_NINETEEN = "19"
# CONF_HOURS_TWENTY = "20"
# CONF_HOURS_TWENTYONE = "21"
# CONF_HOURS_TWENTYTWO = "22"
# CONF_HOURS_TWENTYTHREE = "23"
# CONF_HOURS_MIDNIGHT = "midnight"
# CONF_HOURS_ONE = "one"
# CONF_HOURS_TWO = "two"
# CONF_HOURS_THREE = "three"
# CONF_HOURS_FOUR = "four"
# CONF_HOURS_FIVE = "five"
# CONF_HOURS_SIX = "six"
# CONF_HOURS_SEVEN = "seven"
# CONF_HOURS_EIGHT = "eight"
# CONF_HOURS_NINE = "nine"
# CONF_HOURS_TEN = "ten"
# CONF_HOURS_ELEVEN = "eleven"
# CONF_HOURS_TWELVE = "twelve"
# CONF_HOURS_THIRTEEN = "thirteen"
# CONF_HOURS_FOURTEEN = "fourteen"
# CONF_HOURS_FIFTEEN = "fifteen"
# CONF_HOURS_SIXTEEN = "sixteen"
# CONF_HOURS_SEVENTEEN = "seventeen"
# CONF_HOURS_EIGHTTEEN = "eightteen"
# CONF_HOURS_NINETEEN = "nineteen"
# CONF_HOURS_TWENTY = "twenty"
# CONF_HOURS_TWENTYONE = "twentyone"
# CONF_HOURS_TWENTYTWO = "twentytwo"
# CONF_HOURS_TWENTYTHREE = "twentythree"
# CONF_MINUTES_SHARP = "0"
# CONF_MINUTES_FIVE = "5"
# CONF_MINUTES_TEN = "10"
# CONF_MINUTES_FIFTEEN = "15"
# CONF_MINUTES_TWENTY = "20"
# CONF_MINUTES_TWENTYFIVE = "25"
# CONF_MINUTES_THIRTY = "30"
# CONF_MINUTES_THIRTYFIVE = "35"
# CONF_MINUTES_FORTY = "40"
# CONF_MINUTES_FORTYFIVE = "45"
# CONF_MINUTES_FIFTY = "50"
# CONF_MINUTES_FIFTYFIVE = "55"
DEPENDENCIES = ["display", "time"]
MULTI_CONF = False
wordclock_ns = cg.esphome_ns.namespace("wordclock")
SegmentCoords = wordclock_ns.struct("SegmentCoords")
Wordclock = wordclock_ns.class_(
"Wordclock", cg.PollingComponent
)
WORDCLOCK_SEGMENT_SCHEMA = {
cv.Required(CONF_NAME): cv.string_strict,
cv.Required(CONF_LINE): {
cv.Required(CONF_LINE_START_X): cv.int_,
cv.Required(CONF_LINE_START_Y): cv.int_,
cv.Required(CONF_LINE_END_X): cv.int_,
cv.Optional(CONF_LINE_END_Y): cv.int_,
},
}
WORDCLOCK_HOUR_SCHEMA = {
cv.Required(CONF_HOUR): cv.uint16_t,
cv.Required(CONF_SEGMENTS): cv.ensure_list(cv.string_strict),
}
WORDCLOCK_MINUTE_SCHEMA = {
cv.Required(CONF_MINUTE): cv.uint16_t,
cv.Required(CONF_HOUR_OFFSET): cv.int_range(-1,1),
cv.Required(CONF_SEGMENTS): cv.ensure_list(cv.string_strict),
}
# WORDCLOCK_CONFIG_SCHEMA = {
# cv.Optional(CONF_WORDCLOCK_STATIC_TEXT): cv.ensure_list(cv.string_strict),
# # cv.Required(CONF_LAMBDA): cv.lambda_,
# cv.Required(CONF_SEGMENTS): cv.ensure_list(WORDCLOCK_SEGMENT_SCHEMA),
# cv.Required(CONF_MINUTES): cv.ensure_list(WORDCLOCK_MINUTE_SCHEMA),
# cv.Required(CONF_HOURS): cv.ensure_list(WORDCLOCK_HOUR_SCHEMA),
# }
DATA1 = "data1"
DATA_X1 = "data_x1"
DATA_X2 = "data_x2"
DATA_Y1 = "data_y1"
DATA_Y2 = "data_y2"
DATA_SEGMENT_COORDS = "data_segment_coords"
DATA_MINUTES = "data_minutes"
DATA_HOURS = "data_hours"
DATA_SEGMENTS_HOUR = "data_segments_hour"
DATA_SEGMENTS_MINUTE = "data_segments_minute"
DATA3 = "data3"
DATA4 = "data4"
DATA_VECTOR_SEGMENTS_HOUR = "data_vector_segments_hour"
DATA_VECTOR_SEGMENTS_MINUTE = "data_vector_segments_minute"
int8 = cg.global_ns.namespace("int8_t")
uint16_ptr = cg.global_ns.namespace("uint16_t *")
WORDCLOCK_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(Wordclock),
cv.Required(CONF_DISPLAY_ID): cv.use_id(
display.DisplayBuffer
),
cv.Required(CONF_TIME_ID): cv.use_id(
time.RealTimeClock
),
cv.Required(CONF_ADDRESSABLE_LIGHT_ID): cv.use_id(
light.AddressableLightState
),
# cv.Optional(CONF_LAMBDA): cv.lambda_,
cv.Optional(CONF_WORDCLOCK_STATIC_SEGMENTS): cv.ensure_list(cv.string_strict),
# cv.Required(CONF_LAMBDA): cv.lambda_,
cv.Required(CONF_SEGMENTS): cv.ensure_list(WORDCLOCK_SEGMENT_SCHEMA),
cv.Required(CONF_MINUTES): cv.ensure_list(WORDCLOCK_MINUTE_SCHEMA),
cv.Required(CONF_HOURS): cv.ensure_list(WORDCLOCK_HOUR_SCHEMA),
cv.GenerateID(DATA_X1): cv.declare_id(uint16_ptr),
cv.GenerateID(DATA_X2): cv.declare_id(cg.uint8),
cv.GenerateID(DATA_Y1): cv.declare_id(cg.uint8),
cv.GenerateID(DATA_Y2): cv.declare_id(cg.uint8),
cv.GenerateID(DATA_SEGMENT_COORDS): cv.declare_id(SegmentCoords),
# cv.GenerateID(DATA_MINUTES): cv.declare_id(int8),
# cv.GenerateID(DATA_HOURS): cv.declare_id(int8),
# cv.GenerateID(DATA_MINUTES): cv.declare_id(uint16_ptr),
# cv.GenerateID(DATA_HOURS): cv.declare_id(uint16_ptr),
# cv.GenerateID(DATA_SEGMENTS_MINUTE): cv.declare_id(cg.std_vector.template(cg.uint16)),
# cv.GenerateID(DATA_SEGMENTS_HOUR): cv.declare_id(cg.std_vector.template(cg.uint16)),
cv.GenerateID(DATA_VECTOR_SEGMENTS_HOUR): cv.declare_id(cg.std_vector.template(cg.std_vector.template(cg.uint16))),
cv.GenerateID(DATA_VECTOR_SEGMENTS_MINUTE): cv.declare_id(cg.std_vector.template(cg.std_vector.template(cg.uint16))),
# cv.GenerateID(DATA3): cv.declare_id(cg.uint8),
# cv.GenerateID(DATA4): cv.declare_id(cg.std_vector.template(cg.int32)),
# cv.Required(CONF_WORDCLOCK_CONFIG): cv.ensure_list(WORDCLOCK_CONFIG_SCHEMA),
# cv.Required(CONF_ID): cv.declare_id(Wordclock_),
# cv.Required(CONF_FILE): cv.file_,
# cv.Optional(CONF_RESIZE): cv.dimensions,
# cv.Optional(CONF_TYPE, default="BINARY"): cv.enum(IMAGE_TYPE, upper=True),
# cv.Optional(CONF_DITHER, default="NONE"): cv.one_of(
# "NONE", "FLOYDSTEINBERG", upper=True
# ),
# cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
}
)
# CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, WORDCLOCK_SCHEMA)
CONFIG_SCHEMA = WORDCLOCK_SCHEMA
async def to_code(config):
wrapped_display = await cg.get_variable(config[CONF_DISPLAY_ID])
wrapped_time = await cg.get_variable(config[CONF_TIME_ID])
wrapped_light_state = await cg.get_variable(config[CONF_ADDRESSABLE_LIGHT_ID])
var = cg.new_Pvariable(config[CONF_ID], wrapped_time, wrapped_display, wrapped_light_state)
SEGMENT_MAP = dict()
for idx, segm in enumerate(config[CONF_SEGMENTS]):
print(segm[CONF_NAME])
SEGMENT_MAP[segm[CONF_NAME]] = idx
line_start_x = segm[CONF_LINE][CONF_LINE_START_X]
line_end_x = segm[CONF_LINE][CONF_LINE_END_X]
line_start_y = segm[CONF_LINE][CONF_LINE_START_Y]
line_end_y = segm[CONF_LINE].get(CONF_LINE_END_Y, line_start_y)
exp = cg.StructInitializer(
SegmentCoords,
("x1", line_start_x),
("x2", line_end_x),
("y1", line_start_y),
("y2", line_end_y),
)
cg.add(var.add_segment(exp))
if CONF_WORDCLOCK_STATIC_SEGMENTS in config:
# cg.static_const_array(config[DATA_SEGMENT_COORDS], accumulator)
# print(SEGMENT_MAP)
for segment_name in config[CONF_WORDCLOCK_STATIC_SEGMENTS]:
cg.add(var.add_static(SEGMENT_MAP[segment_name]))
# static_segment_ids = config[CONF_WORDCLOCK_STATIC_SEGMENTS]
# exp = cg.std_vector.template(cg.uint16)(static_segment_ids)
# cg.add(var.add_static(hour[CONF_HOUR], cpp.UnaryOpExpression("&", exp)))
hours = []
for idx, hour in enumerate(config[CONF_HOURS]):
segment_ids = [SEGMENT_MAP[a] for a in hour[CONF_SEGMENTS]]
exp = cg.std_vector.template(cg.uint16)(segment_ids)
hours.append(exp)
hours_array = cg.new_variable(config[DATA_VECTOR_SEGMENTS_HOUR], cg.std_vector.template(cg.std_vector.template(cg.uint16))(hours))
minutes = []
for idx, hour in enumerate(config[CONF_MINUTES]):
segment_ids = [SEGMENT_MAP[a] for a in hour[CONF_SEGMENTS]]
exp = cg.std_vector.template(cg.uint16)(segment_ids)
minutes.append(exp)
minutes_array = cg.new_variable(config[DATA_VECTOR_SEGMENTS_MINUTE], cg.std_vector.template(cg.std_vector.template(cg.uint16))(minutes))
for idx, hour in enumerate(config[CONF_HOURS]):
exp = cg.std_vector.template(cg.uint16)(segment_ids)
cg.add(var.add_hour(hour[CONF_HOUR], cpp.UnaryOpExpression("&", hours_array[idx])))
# minutes = [[],] * 60
for idx, minute in enumerate(config[CONF_MINUTES]):
exp = cg.std_vector.template(cg.uint16)(segment_ids)
cg.add(var.add_minute(minute[CONF_MINUTE], cpp.UnaryOpExpression("&", minutes_array[idx])))
# segment_ids = [SEGMENT_MAP[a] for a in minute[CONF_SEGMENTS]]
# minutes[minute[CONF_MINUTE]] = cg.std_vector.template(cg.int32)()
# foo = []
# cg.ArrayInitializer()
'''exp = cg.std_vector.template(cg.uint16)(segment_ids)'''
# exp = cg.ArrayInitializer(minute[CONF_SEGMENTS])
'''cg.add(var.add_minute(minute[CONF_MINUTE], exp))'''
# for segment_str in minute[CONF_SEGMENTS]:
# foo.append(SEGMENT_MAP[segment_str])
# # minutes[minute[CONF_MINUTE]].push_back(SEGMENT_MAP[segment_str])
# minutes[minute[CONF_MINUTE]] = foo
# print(minute[CONF_MINUTE])
# SEGMENT_MAP[i[CONF_NAME]] = idx
# accumulator.append(idx)
for idx, minute in enumerate(config[CONF_MINUTES]):
cg.add(var.add_hour_offset(minute[CONF_MINUTE], minute[CONF_HOUR_OFFSET]))
await cg.register_component(var, config)
"""
accumulator = []
SEGMENT_MAP = dict()
for idx, segm in enumerate(config[CONF_SEGMENTS]):
print(segm[CONF_NAME])
SEGMENT_MAP[segm[CONF_NAME]] = idx
# x1.append()
# x2.append()
line_start_x = segm[CONF_LINE][CONF_LINE_START_X]
line_end_x = segm[CONF_LINE][CONF_LINE_END_X]
line_start_y = segm[CONF_LINE][CONF_LINE_START_Y]
line_end_y = segm[CONF_LINE].get(CONF_LINE_END_Y, line_start_y)
# print(line_start_y)
# print(line_end_y)
# y1.append(segm[CONF_LINE][CONF_LINE_START_Y])
# y2.append(line_end_y)
exp = cg.StructInitializer(
SegmentCoords,
("x1", line_start_x),
("x2", line_end_x),
("y1", line_start_y),
("y2", line_end_y),
)
accumulator.append(exp)
cg.static_const_array(config[DATA_SEGMENT_COORDS], accumulator)
print(SEGMENT_MAP)
minutes = [[],] * 60
for idx, minute in enumerate(config[CONF_MINUTES]):
# minutes[minute[CONF_MINUTE]] = cg.std_vector.template(cg.int32)()
foo = []
cg.ArrayInitializer()
for segment_str in minute[CONF_SEGMENTS]:
foo.append(SEGMENT_MAP[segment_str])
# minutes[minute[CONF_MINUTE]].push_back(SEGMENT_MAP[segment_str])
minutes[minute[CONF_MINUTE]] = foo
print(minute[CONF_MINUTE])
# SEGMENT_MAP[i[CONF_NAME]] = idx
# accumulator.append(idx)
print(minutes)
cg.static_const_array(config[DATA_MINUTES], minutes)
"""
"""
hours = [cg.std_vector.template(cg.uint16)()] * 24
cg.static_const_array(config[DATA_HOURS], hours)
for idx, i in enumerate(config[CONF_HOURS]):
# for segment_str in i[CONF_SEGMENTS]:
# print(config[DATA_HOURS].type)
# push = config[DATA_HOURS].get(i[CONF_HOUR]).push_back(SEGMENT_MAP[segment_str])
# cg.add(push)
# hours[i[CONF_HOUR]] = cg.std_vector.template(cg.int32)()
print(i[CONF_HOUR])
# SEGMENT_MAP[i[CONF_NAME]] = idx
# accumulator.append(idx)
print(hours)
# cg.static_const_array(config[DATA_HOURS], hours)
# cg.static_const_array(config[DATA3], hours)
# cg.static_const_array(config[DATA4], hours)
"""
# prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
# cg.add_global(cg.Statement(f"const int {i[CONF_NAME]} = 0;"))
# cg.add_global(f"""
# /*
# {global_consts}
# */
# """)
# from PIL import Image
# path = CORE.relative_config_path(config[CONF_FILE])
# try:
# image = Image.open(path)
# except Exception as e:
# raise core.EsphomeError(f"Could not load image file {path}: {e}")
# width, height = image.size
# if CONF_RESIZE in config:
# image.thumbnail(config[CONF_RESIZE])
# width, height = image.size
# else:
# if width > 500 or height > 500:
# _LOGGER.warning(
# "The image you requested is very big. Please consider using"
# " the resize parameter."
# )
# dither = Image.NONE if config[CONF_DITHER] == "NONE" else Image.FLOYDSTEINBERG
# if config[CONF_TYPE] == "GRAYSCALE":
# image = image.convert("L", dither=dither)
# pixels = list(image.getdata())
# data = [0 for _ in range(height * width)]
# pos = 0
# for pix in pixels:
# data[pos] = pix
# pos += 1
# elif config[CONF_TYPE] == "RGB24":
# image = image.convert("RGB")
# pixels = list(image.getdata())
# data = [0 for _ in range(height * width * 3)]
# pos = 0
# for pix in pixels:
# data[pos] = pix[0]
# pos += 1
# data[pos] = pix[1]
# pos += 1
# data[pos] = pix[2]
# pos += 1
# elif config[CONF_TYPE] == "RGB565":
# image = image.convert("RGB")
# pixels = list(image.getdata())
# data = [0 for _ in range(height * width * 3)]
# pos = 0
# for pix in pixels:
# R = pix[0] >> 3
# G = pix[1] >> 2
# B = pix[2] >> 3
# rgb = (R << 11) | (G << 5) | B
# data[pos] = rgb >> 8
# pos += 1
# data[pos] = rgb & 255
# pos += 1
# elif (config[CONF_TYPE] == "BINARY") or (config[CONF_TYPE] == "TRANSPARENT_BINARY"):
# image = image.convert("1", dither=dither)
# 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)
# elif config[CONF_TYPE] == "TRANSPARENT_IMAGE":
# image = image.convert("RGBA")
# width8 = ((width + 7) // 8) * 8
# data = [0 for _ in range(height * width8 // 8)]
# for y in range(height):
# for x in range(width):
# if not image.getpixel((x, y))[3]:
# continue
# pos = x + y * width8
# data[pos // 8] |= 0x80 >> (pos % 8)
# rhs = [HexInt(x) for x in data]
# prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
# cg.new_Pvariable(
# config[CONF_ID], prog_arr, width, height, IMAGE_TYPE[config[CONF_TYPE]]
# )