From dd1408c5cbcd4015f6a4fddff1d9d4d12beca118 Mon Sep 17 00:00:00 2001 From: Philip Stark Date: Fri, 12 May 2023 05:07:21 +0200 Subject: [PATCH] feat: add transitions and clean up code. --- components/wordcl/__init__.py | 53 --- components/wordcl/desky.cpp | 116 ----- components/wordcl/desky.h | 52 --- components/wordclock/__init__.py | 424 ++++------------- components/wordclock/wordclock.cpp | 430 +++++++----------- components/wordclock/wordclock.h | 383 ++++++++-------- flake.lock | 64 +++ ...test_3leds.yaml => susannes_wordclock.yaml | 110 ++--- 8 files changed, 533 insertions(+), 1099 deletions(-) delete mode 100644 components/wordcl/__init__.py delete mode 100644 components/wordcl/desky.cpp delete mode 100644 components/wordcl/desky.h create mode 100644 flake.lock rename image_test_3leds.yaml => susannes_wordclock.yaml (61%) diff --git a/components/wordcl/__init__.py b/components/wordcl/__init__.py deleted file mode 100644 index de1ab14..0000000 --- a/components/wordcl/__init__.py +++ /dev/null @@ -1,53 +0,0 @@ -import esphome.codegen as cg -import esphome.config_validation as cv -from esphome import pins -from esphome.components import light -from esphome.components import uart -from esphome.components import sensor -from esphome.const import CONF_ID, CONF_HEIGHT, CONF_TIMEOUT, ICON_GAUGE - -DEPENDENCIES = ['time'] -AUTO_LOAD = ['light'] - -wordclock_ns = cg.esphome_ns.namespace('wordcl') - -Wordclock = wordclock_ns.class_('Wordclock', cg.Component, light.) -Desky = desky_ns.class_('Desky', cg.Component, uart.UARTDevice) - -CONF_UP = "up" -CONF_DOWN = "down" -CONF_REQUEST = "request" -CONF_STOPPING_DISTANCE = "stopping_distance" - - - -CONFIG_SCHEMA = cv.COMPONENT_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(Desky), - cv.Optional(CONF_UP): pins.gpio_output_pin_schema, - cv.Optional(CONF_DOWN): pins.gpio_output_pin_schema, - cv.Optional(CONF_REQUEST): pins.gpio_output_pin_schema, - cv.Optional(CONF_HEIGHT): sensor.sensor_schema(icon=ICON_GAUGE, accuracy_decimals=0), - cv.Optional(CONF_STOPPING_DISTANCE, default=15): cv.positive_int, - cv.Optional(CONF_TIMEOUT): cv.time_period, -}).extend(uart.UART_DEVICE_SCHEMA) - -async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) - await uart.register_uart_device(var, config) - if CONF_UP in config: - pin = await cg.gpio_pin_expression(config[CONF_UP]) - cg.add(var.set_up_pin(pin)) - if CONF_DOWN in config: - pin = await cg.gpio_pin_expression(config[CONF_DOWN]) - cg.add(var.set_down_pin(pin)) - if CONF_REQUEST in config: - pin = await cg.gpio_pin_expression(config[CONF_REQUEST]) - cg.add(var.set_request_pin(pin)) - if CONF_HEIGHT in config: - sens = await sensor.new_sensor(config[CONF_HEIGHT]) - cg.add(var.set_height_sensor(sens)) - cg.add(var.set_stopping_distance(config[CONF_STOPPING_DISTANCE])) - if CONF_TIMEOUT in config: - cg.add(var.set_timeout(config[CONF_TIMEOUT].total_milliseconds)) - diff --git a/components/wordcl/desky.cpp b/components/wordcl/desky.cpp deleted file mode 100644 index bcd6159..0000000 --- a/components/wordcl/desky.cpp +++ /dev/null @@ -1,116 +0,0 @@ -#include "desky.h" -#include "esphome/core/log.h" - -namespace esphome { -namespace desky { - -static const char *TAG = "desky"; - -const char *desky_operation_to_str(DeskyOperation op) { - switch (op) { - case DESKY_OPERATION_IDLE: - return "IDLE"; - case DESKY_OPERATION_RAISING: - return "RAISING"; - case DESKY_OPERATION_LOWERING: - return "LOWERING"; - default: - return "UNKNOWN"; - } -} - -void Desky::setup() { - if (this->up_pin_ != nullptr) - this->up_pin_->digital_write(false); - if (this->down_pin_ != nullptr) - this->down_pin_->digital_write(false); - if (this->request_pin_ != nullptr) { - this->request_pin_->digital_write(true); - this->request_time_ = millis(); - } -} - -void Desky::loop() { - static int state = 0; - static uint8_t high_byte; - - while (this->available()) { - uint8_t c; - int value; - this->read_byte(&c); - switch (state) { - case 0: - if (c == 1) - state = 1; - break; - case 1: - if (c == 1) - state = 2; - else - state = 0; - break; - case 2: - high_byte = c; - state = 3; - break; - case 3: - value = (high_byte << 8) + c; - this->current_pos_ = value; - if (this->height_sensor_ != nullptr) - this->height_sensor_->publish_state(value); - state = 0; - break; - } - } - - if (this->target_pos_ >= 0) { - if (abs(this->target_pos_ - this->current_pos_) < this->stopping_distance_) - this->stop(); - if ((this->timeout_ >= 0) && (millis() - this->start_time_ >= this->timeout_)) - this->stop(); - } - - if ((this->request_time_ > 0) && (millis() - this->request_time_ >= 100)) { - this->request_pin_->digital_write(false); - this->request_time_ = 0; - } -} - -void Desky::dump_config() { - ESP_LOGCONFIG(TAG, "Desky desk:"); - LOG_SENSOR("", "Height", this->height_sensor_); - LOG_PIN("Up pin: ", this->up_pin_); - LOG_PIN("Down pin: ", this->down_pin_); - LOG_PIN("Request pin: ", this->request_pin_); -} - -void Desky::move_to(int target_pos) { - if (abs(target_pos - this->current_pos_) < this->stopping_distance_) - return; - if (target_pos > this->current_pos_) { - if (this->up_pin_ == nullptr) - return; - this->up_pin_->digital_write(true); - this->current_operation = DESKY_OPERATION_RAISING; - } else { - if (this->down_pin_ == nullptr) - return; - this->down_pin_->digital_write(true); - this->current_operation = DESKY_OPERATION_LOWERING; - } - this->target_pos_ = target_pos; - if (this->timeout_ >= 0) - this->start_time_ = millis(); -} - -void Desky::stop() { - this->target_pos_ = -1; - if (this->up_pin_ != nullptr) - this->up_pin_->digital_write(false); - if (this->down_pin_ != nullptr) - this->down_pin_->digital_write(false); - this->current_operation = DESKY_OPERATION_IDLE; -} - -} // namespace desky -} // namespace esphome diff --git a/components/wordcl/desky.h b/components/wordcl/desky.h deleted file mode 100644 index fc9062c..0000000 --- a/components/wordcl/desky.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include "esphome/core/component.h" -#include "esphome/components/sensor/sensor.h" -#include "esphome/components/uart/uart.h" -#include "esphome/core/hal.h" - -namespace esphome { -namespace desky { - -enum DeskyOperation : uint8_t { - DESKY_OPERATION_IDLE = 0, - DESKY_OPERATION_RAISING, - DESKY_OPERATION_LOWERING, -}; - -const char *desky_operation_to_str(DeskyOperation op); - -class Desky : public Component, public sensor::Sensor, public uart::UARTDevice { - public: - float get_setup_priority() const override { return setup_priority::LATE; } - void setup() override; - void loop() override; - void dump_config() override; - - void set_height_sensor(sensor::Sensor *sensor) { this->height_sensor_ = sensor; } - void set_up_pin(GPIOPin *pin) { this->up_pin_ = pin; } - void set_down_pin(GPIOPin *pin) { this->down_pin_ = pin; } - void set_request_pin(GPIOPin *pin) { this->request_pin_ = pin; } - void set_stopping_distance(int distance) { this->stopping_distance_ = distance; } - void set_timeout(int timeout) { this->timeout_ = timeout; } - - void move_to(int height); - void stop(); - - DeskyOperation current_operation{DESKY_OPERATION_IDLE}; - - protected: - sensor::Sensor *height_sensor_{nullptr}; - GPIOPin *up_pin_{nullptr}; - GPIOPin *down_pin_{nullptr}; - GPIOPin *request_pin_{nullptr}; - int stopping_distance_; - int current_pos_{0}; - int target_pos_{-1}; - int timeout_{-1}; - uint64_t start_time_; - uint64_t request_time_{0}; -}; - -} // namespace desky -} // namespace esphome diff --git a/components/wordclock/__init__.py b/components/wordclock/__init__.py index 3192cf8..242f2b6 100644 --- a/components/wordclock/__init__.py +++ b/components/wordclock/__init__.py @@ -1,10 +1,11 @@ import logging from esphome import core -from esphome.components import display, font, time, light +from esphome.components import display, font, time, light, color 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, @@ -17,6 +18,8 @@ from esphome.const import ( CONF_HOURS, CONF_MINUTE, CONF_MINUTES, + CONF_BRIGHTNESS, + CONF_UPDATE_INTERVAL, ) from esphome.core import CORE, HexInt @@ -32,77 +35,21 @@ CONF_LINE_END_Y = "y2" CONF_LINE = "line" +CONF_COLOR_ON = "color_on" +CONF_COLOR_OFF = "color_off" + +DATA_VECTOR_SEGMENTS_HOUR = "data_vector_segments_hour" +DATA_VECTOR_SEGMENTS_MINUTE = "data_vector_segments_minute" +DATA_VECTOR_SEGMENTS = "data_vector_segments_ptr" + 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" - +CONF_FADE_LENGTH = "fade_length" DEPENDENCIES = ["display", "time"] MULTI_CONF = False - +int8 = cg.global_ns.namespace("int8_t") wordclock_ns = cg.esphome_ns.namespace("wordclock") SegmentCoords = wordclock_ns.struct("SegmentCoords") Wordclock = wordclock_ns.class_( @@ -130,31 +77,7 @@ WORDCLOCK_MINUTE_SCHEMA = { 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), @@ -167,43 +90,28 @@ WORDCLOCK_SCHEMA = cv.Schema( cv.Required(CONF_ADDRESSABLE_LIGHT_ID): cv.use_id( light.AddressableLightState ), - # cv.Optional(CONF_LAMBDA): cv.lambda_, + cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, + cv.Optional(CONF_COLOR_ON): cv.use_id(color.ColorStruct), + cv.Optional(CONF_COLOR_OFF): cv.use_id(color.ColorStruct), 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), + # cv.Optional( + # CONF_FADE_LENGTH, default="700ms" + # ): cv., + cv.Optional( + CONF_FADE_LENGTH, default="700ms" + ): cv.positive_time_period_milliseconds, + cv.Optional( + CONF_UPDATE_INTERVAL, default="16ms" + ): cv.positive_time_period_milliseconds, + # 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(DATA_VECTOR_SEGMENTS): cv.declare_id(cg.std_vector.template(cg.uint16)), } ) -# CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, WORDCLOCK_SCHEMA) CONFIG_SCHEMA = WORDCLOCK_SCHEMA async def to_code(config): @@ -212,236 +120,84 @@ async def to_code(config): 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) + if CONF_COLOR_ON in config: + wrapped_color_on = await cg.get_variable(config[CONF_COLOR_ON]) + cg.add(var.set_on_color(wrapped_color_on)) + if CONF_COLOR_OFF in config: + wrapped_color_off = await cg.get_variable(config[CONF_COLOR_OFF]) + cg.add(var.set_off_color(wrapped_color_off)) + + # segments_vector_ptr = cg.new_Pvariable(config[DATA_VECTOR_SEGMENTS]) +# void Wordclock::setup_transitions(uint32_t milliseconds) { +# this->on_transformer->setup(0, this->brightness, milliseconds); +# this->off_transformer->setup(this->brightness, 0, milliseconds); +# } + cg.add(var.setup_transitions(config[CONF_FADE_LENGTH])) + cg.add(var.set_brightness(int(config[CONF_BRIGHTNESS] * 255))) + + + + SEGMENT_MAP = dict() - for idx, segm in enumerate(config[CONF_SEGMENTS]): - print(segm[CONF_NAME]) - SEGMENT_MAP[segm[CONF_NAME]] = idx + for idx, segment in enumerate(config[CONF_SEGMENTS]): + SEGMENT_MAP[segment[CONF_NAME]] = idx + line = segment[CONF_LINE] - 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) + x1 = line[CONF_LINE_START_X] + y1 = line[CONF_LINE_START_Y] + x2 = line[CONF_LINE_END_X] + y2 = line.get(CONF_LINE_END_Y, y1) - exp = cg.StructInitializer( - SegmentCoords, - ("x1", line_start_x), - ("x2", line_end_x), - ("y1", line_start_y), - ("y2", line_end_y), - ) + exp = cg.StructInitializer(SegmentCoords, ("x1", x1), ("y1", y1), ("x2", x2), ("y2", y2),) 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]))) + segment_ids = [SEGMENT_MAP[a] for a in hour[CONF_SEGMENTS]] + cg.add(var.add_hour(hour[CONF_HOUR], cg.std_vector.template(cg.uint16).new(segment_ids))) + # segments_vector_ptr = segments_vector_ptr.new(segment_ids) + # foo = await cg.get_variable(config[DATA_VECTOR_SEGMENTS]) + # foo = cg.std_vector.template(cg.uint16).new(segment_ids) + + # del foo - # 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])) - - + segment_ids = [SEGMENT_MAP[a] for a in minute[CONF_SEGMENTS]] + cg.add(var.add_minute(minute[CONF_MINUTE], minute[CONF_HOUR_OFFSET], cg.std_vector.template(cg.uint16).new(segment_ids))) + # segments_vector_ptr = segments_vector_ptr.new(segment_ids) + # segments_vector_ptr = cg.new_Pvariable(config[DATA_VECTOR_SEGMENTS], segment_ids) + # foo = await cg.get_variable(config[DATA_VECTOR_SEGMENTS]) + # foo = cg.std_vector.template(cg.uint16).new(segment_ids) + # segments_vector = cg.new_Pvariable(core.ID(None, type=cg.std_vector.template(cg.uint16)), segment_ids) + # del foo 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) + # 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)) - cg.static_const_array(config[DATA_SEGMENT_COORDS], accumulator) - print(SEGMENT_MAP) + # 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]): - # 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, 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]))) + + # for idx, minute in enumerate(config[CONF_MINUTES]): + # cg.add(var.add_hour_offset(minute[CONF_MINUTE], minute[CONF_HOUR_OFFSET])) - 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 index e40a452..848b636 100644 --- a/components/wordclock/wordclock.cpp +++ b/components/wordclock/wordclock.cpp @@ -4,302 +4,202 @@ 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(); + this->valid_time = this->time->now().is_valid(); + if (!this->valid_time) { + this->start_idle_animation(); + } + // this->valid_time = true; } 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 (!time.is_valid()) { if (this->valid_time) { - ESP_LOGD("loop", "time is not valid [%02i:%02i:%02i]", time.hour, time.minute, time.second); + ESP_LOGD("wordclock.cpp", "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) { + ESP_LOGD("wordclock.cpp", "time is valid [%02i:%02i:%02i]", time.hour, time.minute, time.second); 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)); - + bool dirty = false; + int8_t minute = this->find_minute(time.minute); - int8_t hour = this->find_hour((time.hour + this->hour_offsets->at(minute)) % 24); + int8_t hour = this->find_hour((time.hour + this->minutes->at(minute).hour_offset) % 24); - for (uint16_t segment_idx : *this->static_segments){ - this->draw_segment(segment_idx); + if (hour != this->current_hour) { + this->current_hour = hour; + dirty = true; + } + if (minute != this->current_minute) { + this->current_minute = minute; + dirty = true; } - for (uint16_t segment_idx : this->minutes->at(minute)){ - this->draw_segment(segment_idx); + if (dirty) { + this->previous_segments->clear(); + for (uint16_t segment_idx : *this->current_segments) { + this->previous_segments->push_back(segment_idx); + } + // std::sort(this->previous_segments->begin(), this->previous_segments->end()); + + // std::sort(s.begin(), s.end(), [](int a, int b) + // { + // return a > b; + // }); + + this->current_segments->clear(); + for (uint16_t segment_idx : *this->static_segments) { + this->current_segments->push_back(segment_idx); + // this->draw_segment(segment_idx); + } + + for (uint16_t segment_idx : *(this->minutes->at(minute).segments)) { + this->current_segments->push_back(segment_idx); + // this->draw_segment(segment_idx); + } + + for (uint16_t segment_idx : *(this->hours->at(hour).segments)) { + this->current_segments->push_back(segment_idx); + // this->draw_segment(segment_idx); + } + std::sort(this->current_segments->begin(), this->current_segments->end()); + + this->find_difference(this->previous_segments, this->current_segments); + // ESP_LOGD("wordclock.cpp", "reset"); + this->on_transformer->reset(); + this->off_transformer->reset(); } - for (uint16_t segment_idx : this->hours->at(hour)){ - this->draw_segment(segment_idx); + // Color on = this->on_color * this->on_transformer->apply().value_or(255); + // Color off = this->off_color * this->off_transformer->apply().value_or(255); + uint8_t transition_progress = this->on_transformer->apply().value_or(255); + // uint8_t on_progress = this->on_transformer->apply().value_or(255); + // uint8_t off_progress = this->off_transformer->apply().value_or(255); + // ESP_LOGD("wordclock.cpp", "off progress [%d]", off_progress); + Color added_color = this->off_color.gradient(this->on_color, transition_progress); + Color removed_color = this->on_color.gradient(this->off_color, transition_progress); + // ESP_LOGD("wordclock.cpp", "transition progress [%d], added [0x%06x], removed [0x%06x]", transition_progress, added_color.raw_32, removed_color.raw_32); + for (uint16_t segment_idx : *this->added_segments) { + // ESP_LOGD("wordclock.cpp", "on [%d]", segment_idx); + this->draw_segment(segment_idx, added_color); + // this->draw_segment(segment_idx, on_progress); + } + for (uint16_t segment_idx : *this->removed_segments) { + // ESP_LOGD("wordclock.cpp", "off [%d]", segment_idx); + this->draw_segment(segment_idx, removed_color); + // this->draw_segment(segment_idx, off_progress); } - // 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::find_difference(std::vector *a_vec, std::vector *b_vec) { + // for (uint16_t segment_idx : a) { + // this->current_segments->push_back(segment_idx); + // } + this->added_segments->clear(); + this->removed_segments->clear(); + + auto a = a_vec->begin(); + auto b = b_vec->begin(); + + while (!(a == a_vec->end() && b == b_vec->end())) { + if (a == a_vec->end()) { this->added_segments-> push_back(*b); b++; } + else if (b == b_vec->end()) { this->removed_segments->push_back(*a); a++; } + else if (*a > *b) { this->added_segments-> push_back(*b); b++; } + else if (*a < *b) { this->removed_segments->push_back(*a); a++; } + else if (*a == *b) { a++; b++; } + } } -// void Wordclock::set_writer(display_writer_t &&writer) { -// this->writer_ = writer; + +int8_t Wordclock::find_hour(uint8_t target_value) { + std::vector *elements = this->hours; + for (int i = 0; i < elements->size(); i++) { + if (elements->at(i).hour == target_value) { + return i; + } else if (elements->at(i).hour > target_value) { + return i - 1; + } + } + return elements->size() - 1; + // uint16_t last_defined_index = -1; + // for (int i = 0; i < elements->size(); i++) { + // if (elements->at(i).size()) { + // last_defined_index = i; + // } + // if (i >= target_value) break; + // } + // return last_defined_index; + +} + +int8_t Wordclock::find_minute(uint8_t target_value) { + std::vector *elements = this->minutes; + for (int i = 0; i < elements->size(); i++) { + if (elements->at(i).minute == target_value) { + return i; + } else if (elements->at(i).minute > target_value) { + return i - 1; + } + } + return elements->size() - 1; +} + +void Wordclock::setup_transitions(uint32_t milliseconds) { + this->on_transformer->setup(0, this->brightness, milliseconds); + this->off_transformer->setup(this->brightness, 0, milliseconds); +} + + + +// Wordclock::Wordclock() : PollingComponent(1000) { // } -void Wordclock::add_segment(SegmentCoords segment) { - // if (!this->segments) { - - // } +Wordclock::Wordclock( + esphome::time::RealTimeClock *time, + esphome::addressable_light::AddressableLightDisplay *display, + esphome::light::AddressableLightState *light_state) { - this->segments->push_back(segment); - // this->writer_ = writer; + this->time = time; + this->display = display; + this->light_state = light_state; + + this->light_state->set_default_transition_length(1); + + this->minutes = new std::vector(); + this->hours = new std::vector(); + + this->segments = new std::vector(); + this->static_segments = new std::vector(); + + this->previous_segments = new std::vector(); + this->current_segments = new std::vector(); + + this->added_segments = new std::vector(); + this->removed_segments = new std::vector(); + + this->on_transformer = new BrightnessTransitionTransformer(); + // this->on_transformer->setup(0, this->brightness, 700); + this->off_transformer = new BrightnessTransitionTransformer(); + // this->off_transformer->setup(this->brightness, 0, 700); + +// wordclock_wordclock->add_segment(wordclock::SegmentCoords{ +// .x1 = 0, +// .x2 = 4, +// .y1 = 8, +// .y2 = 8, +// }); + + // this->hour_offsets = new std::vector(60, 0); + // this->minutes = new std::vector>(60, std::vector()); + // this->hours = new std::vector>(24, std::vector()); } - -// 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 index 42b98df..226efc0 100644 --- a/components/wordclock/wordclock.h +++ b/components/wordclock/wordclock.h @@ -1,227 +1,218 @@ #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 Minute { + uint8_t minute; + uint8_t hour_offset; + std::vector *segments; +}; +struct Hour { + uint8_t hour; + std::vector *segments; +}; struct SegmentCoords { uint16_t x1; - uint16_t x2; uint16_t y1; + uint16_t x2; uint16_t y2; }; +class BrightnessTransformer { +public: + virtual ~BrightnessTransformer() = default; + + void setup(const uint8_t start_values, const uint8_t target_values, uint32_t length) { + this->start_time_ = millis(); + this->length_ = length; + this->start_values_ = start_values; + this->target_values_ = target_values; + this->start(); + } + + /// Indicates whether this transformation is finished. + virtual bool is_finished() { return this->get_progress_() >= 1.0f; } + + /// This will be called before the transition is started. + virtual void start() {} + + /// This will be called while the transformer is active to apply the transition to the light. Can either write to the + /// light directly, or return LightColorValues that will be applied. + virtual optional apply() = 0; + + /// This will be called after transition is finished. + virtual void stop() {} + + void reset() { + this->start_time_ = millis(); + } + + const uint8_t &get_start_values() const { return this->start_values_; } + const uint8_t &get_target_values() const { return this->target_values_; } + +protected: + /// The progress of this transition, on a scale of 0 to 1. + float get_progress_() { + uint32_t now = esphome::millis(); + if (now < this->start_time_) + return 0.0f; + if (now >= this->start_time_ + this->length_) + return 1.0f; + return clamp((now - this->start_time_) / float(this->length_), 0.0f, 1.0f); + } + + uint32_t start_time_; + uint32_t length_; + uint8_t start_values_; + uint8_t target_values_; +}; + +class BrightnessTransitionTransformer : public BrightnessTransformer { +public: + optional apply() override { + float v = BrightnessTransitionTransformer::smoothed_progress(this->get_progress_()); + return esphome::lerp(v, this->start_values_, this->target_values_); + } +protected: + // This looks crazy, but it reduces to 6x^5 - 15x^4 + 10x^3 which is just a smooth sigmoid-like + // transition from 0 to 1 on x = [0, 1] + static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); } +}; + 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 { - // } +public: + // Wordclock(); + Wordclock( + esphome::time::RealTimeClock *time, + esphome::addressable_light::AddressableLightDisplay *display, + esphome::light::AddressableLightState *light_state + ); + + void setup(); + void update(); + // void setup_color(uint8_t brightness, Color on_color, Color off_color); + void set_brightness(uint8_t brightness) { +this->brightness = brightness; + } + void set_on_color(Color on_color) { + this->on_color = on_color; + } + void set_off_color(Color off_color) { + this->off_color = off_color; + } + void setup_transitions(uint32_t milliseconds); - // void set_writer(display_writer_t &&writer); + void add_segment(SegmentCoords segment) { + this->segments->push_back(segment); + } - // 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 add_static(uint16_t segment_id) { + this->static_segments->push_back(segment_id); + } - // void display_minutes(int minutes, const CRGB& color) { - // int five_minute_chunk = minutes / 5; + void add_hour(uint8_t hour, std::vector *segments_ptr) { + this->hours->push_back(Hour{ + .hour = hour, + .segments = segments_ptr, + }); + } - // 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 add_minute(uint8_t minute, uint8_t hour_offset, std::vector *segments_ptr) { + this->minutes->push_back(Minute{ + .minute = minute, + .hour_offset = hour_offset, + .segments = segments_ptr, + }); + } - // void display_hour(int hour, int minutes, const CRGB& color) { - // int five_minute_chunk = minutes / 5; - // if (five_minute_chunk > 6) { - // hour += 1; - // } + // void add_hour_segment(uint8_t index, uint16_t segment_id) { + // this->hours->at(index).segments->push_back(segment_id); + // } + // void add_minute_segment(uint8_t index, uint16_t segment_id) { + // this->minutes->at(index).segments->push_back(segment_id); + // } + // void add_hour(uint8_t index, std::vector *segments); + // void add_minute(uint8_t index, std::vector *segments); + // void add_hour_offset(uint8_t index, int8_t offset) { + // this->minutes->at(index).hour_offset = offset; + // } +protected: + // ESPHome Components + esphome::time::RealTimeClock *time; + esphome::light::AddressableLightState *light_state; + esphome::addressable_light::AddressableLightDisplay *display; - // 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; - // } - // } + // Time related state + bool valid_time{false}; + int8_t current_hour{-1}; + int8_t current_minute{-1}; - // 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(); - // } + // Segments Configuration + std::vector *segments; + std::vector *static_segments; + std::vector *minutes; + std::vector *hours; + + BrightnessTransitionTransformer *off_transformer; + BrightnessTransitionTransformer *on_transformer; + + std::vector *added_segments; + std::vector *removed_segments; + std::vector *current_segments; + std::vector *previous_segments; + + // Color + // Color get_on_color() { + // return this->on_color * this->brightness; + // } + // Color get_off_color() { + // return this->off_color * this->brightness; + // } + Color on_color {Color(0xFFFFFF)}; + Color off_color {Color(0x000000)}; + uint8_t brightness{255}; + + // Utils + int8_t find_hour(uint8_t target_value); + int8_t find_minute(uint8_t target_value); + void find_difference(std::vector *a_vec, std::vector *b_vec); + // void draw_segment(uint16_t segment_id) { + // this->draw_segment(segment_id, this->brightness); + // } + void draw_segment(uint16_t segment_id, uint8_t brightness) { + this->draw_segment(segment_id, this->on_color * brightness); + } + void draw_segment(uint16_t segment_id, Color color) { + SegmentCoords s = this->segments->at(segment_id); + // ESP_LOGD("wordclock.cpp", "brightness[%d] * color[%06x] = %06x", brightness, color.raw_32, (color * brightness).raw_32); + this->display->line(s.x1, s.y1, s.x2, s.y2, color); + } + void start_idle_animation() { + this->display->fill(this->off_color); + this->display->set_enabled(false); + this->light_state->turn_on().set_effect("random_twinkle").perform(); + } + + void end_idle_animation() { + this->light_state->turn_off().perform(); + this->display->set_enabled(true); + this->display->fill(this->off_color); + this->previous_segments->clear(); + this->current_segments->clear(); + } - - // 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; - + // std::vector> *minutes; + // std::vector> *hours; + // std::vector *hour_offsets; }; - - } // namespace wordclock } // namespace esphome diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..5793ccb --- /dev/null +++ b/flake.lock @@ -0,0 +1,64 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1669833724, + "narHash": "sha256-/HEZNyGbnQecrgJnfE8d0WC5c1xuPSD2LUpB6YXlg4c=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "4d2b37a84fad1091b9de401eb450aae66f1a741e", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "22.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "systems": "systems", + "utils": "utils" + } + }, + "systems": { + "locked": { + "lastModified": 1680978846, + "narHash": "sha256-Gtqg8b/v49BFDpDetjclCYXm8mAnTrUzR0JnE2nv5aw=", + "owner": "nix-systems", + "repo": "x86_64-linux", + "rev": "2ecfcac5e15790ba6ce360ceccddb15ad16d08a8", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "x86_64-linux", + "type": "github" + } + }, + "utils": { + "inputs": { + "systems": [ + "systems" + ] + }, + "locked": { + "lastModified": 1681202837, + "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "cfacdce06f30d2b68473a46042957675eebb3401", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/image_test_3leds.yaml b/susannes_wordclock.yaml similarity index 61% rename from image_test_3leds.yaml rename to susannes_wordclock.yaml index f9d53b1..e47a2d5 100644 --- a/image_test_3leds.yaml +++ b/susannes_wordclock.yaml @@ -1,13 +1,5 @@ esphome: - name: "display-thing" - # on_boot: - # priority: 600 - # # ... - # then: - # - light.turn_on: - # id: neopixel_strip_1 - # brightness: 100% - # effect: random_twinkle + name: "wordclock" esp8266: board: d1_mini @@ -19,7 +11,6 @@ external_components: type: local path: components components: [ wordclock ] - # components: [ wordclock, addressable_light ] wifi: ssid: !secret wifi_ssid @@ -42,22 +33,12 @@ 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 @@ -67,28 +48,15 @@ light: pin: GPIO3 num_leds: 198 restore_mode: ALWAYS_OFF - # default_transition_length: 0.7s - # color_correct: [100%, 100%, 100%] + # default_transition_length: 0.0s + # default_transition_length: 0.1s 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 @@ -104,20 +72,35 @@ display: 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]; + return (y * 18 + mapping_even[x]); } else { - return (y * 18 + mapping_odd[x]); // mapping_odd[y]; + return (y * 18 + mapping_odd[x]); } - # lambda: |- - # //ESP_LOGE("lambda", "display_ptr: %i", &it); - # //it.fill(Color(0xff00ff)); - # //it.line(0,0,0,0, Color(0x00FF00)); +# color: +# - id: col_on +# hex: "CC0000" +# - id: col_off +# hex: "005500" + +color: + - id: col_on + red: 90% + green: 50% + blue: 0% + - id: col_off + red: 20% + green: 20% + blue: 30% wordclock: time_id: current_time display_id: led_matrix_display addressable_light_id: neopixel_strip_1 + brightness: 100% + color_on: col_on + color_off: col_off + update_interval: 16ms static_segments: ["IT", "IS"] segments: - {name: "IT", line: {x1: 0, x2: 1, y1: 0}} @@ -182,44 +165,5 @@ wordclock: - {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