feat: add transitions and clean up code.

This commit is contained in:
Philip Stark 2023-05-12 05:07:21 +02:00
parent 7f091409cf
commit dd1408c5cb
8 changed files with 533 additions and 1099 deletions

View file

@ -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))

View file

@ -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

View file

@ -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

View file

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

View file

@ -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<uint16_t> *minute = this->find_minute(time.minute);
// std::vector<uint16_t> *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<uint16_t> *a_vec, std::vector<uint16_t> *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<Hour> *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<Minute> *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<Minute>();
this->hours = new std::vector<Hour>();
this->segments = new std::vector<SegmentCoords>();
this->static_segments = new std::vector<uint16_t>();
this->previous_segments = new std::vector<uint16_t>();
this->current_segments = new std::vector<uint16_t>();
this->added_segments = new std::vector<uint16_t>();
this->removed_segments = new std::vector<uint16_t>();
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<int8_t>(60, 0);
// this->minutes = new std::vector<std::vector<uint16_t>>(60, std::vector<uint16_t>());
// this->hours = new std::vector<std::vector<uint16_t>>(24, std::vector<uint16_t>());
}
// std::vector<uint16_t> * Wordclock::find_hour(uint8_t hour) {
// std::vector<uint16_t> *empty_vector; // = new std::vector<uint16_t>();
// 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<uint16_t> * Wordclock::find_minute(uint8_t minute) {
// std::vector<uint16_t> *empty_vector;// = new std::vector<uint16_t>();
// 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<uint16_t> *segments) {
for (uint16_t i : *segments){
this->hours->at(hour).push_back(i);
}
}
void Wordclock::add_minute(uint8_t minute, std::vector<uint16_t> *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<uint16_t> *minutes, std::vector<uint16_t> *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<uint16_t> minutes[60];
// // std::vector<uint16_t> hours[24];
// }
Wordclock::Wordclock()
: PollingComponent(1000) {
// this->minutes = std::vector<std::vector<uint16_t>>();
// // for (int i=0; i<60; i++) this->minutes.push_back(std::vector<uint16_t>());
// this->hours = std::vector<std::vector<uint16_t>>();
// // for (int i=0; i<24; i++) this->minutes.push_back(std::vector<uint16_t>());
// this->segments = std::vector<SegmentCoords>();
}
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<int8_t>(60, 0);
this->minutes = new std::vector<std::vector<uint16_t>>(60, std::vector<uint16_t>());
// for (int i=0; i<60; i++) this->minutes->push_back(std::vector<uint16_t>());
this->hours = new std::vector<std::vector<uint16_t>>(24, std::vector<uint16_t>());
// for (int i=0; i<24; i++) this->hours->push_back(std::vector<uint16_t>());
this->segments = new std::vector<SegmentCoords>();
this->static_segments = new std::vector<uint16_t>();
}
} // namespace wordclock
} // namespace esphome

View file

@ -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<void(esphome::Dieplay::Display &)>;
struct Minute {
uint8_t minute;
uint8_t hour_offset;
std::vector<uint16_t> *segments;
};
struct Hour {
uint8_t hour;
std::vector<uint16_t> *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<uint8_t> 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<uint8_t> 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<uint16_t> *segments);
void add_minute(uint8_t index, std::vector<uint16_t> *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<uint16_t> *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<uint16_t> *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<uint16_t> *segments);
// void add_minute(uint8_t index, std::vector<uint16_t> *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<SegmentCoords> *segments;
std::vector<uint16_t> *static_segments;
std::vector<Minute> *minutes;
std::vector<Hour> *hours;
BrightnessTransitionTransformer *off_transformer;
BrightnessTransitionTransformer *on_transformer;
std::vector<uint16_t> *added_segments;
std::vector<uint16_t> *removed_segments;
std::vector<uint16_t> *current_segments;
std::vector<uint16_t> *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<uint16_t> *a_vec, std::vector<uint16_t> *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<uint16_t> * find_hour(uint8_t hour);
// std::vector<uint16_t> * find_minute(uint8_t minute);
std::vector<std::vector<uint16_t>> *minutes;
std::vector<std::vector<uint16_t>> *hours;
std::vector<uint16_t> *static_segments;
std::vector<int8_t> *hour_offsets;
void start_idle_animation();
void end_idle_animation();
// uint16_t **minutes;
// uint16_t **hours;
bool valid_time{true};
std::vector<SegmentCoords> *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<std::vector<uint16_t>> *minutes;
// std::vector<std::vector<uint16_t>> *hours;
// std::vector<int8_t> *hour_offsets;
};
} // namespace wordclock
} // namespace esphome