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]] # )