447 lines
16 KiB
Python
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]]
|
|
# )
|