diff --git a/.gitignore b/.gitignore index 844caea..08f0921 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ /secrets.yaml /secrets.*.yaml !/secrets.example.yaml +/.pio +/.vscode +__pycache__ diff --git a/components/wordclock/__init__.py b/components/wordclock/__init__.py new file mode 100644 index 0000000..3192cf8 --- /dev/null +++ b/components/wordclock/__init__.py @@ -0,0 +1,447 @@ +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]] + # ) diff --git a/components/wordclock/wordclock.cpp b/components/wordclock/wordclock.cpp new file mode 100644 index 0000000..e40a452 --- /dev/null +++ b/components/wordclock/wordclock.cpp @@ -0,0 +1,305 @@ +#include "wordclock.h" + +namespace esphome { +namespace wordclock { + +void Wordclock::setup() { + // this->time->update(); + + + // this->light_state->add_effects({this->randomTwinkle}); + // this->start_idle_animation(); +} + +void Wordclock::start_idle_animation() { + this->display->set_enabled(false); + // auto call = + this->light_state->turn_on().set_effect("random_twinkle").perform(); + // call.set_effect("random_twinkle"); + // call.perform(); +} + +void Wordclock::end_idle_animation() { + this->light_state->turn_off().perform(); + this->display->set_enabled(true); + + + // this->light->clear_effect_data(); + // this->display->get_light()->set_effect_active(false); + // auto call1 = this->light_state->turn_on(); + // call1.set_effect("None"); + // call1.perform(); + // this->light->all().set(Color(0xF0FF00)); + + // this->display->fill(Color(0)); + + // this->light_state->turn_off().perform(); + // call2.perform(); +} + +void Wordclock::update() { + + // esphome::addressable_light::AddressableLightDisplay it = *(this->display); + // ESP_LOGD("loop", "beep"); + // ESP_LOGD("loop", "time is now [%02i:%02i:%02i]", this->time->now().hour, this->time->now().minute, this->time->now().second); + // ESP_LOGE("wordclock.cpp", "display_ptr: 0x%x", it); + // ESP_LOGE("wordclock.cpp", "this: 0x%x", (this)); + // ESP_LOGE("wordclock.cpp", "this->display: 0x%x", (this->display)); + // ESP_LOGE("wordclock.cpp", "&this->display: 0x%x", &(this->display)); + // ESP_LOGE("loop", "time_ptr: %i", this->time); + // it.line(0,0,0,0, Color(0x00FF00)); + + esphome::time::ESPTime time = this->time->now(); + this->find_hour(9); + this->find_minute(31); + + if (time.is_valid() == false) { + if (this->valid_time) { + ESP_LOGD("loop", "time is not valid [%02i:%02i:%02i]", time.hour, time.minute, time.second); + this->start_idle_animation(); + this->valid_time = false; + return; + } + } + + else { + if (!this->valid_time) { + this->end_idle_animation(); + this->valid_time = true; + ESP_LOGD("wordclock.cpp", "time is now valid [%02i:%02i:%02i]", time.hour, time.minute, time.second); + return; + } + // for (uint8_t idx = 0;idx < ; idx++) { + + // } + + // std::vector *minute = this->find_minute(time.minute); + // std::vector *hour = this->find_hour(time.hour); + this->display->fill(Color(0x000000)); + + int8_t minute = this->find_minute(time.minute); + int8_t hour = this->find_hour((time.hour + this->hour_offsets->at(minute)) % 24); + + for (uint16_t segment_idx : *this->static_segments){ + this->draw_segment(segment_idx); + } + + for (uint16_t segment_idx : this->minutes->at(minute)){ + this->draw_segment(segment_idx); + } + + for (uint16_t segment_idx : this->hours->at(hour)){ + this->draw_segment(segment_idx); + } + // this->draw_segment(0); + // this->draw_segment(1); + // this->draw_segment(4); + // this->draw_segment(10); + // this->draw_segment(19); + // this->light->range(0, 10).fade_to_white(100); + // this->display->get_light()->range(18, 35).set(Color(0xf0ff0f)); + + // SegmentCoords s = this->segments->at(time); + + + // ESP_LOGD("wordclock.cpp", "time is now [%02i:%02i:%02i]", time.hour, time.minute, time.second); + // ESP_LOGD("wordclock.cpp", "x1: %i, y1: %i, x2: %i, y2: %i", s.x1, s.y1, s.x2, s.y2); + + + // this->display->draw_pixel_at(0, 0, Color(0xFF0000)); + // this->display->draw_pixel_at(1, 0, Color(0x00FF00)); + // this->display->draw_pixel_at(2, 0, Color(0x0000FF)); + // if (time.second != second) { + // second = time.second; + // ESP_LOGD("loop", "time is now [%02i:%02i:%02i]", time.hour, time.minute, time.second); + // } + // if(time.hour != hour || time.minute != minute) { + // hour = time.hour; + // minute = time.minute; + // if (hour >= 0 && time.is_valid() == true){ + + // display_time(time.hour, time.minute, CRGB(red, green, blue)); + + // ESP_LOGE("loop", "Update Time: %i:%i Brightness: %i RGB: %i-%i-%i", time.hour, time.minute, brightness, red, green, blue); + // } + // } + // } + // if (!this->reading_ && !mode_funcs_.empty()) { + // this->reading_ = true; + // this->read_mode_(0); + } +} + +void Wordclock::draw_segment(uint16_t segment_id) { + SegmentCoords s = this->segments->at(segment_id); + // ESP_LOGD("wordclock.cpp", "x1: %i, y1: %i, x2: %i, y2: %i", s.x1, s.y1, s.x2, s.y2); + this->display->line(s.x1, s.y1, s.x2, s.y2, esphome::Color(0xFFFFFF)); +} +// void Wordclock::set_writer(display_writer_t &&writer) { +// this->writer_ = writer; +// } + +void Wordclock::add_segment(SegmentCoords segment) { + // if (!this->segments) { + + // } + + this->segments->push_back(segment); + // this->writer_ = writer; +} + +// std::vector * Wordclock::find_hour(uint8_t hour) { +// std::vector *empty_vector; // = new std::vector(); +// uint16_t last_defined_hour = -1; +// for (int i = 0; i < this->hours->size(); i++) { +// if (this->hours->at(i).size()) { +// if (hour == i) { +// return &(this->hours->at(i)); +// } +// else { +// last_defined_hour = i; +// } +// } +// else { +// empty_vector = &(this->hours->at(i)); +// if (hour == i) { +// if (last_defined_hour == -1) return empty_vector; +// return &(this->hours->at(last_defined_hour)); +// } +// } +// } +// return empty_vector; +// } + +int8_t Wordclock::find_hour(uint8_t hour) { + uint16_t last_defined_hour = -1; + for (int i = 0; i < this->hours->size(); i++) { + if (this->hours->at(i).size()) { + last_defined_hour = i; + if (hour == i) { + return last_defined_hour; + } + } + if (hour == i) break; + } + return last_defined_hour; +} + +int8_t Wordclock::find_minute(uint8_t minute) { + uint16_t last_defined_minute = -1; + for (int i = 0; i < this->minutes->size(); i++) { + if (this->minutes->at(i).size()) { + last_defined_minute = i; + if (minute == i) { + return last_defined_minute; + } + } + if (minute == i) break; + } + return last_defined_minute; +} +// std::vector * Wordclock::find_minute(uint8_t minute) { +// std::vector *empty_vector;// = new std::vector(); +// uint16_t last_defined_minute = -1; +// for (int i = 0; i < this->minutes->size(); i++) { +// if (this->minutes->at(i).size()) { +// if (minute == i) { +// return &(this->minutes->at(i)); +// } +// else { +// last_defined_minute = i; +// } +// } +// else { +// empty_vector = &(this->minutes->at(i)); +// if (minute == i) { +// if (last_defined_minute == -1) return empty_vector; +// return &(this->minutes->at(last_defined_minute)); +// } +// } +// } +// return empty_vector; +// } + + +void Wordclock::add_hour_offset(uint8_t index, int8_t offset) { + (*this->hour_offsets)[index] = offset; +} + +void Wordclock::add_hour(uint8_t hour, std::vector *segments) { + for (uint16_t i : *segments){ + this->hours->at(hour).push_back(i); + } +} + +void Wordclock::add_minute(uint8_t minute, std::vector *segments) { + for (uint16_t i : *segments){ + this->minutes->at(minute).push_back(i); + } +} + +void Wordclock::add_static(uint16_t segment_id) { + this->static_segments->push_back(segment_id); +} + +// uint16_t ** + +// Wordclock::Wordclock(std::vector *minutes, std::vector *hours, SegmentCoords *segments) +// Wordclock::Wordclock(uint16_t **minutes, uint16_t **hours, SegmentCoords *segments) +// : PollingComponent(1000) { +// // this->minutes = minutes; +// // this->hours = hours; +// // this->segments = segments; +// // std::vector minutes[60]; +// // std::vector hours[24]; +// } + Wordclock::Wordclock() + : PollingComponent(1000) { + // this->minutes = std::vector>(); + // // for (int i=0; i<60; i++) this->minutes.push_back(std::vector()); + + // this->hours = std::vector>(); + // // for (int i=0; i<24; i++) this->minutes.push_back(std::vector()); + + // this->segments = std::vector(); + } + + Wordclock::Wordclock(esphome::time::RealTimeClock *time, esphome::addressable_light::AddressableLightDisplay *display, esphome::light::AddressableLightState *light_state) + : PollingComponent(16) { + // ESP_LOGE("wordclock.cpp", "this: 0x%x", (this)); + // ESP_LOGE("wordclock.cpp", "display: 0x%x", (display)); + // ESP_LOGE("wordclock.cpp", "&display: 0x%x", (&display)); + + this->time = time; + this->display = display; + this->light = this->display->get_light(); + this->light_state = light_state; + // light::AddressableLight *light = this->display->get_light(); + + // light::AddressableRainbowLightEffect *light_addressablerainbowlighteffect; + // light::AddressableTwinkleEffect *light_addressabletwinkleeffect; + // light::AddressableRandomTwinkleEffect *light_addressablerandomtwinkleeffect; + + // this->rainbow = new light::AddressableRainbowLightEffect("rainbow"); + // this->rainbow->set_speed(10); + // this->rainbow->set_width(50); + // this->twinkle = new light::AddressableTwinkleEffect("twinkle"); + // this->twinkle->set_twinkle_probability(0.05f); + // this->twinkle->set_progress_interval(4); + // this->randomTwinkle = new light::AddressableRandomTwinkleEffect("random_twinkle"); + // this->randomTwinkle->set_twinkle_probability(0.05f); + // this->randomTwinkle->set_progress_interval(32); + + this->hour_offsets = new std::vector(60, 0); + + this->minutes = new std::vector>(60, std::vector()); + // for (int i=0; i<60; i++) this->minutes->push_back(std::vector()); + + this->hours = new std::vector>(24, std::vector()); + // for (int i=0; i<24; i++) this->hours->push_back(std::vector()); + + this->segments = new std::vector(); + this->static_segments = new std::vector(); + } +} // namespace wordclock +} // namespace esphome diff --git a/components/wordclock/wordclock.h b/components/wordclock/wordclock.h new file mode 100644 index 0000000..42b98df --- /dev/null +++ b/components/wordclock/wordclock.h @@ -0,0 +1,227 @@ +#pragma once + +#include "esphome.h" +// By now only loosely based on https://github.com/leinich/ha-wordclock-esphome + +// esphome dependencies: +// needs: esphome time --> id: current_time +// needs: esphome fastled --> id: fastledlight + +// #ifdef USE_ESP32 + +namespace esphome { +namespace wordclock { + +///// Word Table ///// +// .line(0,0,1,0, color); +// .line(2,0,3,0, color); +// int WORD_IT_IS[5][2] = {{4,0}, {0,0}, {0,1}, {0,3}, {0,4}}; + +// // .line(2,1,8,1, color); +// int WORD_QUARTER[8][2] = {{7,0}, {1,2}, {1,3}, {1,4}, {1,5}, {1,6}, {1,7}, {1,8}}; +// // .line(0,2, 5,2, color); +// int WORD_TWENTY[7][2] = {{6,0}, {2,0}, {2,1}, {2,2}, {2,3}, {2,4}, {2,5}}; +// // .line(6,2, 9,2, color); +// int WORD_FIVE_MINUTES[5][2] = {{4,0}, {2,6}, {2,7}, {2,8}, {2,9}}; +// // .line(0,3, 3,3, color); +// int WORD_HALF[5][2] = {{4,0}, {3,0}, {3,1}, {3,2}, {3,3}}; +// // .line(5,3, 7,3, color); +// int WORD_TEN_MINUTES[4][2] = {{3,0}, {3,5}, {3,6}, {3,7}}; +// // .line(9,3, 10,3, color); +// int WORD_TO[3][2] = {{2,0}, {3,9}, {3,10}}; +// // .line(0,4, 3,4, color); +// int WORD_PAST[5][2] = {{4,0}, {4,0}, {4,1}, {4,2}, {4,3}}; + +// int WORD_NINE[5][2] = {{4,0}, {4,7}, {4,8}, {4,9}, {4,10}}; +// int WORD_ONE[4][2] = {{3,0}, {5,0}, {5,1}, {5,2}}; +// int WORD_SIX[4][2] = {{3,0}, {5,3}, {5,4}, {5,5}}; +// int WORD_THREE[6][2] = {{5,0}, {5,6}, {5,7}, {5,8}, {5,9}, {5,10}}; +// int WORD_FOUR[5][2] = {{4,0}, {6,0}, {6,1}, {6,2}, {6,3}}; +// int WORD_FIVE[5][2] = {{4,0}, {6,4}, {6,5}, {6,6}, {6,7}}; +// int WORD_TWO[4][2] = {{3,0}, {6,8}, {6,9}, {6,10}}; +// int WORD_EIGHT[6][2] = {{5,0}, {7,0}, {7,1}, {7,2}, {7,3}, {7,4}}; +// int WORD_ELEVEN[7][2] = {{6,0}, {7,5}, {7,6}, {7,7}, {7,8}, {7,9}, {7,10}}; +// int WORD_SEVEN[6][2] = {{5,0}, {8,0}, {8,1}, {8,2}, {8,3}, {8,4}}; +// int WORD_TWELFE[7][2] = {{6,0}, {8,5}, {8,6}, {8,7}, {8,8}, {8,9}, {8,10}}; +// int WORD_TEN[4][2] = {{3,0}, {9,0}, {9,1}, {9,2}}; +// int WORD_OCLOCK[7][2] = {{6,0}, {9,5}, {9,6}, {9,7}, {9,8}, {9,9}, {9,10}}; + + + +// using display_writer_t = std::function; + + +struct SegmentCoords { + uint16_t x1; + uint16_t x2; + uint16_t y1; + uint16_t y2; +}; + + +class Wordclock : public esphome::PollingComponent { + public: + Wordclock(); + Wordclock(esphome::time::RealTimeClock *time, esphome::addressable_light::AddressableLightDisplay *display, esphome::light::AddressableLightState *light_state); + void add_segment(SegmentCoords segment); + void add_hour(uint8_t index, std::vector *segments); + void add_minute(uint8_t index, std::vector *segments); + void add_static(uint16_t segment_id); + void add_hour_offset(uint8_t index, int8_t offset); + void setup(); + void update(); + // void setup() override { + + // } + + // void set_writer(display_writer_t &&writer); + + // void display_word(const int word[][2], const CRGB& c) { + // for (int i=1; i < word[0][0] + 1; i++) { + // leds[map_coords_to_strip(word[i][0], word[i][1])].setRGB(c.r, c.g, c.b); + // } + // } + + // void display_minutes(int minutes, const CRGB& color) { + // int five_minute_chunk = minutes / 5; + + // switch (five_minute_chunk) + // { + // case 0: // sharp + // display_word(WORD_OCLOCK, color); ESP_LOGD("minute", "oclock "); break; + // case 1: // five past + // display_word(WORD_FIVE_MINUTES, color); ESP_LOGD("minute", "five past "); break; + // case 2: // ten past + // display_word(WORD_TEN_MINUTES, color); ESP_LOGD("minute", "ten past "); break; + // case 3: // quarter past + // display_word(WORD_QUARTER, color); ESP_LOGD("minute", "quarter past "); break; + // case 4: // twenty past + // display_word(WORD_TWENTY, color); ESP_LOGD("minute", "twenty past "); break; + // case 5: // twenty five past + // display_word(WORD_TWENTY, color); display_word(WORD_FIVE_MINUTES, color); + // ESP_LOGD("minute", "twenty five past "); break; + // case 6: // half past + // display_word(WORD_HALF, color); ESP_LOGD("minute", "half past "); break; + // case 7: // twenty five to + // display_word(WORD_TWENTY, color); display_word(WORD_FIVE_MINUTES, color); + // ESP_LOGD("minute", "twenty five to "); break; + // case 8: // twenty to + // display_word(WORD_TWENTY, color); ESP_LOGD("minute", "twenty to "); break; + // case 9: // quarter to + // display_word(WORD_QUARTER, color); ESP_LOGD("minute", "quarter to "); break; + // case 10: // ten to + // display_word(WORD_TEN_MINUTES, color); ESP_LOGD("minute", "ten to "); break; + // case 11: // five to + // display_word(WORD_FIVE_MINUTES, color); ESP_LOGD("minute", "five to "); break; + // default: + // break; + // } + // if (five_minute_chunk > 6) { + // display_word(WORD_TO, color); + // } else if (five_minute_chunk > 0) { + // display_word(WORD_PAST, color); + // } + // } + + // void display_hour(int hour, int minutes, const CRGB& color) { + // int five_minute_chunk = minutes / 5; + // if (five_minute_chunk > 6) { + // hour += 1; + // } + + // switch (hour % 12) + // { + // case 0: // twelve + // display_word(WORD_TWELFE, color); ESP_LOGD("hour", "twelve "); break; + // case 1: // one + // display_word(WORD_ONE, color); ESP_LOGD("hour", "one "); break; + // case 2: // two + // display_word(WORD_TWO, color); ESP_LOGD("hour", "two "); break; + // case 3: // three + // display_word(WORD_THREE, color); ESP_LOGD("hour", "three "); break; + // case 4: // four + // display_word(WORD_FOUR, color); ESP_LOGD("hour", "four "); break; + // case 5: // five + // display_word(WORD_FIVE, color); ESP_LOGD("hour", "five "); break; + // case 6: // six + // display_word(WORD_SIX, color); ESP_LOGD("hour", "six "); break; + // case 7: // seven + // display_word(WORD_SEVEN, color); ESP_LOGD("hour", "seven "); break; + // case 8: // eight + // display_word(WORD_EIGHT, color); ESP_LOGD("hour", "eight "); break; + // case 9: // nine + // display_word(WORD_NINE, color); ESP_LOGD("hour", "nine "); break; + // case 10: // ten + // display_word(WORD_TEN, color); ESP_LOGD("hour", "ten "); break; + // case 11: // eleven + // display_word(WORD_ELEVEN, color); ESP_LOGD("hour", "eleven "); break; + // default: + // break; + // } + // } + + // void display_time(int hour, int minutes, const CRGB& color) { + // // clear_all_leds(); + // display_word(WORD_IT_IS, color); + // display_hour(hour, minutes, color); + // display_minutes(minutes, color); + // // FastLED.show(); + // } + + + + // void loop() override { + // // auto time = id(current_time).now(); + // // https://www.esphome.io/api/classesphome_1_1light_1_1_light_color_values.html LightColorValues Class + // // auto fastledlight2 = id(fastledlight).current_values; + // //convert float 0.0 till 1.0 into int 0 till 255 + // red = (int) (fastledlight2.get_red() * 125); + // green = (int) (fastledlight2.get_green() * 125); + // blue = (int) (fastledlight2.get_blue() * 125); + // brightness = 0; + // //check if light is on and set brightness + // if (fastledlight2.get_state() > 0 ) { + // brightness = (int) (fastledlight2.get_brightness()*125); + // } else { + // // ESP_LOGD("loop", "fastledlight state off - b: %i rgb %i %i %i", brightness, red, green, blue); delay(100); + // } + + // FastLED.setBrightness(brightness); + // FastLED.show(); + // //check if valid time. Blink red,green,blue until valid time is present + + // } + protected: + void draw_segment(uint16_t segment_id); + int8_t find_hour(uint8_t hour); + int8_t find_minute(uint8_t minute); + // std::vector * find_hour(uint8_t hour); + // std::vector * find_minute(uint8_t minute); + std::vector> *minutes; + std::vector> *hours; + std::vector *static_segments; + std::vector *hour_offsets; + + void start_idle_animation(); + void end_idle_animation(); + + // uint16_t **minutes; + // uint16_t **hours; + + bool valid_time{true}; + std::vector *segments; + esphome::time::RealTimeClock *time; + esphome::light::AddressableLight *light; + esphome::light::AddressableLightState *light_state; + esphome::addressable_light::AddressableLightDisplay *display; + light::AddressableRainbowLightEffect *rainbow; + light::AddressableTwinkleEffect *twinkle; + light::AddressableRandomTwinkleEffect *randomTwinkle; + +}; + + + +} // namespace wordclock +} // namespace esphome + diff --git a/display_test.yaml b/display_test.yaml index 837eacf..9a0f27f 100644 --- a/display_test.yaml +++ b/display_test.yaml @@ -1,5 +1,5 @@ esphome: - name: "display_thing" + name: "display-thing" esp8266: board: d1_mini @@ -26,8 +26,8 @@ ota: logger: # esp8266_store_log_strings_in_flash: false -web_server: - port: 80 +# web_server: +# port: 80 light: - name: NeoPixel Strip 1 @@ -36,8 +36,10 @@ light: type: GRB variant: WS2812 pin: GPIO3 - num_leds: 200 - # default_transition_length: 0.2s + num_leds: 199 + restore_mode: ALWAYS_ON + default_transition_length: 0s + color_correct: [100%, 100%, 100%] method: type: esp8266_dma @@ -47,18 +49,48 @@ display: addressable_light_id: neopixel_strip_1 width: 18 height: 11 - rotation: 0° + rotation: 270° update_interval: 16ms + pixel_mapper: |- + int mapping_even[] = {0, 2, 4, 6,8,10,11,13,15,17, 16,16,16,16, 16,16,16,16}; + int mapping_odd[] = {17,15,13,11,9, 7, 6, 4, 2, 0, 16,16,16,16, 16,16,16,16}; + if (x > 9) return -1; + if (y % 2 == 0) { + return (y * 18 + mapping_even[x]); // mapping_even[y]; + } else { + return (y * 18 + mapping_odd[x]); // mapping_odd[y]; + //return (y * 18 + 17 - x); // mapping_odd[y]; + } + //if (x % 2 == 0) { + // return (x * 8) + y; + //} + //return (x * 8) + (7 - y); lambda: |- - // Draw a bulls-eye pattern Color red = Color(0xFF0000); Color green = Color(0x00FF00); Color blue = Color(0x0000FF); - it.rectangle(0, 0, 18, 11, red); - it.rectangle(1, 1, 17, 10, green); - it.rectangle(2, 2, 16, 9, blue); - it.rectangle(3, 3, 15, 8, red); - + Color turquoise = Color(0x00FFFF); + it.draw_pixel_at(0,0, green); + it.draw_pixel_at(10,0, red); + it.draw_pixel_at(0,9, turquoise); + it.draw_pixel_at(10,9, blue); + //it.line(0,0, 11,0, red); + //it.line(0,1, 11,1, green); + //it.line(0,2, 11,2, blue); + //it.line(0,3, 11,3, red); + //it.line(0,4, 11,4, green); + //it.line(0,5, 11,5, blue); + //it.line(0,6, 11,6, red); + //it.line(0,7, 11,7, green); + //it.line(0,8, 11,8, blue); + //it.line(0,9, 11,9, red); + //it.line(0,10, 9,10, green); + //it.line(0,11, 9,11, blue); + // it.line(5,14, 10,14, green); + //it.rectangle(0, 0, 17, 10, red); + //it.rectangle(1, 1, 16, 9, green); + //it.rectangle(2, 2, 15, 8, blue); + //it.rectangle(3, 3, 14, 7, red); time: - platform: sntp diff --git a/image_test.yaml b/image_test.yaml new file mode 100644 index 0000000..f3662f4 --- /dev/null +++ b/image_test.yaml @@ -0,0 +1,154 @@ +esphome: + name: "display-thing" + +esp8266: + board: d1_mini + framework: + version: recommended + +external_components: + - source: + type: local + path: components + components: [ wordclock, addressable_light ] + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + ap: + ssid: "${devicename}" + password: !secret ap_password + manual_ip: + static_ip: !secret manualip_static_ip + gateway: !secret manualip_gateway + subnet: !secret manualip_subnet + dns1: 1.1.1.1 + dns2: 1.0.0.1 + +api: + +# ota: +# password: "${devicename}" + +logger: + # esp8266_store_log_strings_in_flash: false +web_server: + port: 80 + +light: + - name: NeoPixel Strip 1 + id: neopixel_strip_1 + platform: neopixelbus + type: GRB + variant: WS2812 + pin: GPIO3 + num_leds: 199 + restore_mode: ALWAYS_ON + default_transition_length: 0s + color_correct: [100%, 100%, 100%] + method: + type: esp8266_dma + +display: + - platform: addressable_light + id: led_matrix_display + addressable_light_id: neopixel_strip_1 + width: 18 + height: 11 + rotation: 270° + update_interval: 16ms + auto_clear_enabled: false + pixel_mapper: |- + int mapping_even[] = {0, 2, 4, 6,8,10,11,13,15,17, 16,16,16,16, 16,16,16,16}; + int mapping_odd[] = {17,15,13,11,9, 7, 6, 4, 2, 0, 16,16,16,16, 16,16,16,16}; + if (x > 9) return -1; + if (y % 2 == 0) { + return (y * 18 + mapping_even[x]); // mapping_even[y]; + } else { + return (y * 18 + mapping_odd[x]); // mapping_odd[y]; + } + # lambda: |- + # it.fill(Color(0)); + # it.image(0, 0, id(foo)); + # //it.line(0,0,5,5, Color(0x00FF00)); + +# image: +# - id: foo +# file: ./test.png +# type: RGB24 +# resize: 18x11 + + +time: + - platform: sntp + id: current_time + timezone: !secret timezone + + +wordclock: + time_id: current_time + display_id: led_matrix_display + static_segments: ["IT", "IS"] + segments: + - {name: "IT", line: {x1: 0, x2: 1, y1: 0}} + - {name: "IS", line: {x1: 3, x2: 4, y1: 0}} + - {name: "AM", line: {x1: 7, x2: 8, y1: 0}} + - {name: "PM", line: {x1: 9, x2: 10, y1: 0}} + - {name: "QUARTER", line: {x1: 2, x2: 8, y1: 1}} + - {name: "TWENTY", line: {x1: 0, x2: 5, y1: 2}} + - {name: "FIVE_MINUTES", line: {x1: 6, x2: 9, y1: 2}} + - {name: "HALF", line: {x1: 0, x2: 3, y1: 3}} + - {name: "TEN_MINUTES", line: {x1: 5, x2: 7, y1: 3}} + - {name: "TO", line: {x1: 9, x2: 10, y1: 3}} + - {name: "PAST", line: {x1: 0, x2: 3, y1: 4}} + - {name: "NINE", line: {x1: 7, x2: 10, y1: 4}} + - {name: "ONE", line: {x1: 0, x2: 2, y1: 5}} + - {name: "SIX", line: {x1: 3, x2: 5, y1: 5}} + - {name: "THREE", line: {x1: 6, x2: 10, y1: 5}} + - {name: "FOUR", line: {x1: 0, x2: 3, y1: 6}} + - {name: "FIVE", line: {x1: 4, x2: 7, y1: 6}} + - {name: "TWO", line: {x1: 8, x2: 10, y1: 6}} + - {name: "EIGHT", line: {x1: 0, x2: 4, y1: 7}} + - {name: "ELEVEN", line: {x1: 5, x2: 10, y1: 7}} + - {name: "SEVEN", line: {x1: 0, x2: 4, y1: 8}} + - {name: "TWELVE", line: {x1: 5, x2: 10, y1: 8}} + - {name: "TEN", line: {x1: 0, x2: 2, y1: 9}} + - {name: "OCLOCK", line: {x1: 5, x2: 10, y1: 9}} + minutes: + - {minute: 0, segments: ["OCLOCK"]} + - {minute: 5, segments: ["FIVE_MINUTES", "PAST"]} + - {minute: 10, segments: ["TEN_MINUTES", "PAST"]} + - {minute: 15, segments: ["QUARTER", "PAST"]} + - {minute: 20, segments: ["TWENTY", "PAST"]} + - {minute: 25, segments: ["TWENTY", "FIVE_MINUTES", "PAST"]} + - {minute: 30, segments: ["HALF", "PAST"]} + - {minute: 35, segments: ["TWENTY", "FIVE_MINUTES", "TO"]} + - {minute: 40, segments: ["TWENTY", "TO"]} + - {minute: 45, segments: ["QUARTER", "TO"]} + - {minute: 50, segments: ["TEN_MINUTES", "TO"]} + - {minute: 55, segments: ["FIVE_MINUTES", "TO"]} + hours: + - {hour: 0, segments: ["TWELVE", "AM"]} + - {hour: 1, segments: ["ONE", "AM"]} + - {hour: 2, segments: ["TWO", "AM"]} + - {hour: 3, segments: ["THREE", "AM"]} + - {hour: 4, segments: ["FOUR", "AM"]} + - {hour: 5, segments: ["FIVE", "AM"]} + - {hour: 6, segments: ["SIX", "AM"]} + - {hour: 7, segments: ["SEVEN", "AM"]} + - {hour: 8, segments: ["EIGHT", "AM"]} + - {hour: 9, segments: ["NINE", "AM"]} + - {hour: 10, segments: ["TEN", "AM"]} + - {hour: 11, segments: ["ELEVEN", "AM"]} + - {hour: 12, segments: ["TWELVE", "PM"]} + - {hour: 13, segments: ["ONE", "PM"]} + - {hour: 14, segments: ["TWO", "PM"]} + - {hour: 15, segments: ["THREE", "PM"]} + - {hour: 16, segments: ["FOUR", "PM"]} + - {hour: 17, segments: ["FIVE", "PM"]} + - {hour: 18, segments: ["SIX", "PM"]} + - {hour: 19, segments: ["SEVEN", "PM"]} + - {hour: 20, segments: ["EIGHT", "PM"]} + - {hour: 21, segments: ["NINE", "PM"]} + - {hour: 22, segments: ["TEN", "PM"]} + - {hour: 23, segments: ["ELEVEN", "PM"]} \ No newline at end of file diff --git a/image_test_3leds.yaml b/image_test_3leds.yaml new file mode 100644 index 0000000..f9d53b1 --- /dev/null +++ b/image_test_3leds.yaml @@ -0,0 +1,225 @@ +esphome: + name: "display-thing" + # on_boot: + # priority: 600 + # # ... + # then: + # - light.turn_on: + # id: neopixel_strip_1 + # brightness: 100% + # effect: random_twinkle + +esp8266: + board: d1_mini + framework: + version: recommended + +external_components: + - source: + type: local + path: components + components: [ wordclock ] + # components: [ wordclock, addressable_light ] + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + ap: + ssid: "WordClock Configuration" + password: !secret ap_password + manual_ip: + static_ip: !secret manualip_static_ip + gateway: !secret manualip_gateway + subnet: !secret manualip_subnet + dns1: 1.1.1.1 + dns2: 1.0.0.1 + +captive_portal: + +api: + +ota: + password: "wordclock" + +logger: + # esp8266_store_log_strings_in_flash: false +# web_server: +# port: 80 + +time: + - platform: sntp + id: current_time + timezone: !secret timezone + on_time_sync: + then: + - logger.log: "synced system clock" + # - light.turn_on: + # id: neopixel_strip_1 + # effect: "None" + # - light.turn_off: + # id: neopixel_strip_1 +light: + - name: NeoPixel Strip 1 + id: neopixel_strip_1 + platform: neopixelbus + type: GRB + variant: WS2812 + pin: GPIO3 + num_leds: 198 + restore_mode: ALWAYS_OFF + # default_transition_length: 0.7s + # color_correct: [100%, 100%, 100%] + method: + type: esp8266_dma + + effects: + # - random: + # - pulse: + # - strobe: + # - flicker: + - addressable_rainbow: + name: "rainbow" + # - addressable_color_wipe: + # - addressable_scan: + - addressable_twinkle: + name: "twinkle" + - addressable_random_twinkle: + name: "random_twinkle" + # - addressable_fireworks: + # - addressable_flicker: + # - wled: + + +display: + - platform: addressable_light + id: led_matrix_display + addressable_light_id: neopixel_strip_1 + width: 18 + height: 11 + rotation: 270° + update_interval: 16ms + auto_clear_enabled: false + pixel_mapper: |- + int mapping_even[] = {0, 2, 4, 6,8,10,11,13,15,17, 16,16,16,16, 16,16,16,16}; + int mapping_odd[] = {17,15,13,11,9, 7, 6, 4, 2, 0, 16,16,16,16, 16,16,16,16}; + if (x > 9) return -1; + if (y % 2 == 0) { + return (y * 18 + mapping_even[x]); // mapping_even[y]; + } else { + return (y * 18 + mapping_odd[x]); // mapping_odd[y]; + } + # lambda: |- + # //ESP_LOGE("lambda", "display_ptr: %i", &it); + # //it.fill(Color(0xff00ff)); + # //it.line(0,0,0,0, Color(0x00FF00)); + + +wordclock: + time_id: current_time + display_id: led_matrix_display + addressable_light_id: neopixel_strip_1 + static_segments: ["IT", "IS"] + segments: + - {name: "IT", line: {x1: 0, x2: 1, y1: 0}} + - {name: "IS", line: {x1: 3, x2: 4, y1: 0}} + - {name: "AM", line: {x1: 7, x2: 8, y1: 0}} + - {name: "PM", line: {x1: 9, x2: 10, y1: 0}} + - {name: "QUARTER", line: {x1: 2, x2: 8, y1: 1}} + - {name: "TWENTY", line: {x1: 0, x2: 5, y1: 2}} + - {name: "FIVE_MINUTES", line: {x1: 6, x2: 9, y1: 2}} + - {name: "HALF", line: {x1: 0, x2: 3, y1: 3}} + - {name: "TEN_MINUTES", line: {x1: 5, x2: 7, y1: 3}} + - {name: "TO", line: {x1: 9, x2: 10, y1: 3}} + - {name: "PAST", line: {x1: 0, x2: 3, y1: 4}} + - {name: "NINE", line: {x1: 7, x2: 10, y1: 4}} + - {name: "ONE", line: {x1: 0, x2: 2, y1: 5}} + - {name: "SIX", line: {x1: 3, x2: 5, y1: 5}} + - {name: "THREE", line: {x1: 6, x2: 10, y1: 5}} + - {name: "FOUR", line: {x1: 0, x2: 3, y1: 6}} + - {name: "FIVE", line: {x1: 4, x2: 7, y1: 6}} + - {name: "TWO", line: {x1: 8, x2: 10, y1: 6}} + - {name: "EIGHT", line: {x1: 0, x2: 4, y1: 7}} + - {name: "ELEVEN", line: {x1: 5, x2: 10, y1: 7}} + - {name: "SEVEN", line: {x1: 0, x2: 4, y1: 8}} + - {name: "TWELVE", line: {x1: 5, x2: 10, y1: 8}} + - {name: "TEN", line: {x1: 0, x2: 2, y1: 9}} + - {name: "OCLOCK", line: {x1: 5, x2: 10, y1: 9}} + minutes: + - {minute: 0, hour_offset: 0, segments: ["OCLOCK"]} + - {minute: 5, hour_offset: 0, segments: ["FIVE_MINUTES", "PAST"]} + - {minute: 10, hour_offset: 0, segments: ["TEN_MINUTES", "PAST"]} + - {minute: 15, hour_offset: 0, segments: ["QUARTER", "PAST"]} + - {minute: 20, hour_offset: 0, segments: ["TWENTY", "PAST"]} + - {minute: 25, hour_offset: 0, segments: ["TWENTY", "FIVE_MINUTES", "PAST"]} + - {minute: 30, hour_offset: 0, segments: ["HALF", "PAST"]} + - {minute: 35, hour_offset: 1, segments: ["TWENTY", "FIVE_MINUTES", "TO"]} + - {minute: 40, hour_offset: 1, segments: ["TWENTY", "TO"]} + - {minute: 45, hour_offset: 1, segments: ["QUARTER", "TO"]} + - {minute: 50, hour_offset: 1, segments: ["TEN_MINUTES", "TO"]} + - {minute: 55, hour_offset: 1, segments: ["FIVE_MINUTES", "TO"]} + hours: + - {hour: 0, segments: "TWELVE"} + - {hour: 1, segments: "ONE"} + - {hour: 2, segments: "TWO"} + - {hour: 3, segments: "THREE"} + - {hour: 4, segments: "FOUR"} + - {hour: 5, segments: "FIVE"} + - {hour: 6, segments: "SIX"} + - {hour: 7, segments: "SEVEN"} + - {hour: 8, segments: "EIGHT"} + - {hour: 9, segments: "NINE"} + - {hour: 10, segments: "TEN"} + - {hour: 11, segments: "ELEVEN"} + - {hour: 12, segments: "TWELVE"} + - {hour: 13, segments: "ONE"} + - {hour: 14, segments: "TWO"} + - {hour: 15, segments: "THREE"} + - {hour: 16, segments: "FOUR"} + - {hour: 17, segments: "FIVE"} + - {hour: 18, segments: "SIX"} + - {hour: 19, segments: "SEVEN"} + - {hour: 20, segments: "EIGHT"} + - {hour: 21, segments: "NINE"} + - {hour: 22, segments: "TEN"} + - {hour: 23, segments: "ELEVEN"} + # hours: + # - {hour: 0, segments: ["TWELVE", "AM"]} + # - {hour: 1, segments: ["ONE", "AM"]} + # - {hour: 2, segments: ["TWO", "AM"]} + # - {hour: 3, segments: ["THREE", "AM"]} + # - {hour: 4, segments: ["FOUR", "AM"]} + # - {hour: 5, segments: ["FIVE", "AM"]} + # - {hour: 6, segments: ["SIX", "AM"]} + # - {hour: 7, segments: ["SEVEN", "AM"]} + # - {hour: 8, segments: ["EIGHT", "AM"]} + # - {hour: 9, segments: ["NINE", "AM"]} + # - {hour: 10, segments: ["TEN", "AM"]} + # - {hour: 11, segments: ["ELEVEN", "AM"]} + # - {hour: 12, segments: ["TWELVE", "PM"]} + # - {hour: 13, segments: ["ONE", "PM"]} + # - {hour: 14, segments: ["TWO", "PM"]} + # - {hour: 15, segments: ["THREE", "PM"]} + # - {hour: 16, segments: ["FOUR", "PM"]} + # - {hour: 17, segments: ["FIVE", "PM"]} + # - {hour: 18, segments: ["SIX", "PM"]} + # - {hour: 19, segments: ["SEVEN", "PM"]} + # - {hour: 20, segments: ["EIGHT", "PM"]} + # - {hour: 21, segments: ["NINE", "PM"]} + # - {hour: 22, segments: ["TEN", "PM"]} + # - {hour: 23, segments: ["ELEVEN", "PM"]} + # minutes: + # - {minute: 0, segments: ["A"]} + # - {minute: 5, segments: ["B"]} + # - {minute: 10, segments: ["A"]} + # - {minute: 15, segments: ["B"]} + # - {minute: 20, segments: ["A"]} + # - {minute: 25, segments: ["B"]} + # - {minute: 30, segments: ["A"]} + # - {minute: 35, segments: ["B"]} + # - {minute: 40, segments: ["A"]} + # - {minute: 45, segments: ["B"]} + # - {minute: 50, segments: ["A"]} + # - {minute: 55, segments: ["B"]} + # hours: + # - {hour: 0, segments: ["C"]} + \ No newline at end of file diff --git a/test.png b/test.png new file mode 100644 index 0000000..a6acecd Binary files /dev/null and b/test.png differ