Compare commits
16 commits
main
...
feature/fi
| Author | SHA1 | Date | |
|---|---|---|---|
| c54ff63f89 | |||
| 47f31301dd | |||
| 587a968451 | |||
| 4962f344e3 | |||
| e4bdcf71dc | |||
| 03a012650f | |||
| 8f90d6b1ca | |||
| 4e9041aec5 | |||
| 3e31b32853 | |||
| 519b36bfed | |||
| 70458d4ed9 | |||
| b712b90be1 | |||
| dd1408c5cb | |||
| 7f091409cf | |||
| 5b0930d616 | |||
| 4c612fc462 |
19 changed files with 1905 additions and 0 deletions
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Gitignore settings for ESPHome
|
||||||
|
# This is an example and may include too much for your use-case.
|
||||||
|
# You can modify this file to suit your needs.
|
||||||
|
/.esphome/
|
||||||
|
/secrets.yaml
|
||||||
|
/secrets.*.yaml
|
||||||
|
!/secrets.example.yaml
|
||||||
|
/.pio
|
||||||
|
/.vscode
|
||||||
|
__pycache__
|
||||||
|
/_ignored
|
||||||
|
esp32_test.code-workspace
|
||||||
|
case/d1_mini_esp32.FCStd1
|
||||||
BIN
case/d1_mini_esp32-Body.stl
Normal file
BIN
case/d1_mini_esp32-Body.stl
Normal file
Binary file not shown.
BIN
case/d1_mini_esp32.FCStd
Normal file
BIN
case/d1_mini_esp32.FCStd
Normal file
Binary file not shown.
153
components/wordclock/__init__.py
Normal file
153
components/wordclock/__init__.py
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from esphome import core
|
||||||
|
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,
|
||||||
|
CONF_RAW_DATA_ID,
|
||||||
|
CONF_TYPE,
|
||||||
|
CONF_TIME_ID,
|
||||||
|
CONF_SEGMENTS,
|
||||||
|
CONF_ADDRESSABLE_LIGHT_ID,
|
||||||
|
CONF_HOUR,
|
||||||
|
CONF_HOURS,
|
||||||
|
CONF_MINUTE,
|
||||||
|
CONF_MINUTES,
|
||||||
|
CONF_BRIGHTNESS,
|
||||||
|
CONF_UPDATE_INTERVAL,
|
||||||
|
)
|
||||||
|
from esphome.core import CORE, HexInt
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
CONF_DISPLAY_ID = "display_id"
|
||||||
|
|
||||||
|
CONF_LINE_START_X = "x1"
|
||||||
|
CONF_LINE_START_Y = "y1"
|
||||||
|
|
||||||
|
CONF_LINE_END_X = "x2"
|
||||||
|
CONF_LINE_END_Y = "y2"
|
||||||
|
|
||||||
|
CONF_LINE = "line"
|
||||||
|
|
||||||
|
CONF_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_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_(
|
||||||
|
"Wordclock", cg.PollingComponent
|
||||||
|
)
|
||||||
|
|
||||||
|
WORDCLOCK_SEGMENT_SCHEMA = {
|
||||||
|
cv.Required(CONF_NAME): cv.string_strict,
|
||||||
|
cv.Required(CONF_LINE): {
|
||||||
|
cv.Required(CONF_LINE_START_X): cv.int_,
|
||||||
|
cv.Required(CONF_LINE_START_Y): cv.int_,
|
||||||
|
cv.Required(CONF_LINE_END_X): cv.int_,
|
||||||
|
cv.Optional(CONF_LINE_END_Y): cv.int_,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
WORDCLOCK_HOUR_SCHEMA = {
|
||||||
|
cv.Required(CONF_HOUR): cv.uint16_t,
|
||||||
|
cv.Required(CONF_SEGMENTS): cv.ensure_list(cv.string_strict),
|
||||||
|
}
|
||||||
|
|
||||||
|
WORDCLOCK_MINUTE_SCHEMA = {
|
||||||
|
cv.Required(CONF_MINUTE): cv.uint16_t,
|
||||||
|
cv.Required(CONF_HOUR_OFFSET): cv.int_range(-1,1),
|
||||||
|
cv.Required(CONF_SEGMENTS): cv.ensure_list(cv.string_strict),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
WORDCLOCK_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(Wordclock),
|
||||||
|
cv.Required(CONF_DISPLAY_ID): cv.use_id(
|
||||||
|
display.DisplayBuffer
|
||||||
|
),
|
||||||
|
cv.Required(CONF_TIME_ID): cv.use_id(
|
||||||
|
time.RealTimeClock
|
||||||
|
),
|
||||||
|
cv.Required(CONF_ADDRESSABLE_LIGHT_ID): cv.use_id(
|
||||||
|
light.AddressableLightState
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_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_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.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): cv.declare_id(cg.std_vector.template(cg.uint16)),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = WORDCLOCK_SCHEMA
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
wrapped_display = await cg.get_variable(config[CONF_DISPLAY_ID])
|
||||||
|
wrapped_time = await cg.get_variable(config[CONF_TIME_ID])
|
||||||
|
wrapped_light_state = await cg.get_variable(config[CONF_ADDRESSABLE_LIGHT_ID])
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID], wrapped_time, wrapped_display, wrapped_light_state)
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
cg.add(var.setup_transitions(config[CONF_FADE_LENGTH]))
|
||||||
|
cg.add(var.set_brightness(int(config[CONF_BRIGHTNESS] * 255)))
|
||||||
|
|
||||||
|
SEGMENT_MAP = dict()
|
||||||
|
for idx, segment in enumerate(config[CONF_SEGMENTS]):
|
||||||
|
SEGMENT_MAP[segment[CONF_NAME]] = idx
|
||||||
|
line = segment[CONF_LINE]
|
||||||
|
|
||||||
|
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", x1), ("y1", y1), ("x2", x2), ("y2", y2),)
|
||||||
|
cg.add(var.add_segment(exp))
|
||||||
|
|
||||||
|
if CONF_WORDCLOCK_STATIC_SEGMENTS in config:
|
||||||
|
for segment_name in config[CONF_WORDCLOCK_STATIC_SEGMENTS]:
|
||||||
|
cg.add(var.add_static(SEGMENT_MAP[segment_name]))
|
||||||
|
|
||||||
|
for idx, hour in enumerate(config[CONF_HOURS]):
|
||||||
|
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)))
|
||||||
|
|
||||||
|
for idx, minute in enumerate(config[CONF_MINUTES]):
|
||||||
|
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)))
|
||||||
|
|
||||||
|
await cg.register_component(var, config)
|
||||||
184
components/wordclock/wordclock.cpp
Normal file
184
components/wordclock/wordclock.cpp
Normal file
|
|
@ -0,0 +1,184 @@
|
||||||
|
|
||||||
|
#include "wordclock.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace wordclock {
|
||||||
|
|
||||||
|
void Wordclock::setup() {
|
||||||
|
this->valid_time = this->time->now().is_valid();
|
||||||
|
if (!this->valid_time) {
|
||||||
|
this->start_idle_animation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Wordclock::update() {
|
||||||
|
esphome::time::ESPTime time = this->time->now();
|
||||||
|
|
||||||
|
if (!time.is_valid()) {
|
||||||
|
if (this->valid_time) {
|
||||||
|
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;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool dirty = false;
|
||||||
|
|
||||||
|
int8_t minute = this->find_minute(time.minute);
|
||||||
|
int8_t hour = this->find_hour((time.hour + this->minutes->at(minute).hour_offset) % 24);
|
||||||
|
|
||||||
|
if (hour != this->current_hour) {
|
||||||
|
this->current_hour = hour;
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
if (minute != this->current_minute) {
|
||||||
|
this->current_minute = minute;
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dirty) {
|
||||||
|
this->previous_segments->clear();
|
||||||
|
|
||||||
|
for (uint16_t segment_idx : *this->current_segments) {
|
||||||
|
this->previous_segments->push_back(segment_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->current_segments->clear();
|
||||||
|
|
||||||
|
for (uint16_t segment_idx : *this->static_segments) {
|
||||||
|
this->current_segments->push_back(segment_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint16_t segment_idx : *(this->minutes->at(minute).segments)) {
|
||||||
|
this->current_segments->push_back(segment_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint16_t segment_idx : *(this->hours->at(hour).segments)) {
|
||||||
|
this->current_segments->push_back(segment_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(this->current_segments->begin(), this->current_segments->end());
|
||||||
|
|
||||||
|
this->find_difference(this->previous_segments, this->current_segments);
|
||||||
|
|
||||||
|
this->on_transformer->reset();
|
||||||
|
this->off_transformer->reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
this->current_position = 0;
|
||||||
|
uint8_t transition_progress = this->on_transformer->apply().value_or(255);
|
||||||
|
Color removed_color = this->on_color.gradient(this->off_color, transition_progress);
|
||||||
|
|
||||||
|
for (uint16_t segment_idx : *this->removed_segments) {
|
||||||
|
this->disable_segment_effect(segment_idx, this->off_color, transition_progress);
|
||||||
|
}
|
||||||
|
for (uint16_t segment_idx : *this->added_segments) {
|
||||||
|
this->enable_segment_effect(segment_idx, this->off_color, transition_progress);
|
||||||
|
}
|
||||||
|
for (uint16_t segment_idx : *this->staying_segments) {
|
||||||
|
this->apply_segment_effect(segment_idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Wordclock::find_difference(std::vector<uint16_t> *a_vec, std::vector<uint16_t> *b_vec) {
|
||||||
|
this->added_segments->clear();
|
||||||
|
this->removed_segments->clear();
|
||||||
|
this->staying_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) { this->staying_segments->push_back(*a); a++; b++; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Color Wordclock::get_next_color_base_(uint32_t position, uint32_t speed, uint16_t width, const Color ¤t_color) {
|
||||||
|
esphome::light::ESPHSVColor hsv;
|
||||||
|
hsv.value = 255;
|
||||||
|
hsv.saturation = 240;
|
||||||
|
hsv.hue = ((millis() * speed + (position * (0xFFFF / width))) % 0xFFFF) >> 8;
|
||||||
|
return hsv.to_rgb();
|
||||||
|
}
|
||||||
|
|
||||||
|
Color Wordclock::get_next_color(uint32_t position, const Color ¤t_color) {
|
||||||
|
if (this->effect_active) {
|
||||||
|
uint32_t speed_ = 3;
|
||||||
|
uint16_t width_ = 100; // Original width_ = 50
|
||||||
|
return get_next_color_base_(position, speed_, width_, current_color);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Color(this->on_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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, 255, milliseconds);
|
||||||
|
this->off_transformer->setup(255, 0, milliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
Wordclock::Wordclock(
|
||||||
|
esphome::time::RealTimeClock *time,
|
||||||
|
esphome::addressable_light::AddressableLightDisplay *display,
|
||||||
|
esphome::light::AddressableLightState *light_state) {
|
||||||
|
|
||||||
|
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->staying_segments = new std::vector<uint16_t>();
|
||||||
|
|
||||||
|
this->on_transformer = new BrightnessTransitionTransformer();
|
||||||
|
this->off_transformer = new BrightnessTransitionTransformer();
|
||||||
|
}
|
||||||
|
} // namespace wordclock
|
||||||
|
} // namespace esphome
|
||||||
223
components/wordclock/wordclock.h
Normal file
223
components/wordclock/wordclock.h
Normal file
|
|
@ -0,0 +1,223 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome.h"
|
||||||
|
// #include "esphome/components/sensor/sensor.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace wordclock {
|
||||||
|
|
||||||
|
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 y1;
|
||||||
|
uint16_t x2;
|
||||||
|
uint16_t y2;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BrightnessTransitionTransformer {
|
||||||
|
public:
|
||||||
|
virtual ~BrightnessTransitionTransformer() = 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();
|
||||||
|
}
|
||||||
|
virtual bool is_finished() { return this->get_progress_() >= 1.0f; }
|
||||||
|
virtual void start() {}
|
||||||
|
// virtual optional<uint8_t> apply() = 0;
|
||||||
|
virtual optional<uint8_t> apply() {
|
||||||
|
float v = BrightnessTransitionTransformer::smoothed_progress(this->get_progress_());
|
||||||
|
return esphome::lerp(v, this->start_values_, this->target_values_);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); }
|
||||||
|
|
||||||
|
uint32_t start_time_;
|
||||||
|
uint32_t length_;
|
||||||
|
uint8_t start_values_;
|
||||||
|
uint8_t target_values_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Wordclock : public esphome::PollingComponent {
|
||||||
|
public:
|
||||||
|
Wordclock(
|
||||||
|
esphome::time::RealTimeClock *time,
|
||||||
|
esphome::addressable_light::AddressableLightDisplay *display,
|
||||||
|
esphome::light::AddressableLightState *light_state
|
||||||
|
);
|
||||||
|
|
||||||
|
void setup();
|
||||||
|
void update();
|
||||||
|
void set_brightness(uint8_t brightness) {
|
||||||
|
this->brightness = brightness;
|
||||||
|
}
|
||||||
|
bool get_effect_active() { return this->effect_active; }
|
||||||
|
void set_effect_active(bool x) { this->effect_active = x; }
|
||||||
|
|
||||||
|
bool get_effect_width() { return this->effect_width; }
|
||||||
|
void set_effect_width(bool x) { this->effect_width = x; }
|
||||||
|
|
||||||
|
bool get_effect_speed() { return this->effect_speed; }
|
||||||
|
void set_effect_speed(bool x) { this->effect_speed = x; }
|
||||||
|
|
||||||
|
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 add_segment(SegmentCoords segment) {
|
||||||
|
this->segments->push_back(segment);
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_static(uint16_t segment_id) {
|
||||||
|
this->static_segments->push_back(segment_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_hour(uint8_t hour, std::vector<uint16_t> *segments_ptr) {
|
||||||
|
this->hours->push_back(Hour{
|
||||||
|
.hour = hour,
|
||||||
|
.segments = segments_ptr,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Color on_color {Color(0xFFFFFF)};
|
||||||
|
Color off_color {Color(0x000000)};
|
||||||
|
uint8_t brightness{255};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// ESPHome Components
|
||||||
|
esphome::time::RealTimeClock *time;
|
||||||
|
esphome::light::AddressableLightState *light_state;
|
||||||
|
esphome::addressable_light::AddressableLightDisplay *display;
|
||||||
|
|
||||||
|
// Time related state
|
||||||
|
bool valid_time{false};
|
||||||
|
int8_t current_hour{-1};
|
||||||
|
int8_t current_minute{-1};
|
||||||
|
|
||||||
|
// 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> *staying_segments;
|
||||||
|
|
||||||
|
std::vector<uint16_t> *current_segments;
|
||||||
|
std::vector<uint16_t> *previous_segments;
|
||||||
|
|
||||||
|
uint32_t current_position{0};
|
||||||
|
bool effect_active{true};
|
||||||
|
uint32_t effect_speed{12};
|
||||||
|
uint16_t effect_width{50};
|
||||||
|
Color get_next_color_base_(uint32_t position, uint32_t speed, uint16_t width, const Color ¤t_color);
|
||||||
|
Color get_next_color(uint32_t position, const Color ¤t_color);
|
||||||
|
|
||||||
|
|
||||||
|
// 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 segment_effect_base(uint16_t segment_id, bool to_effect, Color base_color, uint8_t transition_progress) {
|
||||||
|
SegmentCoords s = this->segments->at(segment_id);
|
||||||
|
int x1 = s.x1; int y1 = s.y1; int x2 = s.x2; int y2 = s.y2;
|
||||||
|
Color color_to_draw;
|
||||||
|
Color effect_color;
|
||||||
|
|
||||||
|
const int32_t dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1;
|
||||||
|
const int32_t dy = -abs(y2 - y1), sy = y1 < y2 ? 1 : -1;
|
||||||
|
int32_t err = dx + dy;
|
||||||
|
while (true) {
|
||||||
|
effect_color = this->get_next_color(x1 + y1, Color(0)) * this->brightness;
|
||||||
|
if (to_effect) {
|
||||||
|
color_to_draw = base_color.gradient(effect_color, transition_progress);
|
||||||
|
} else {
|
||||||
|
color_to_draw = effect_color.gradient(base_color, transition_progress);
|
||||||
|
}
|
||||||
|
this->display->draw_pixel_at(x1, y1, color_to_draw);
|
||||||
|
if (x1 == x2 && y1 == y2) break;
|
||||||
|
int32_t e2 = 2 * err;
|
||||||
|
if (e2 >= dy) {
|
||||||
|
err += dy;
|
||||||
|
x1 += sx;
|
||||||
|
}
|
||||||
|
if (e2 <= dx) {
|
||||||
|
err += dx;
|
||||||
|
y1 += sy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void enable_segment_effect(uint16_t segment_id, Color off_color, uint8_t transition_progress) {
|
||||||
|
segment_effect_base(segment_id, true, off_color, transition_progress);
|
||||||
|
}
|
||||||
|
void disable_segment_effect(uint16_t segment_id, Color off_color, uint8_t transition_progress) {
|
||||||
|
segment_effect_base(segment_id, false, off_color, transition_progress);
|
||||||
|
}
|
||||||
|
void apply_segment_effect(uint16_t segment_id) {
|
||||||
|
segment_effect_base(segment_id, true, Color(0), 255);
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace wordclock
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
9
devlog.md
Normal file
9
devlog.md
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
# Wordclock Development Log
|
||||||
|
|
||||||
|
|
||||||
|
## Feature "make speed and width of rainbow effect user-configurable" [2023-06-10 17:13]
|
||||||
|
|
||||||
|
[17:16] Step - add attributes, getters, and setters for effect-speed and -width.
|
||||||
|
- [x] add attr/funcs to header file.
|
||||||
|
- [x] add inputs to yaml.
|
||||||
|
-
|
||||||
98
display_test.yaml
Normal file
98
display_test.yaml
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
esphome:
|
||||||
|
name: "display-thing"
|
||||||
|
|
||||||
|
esp8266:
|
||||||
|
board: d1_mini
|
||||||
|
framework:
|
||||||
|
version: recommended
|
||||||
|
|
||||||
|
wifi:
|
||||||
|
ssid: !secret wifi_ssid
|
||||||
|
password: !secret wifi_password
|
||||||
|
ap:
|
||||||
|
ssid: "${devicename}"
|
||||||
|
password: !secret ap_password
|
||||||
|
manual_ip:
|
||||||
|
static_ip: !secret manualip_static_ip
|
||||||
|
gateway: !secret manualip_gateway
|
||||||
|
subnet: !secret manualip_subnet
|
||||||
|
dns1: 1.1.1.1
|
||||||
|
dns2: 1.0.0.1
|
||||||
|
|
||||||
|
api:
|
||||||
|
|
||||||
|
ota:
|
||||||
|
password: "${devicename}"
|
||||||
|
|
||||||
|
logger:
|
||||||
|
# esp8266_store_log_strings_in_flash: false
|
||||||
|
# web_server:
|
||||||
|
# port: 80
|
||||||
|
|
||||||
|
light:
|
||||||
|
- name: NeoPixel Strip 1
|
||||||
|
id: neopixel_strip_1
|
||||||
|
platform: neopixelbus
|
||||||
|
type: GRB
|
||||||
|
variant: WS2812
|
||||||
|
pin: GPIO3
|
||||||
|
num_leds: 199
|
||||||
|
restore_mode: ALWAYS_ON
|
||||||
|
default_transition_length: 0s
|
||||||
|
color_correct: [100%, 100%, 100%]
|
||||||
|
method:
|
||||||
|
type: esp8266_dma
|
||||||
|
|
||||||
|
display:
|
||||||
|
- platform: addressable_light
|
||||||
|
id: led_matrix_display
|
||||||
|
addressable_light_id: neopixel_strip_1
|
||||||
|
width: 18
|
||||||
|
height: 11
|
||||||
|
rotation: 270°
|
||||||
|
update_interval: 16ms
|
||||||
|
pixel_mapper: |-
|
||||||
|
int mapping_even[] = {0, 2, 4, 6,8,10,11,13,15,17, 16,16,16,16, 16,16,16,16};
|
||||||
|
int mapping_odd[] = {17,15,13,11,9, 7, 6, 4, 2, 0, 16,16,16,16, 16,16,16,16};
|
||||||
|
if (x > 9) return -1;
|
||||||
|
if (y % 2 == 0) {
|
||||||
|
return (y * 18 + mapping_even[x]); // mapping_even[y];
|
||||||
|
} else {
|
||||||
|
return (y * 18 + mapping_odd[x]); // mapping_odd[y];
|
||||||
|
//return (y * 18 + 17 - x); // mapping_odd[y];
|
||||||
|
}
|
||||||
|
//if (x % 2 == 0) {
|
||||||
|
// return (x * 8) + y;
|
||||||
|
//}
|
||||||
|
//return (x * 8) + (7 - y);
|
||||||
|
lambda: |-
|
||||||
|
Color red = Color(0xFF0000);
|
||||||
|
Color green = Color(0x00FF00);
|
||||||
|
Color blue = Color(0x0000FF);
|
||||||
|
Color turquoise = Color(0x00FFFF);
|
||||||
|
it.draw_pixel_at(0,0, green);
|
||||||
|
it.draw_pixel_at(10,0, red);
|
||||||
|
it.draw_pixel_at(0,9, turquoise);
|
||||||
|
it.draw_pixel_at(10,9, blue);
|
||||||
|
//it.line(0,0, 11,0, red);
|
||||||
|
//it.line(0,1, 11,1, green);
|
||||||
|
//it.line(0,2, 11,2, blue);
|
||||||
|
//it.line(0,3, 11,3, red);
|
||||||
|
//it.line(0,4, 11,4, green);
|
||||||
|
//it.line(0,5, 11,5, blue);
|
||||||
|
//it.line(0,6, 11,6, red);
|
||||||
|
//it.line(0,7, 11,7, green);
|
||||||
|
//it.line(0,8, 11,8, blue);
|
||||||
|
//it.line(0,9, 11,9, red);
|
||||||
|
//it.line(0,10, 9,10, green);
|
||||||
|
//it.line(0,11, 9,11, blue);
|
||||||
|
// it.line(5,14, 10,14, green);
|
||||||
|
//it.rectangle(0, 0, 17, 10, red);
|
||||||
|
//it.rectangle(1, 1, 16, 9, green);
|
||||||
|
//it.rectangle(2, 2, 15, 8, blue);
|
||||||
|
//it.rectangle(3, 3, 14, 7, red);
|
||||||
|
|
||||||
|
time:
|
||||||
|
- platform: sntp
|
||||||
|
id: current_time
|
||||||
|
timezone: !secret timezone
|
||||||
64
flake.lock
generated
Normal file
64
flake.lock
generated
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1683777345,
|
||||||
|
"narHash": "sha256-V2p/A4RpEGqEZussOnHYMU6XglxBJGCODdzoyvcwig8=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "635a306fc8ede2e34cb3dd0d6d0a5d49362150ed",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"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
|
||||||
|
}
|
||||||
67
flake.nix
Normal file
67
flake.nix
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
{
|
||||||
|
description = "EspHome Word Clock";
|
||||||
|
inputs = {
|
||||||
|
systems.url = "github:nix-systems/x86_64-linux";
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
|
# nixpkgs.url = "github:NixOS/nixpkgs/22.11";
|
||||||
|
utils.url = "github:numtide/flake-utils";
|
||||||
|
utils.inputs.systems.follows = "systems";
|
||||||
|
};
|
||||||
|
outputs = { self, nixpkgs, utils, ... }: utils.lib.eachDefaultSystem (
|
||||||
|
system:
|
||||||
|
let pkgs = import nixpkgs { inherit system; };
|
||||||
|
in {
|
||||||
|
|
||||||
|
# This block here is used when running `nix develop`
|
||||||
|
devShells.default = pkgs.mkShell rec {
|
||||||
|
# Update the name to something that suites your project.
|
||||||
|
name = "esphome-wordclock";
|
||||||
|
|
||||||
|
# build environment dependencies
|
||||||
|
packages = with pkgs; [
|
||||||
|
esphome
|
||||||
|
];
|
||||||
|
|
||||||
|
# Setting up the environment variables you need during development.
|
||||||
|
|
||||||
|
# Todo figure out why I can't use clang on Asahi but can on Darwin
|
||||||
|
# Use "clang++" for most systems but OSX Asahi requires g++ for some reason or a runtime error occurs
|
||||||
|
shellHook = let
|
||||||
|
# This is for an icon that is used below for the command line input below
|
||||||
|
icon = "f121";
|
||||||
|
in ''
|
||||||
|
export PS1="$(echo -e '\u${icon}') {\[$(tput sgr0)\]\[\033[38;5;228m\]\w\[$(tput sgr0)\]\[\033[38;5;15m\]} (${name}) \\$ \[$(tput sgr0)\]"
|
||||||
|
# export COMPILER="clang++"
|
||||||
|
#export COMPILER="g++"
|
||||||
|
echo "test";
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
# This is used when running `nix build`
|
||||||
|
# packages.default = pkgs.llvmPackages_14.stdenv.mkDerivation rec {
|
||||||
|
packages.default = pkgs.esphome.stdenv.mkDerivation rec {
|
||||||
|
name = "esphome-wordclock";
|
||||||
|
version = "2.0.0";
|
||||||
|
src = self;
|
||||||
|
|
||||||
|
# buildInputs = [ pkgs.esphome ];
|
||||||
|
|
||||||
|
# buildPhase = "COMPILER='clang++' make";
|
||||||
|
|
||||||
|
# installPhase = ''
|
||||||
|
# mkdir -p $out/bin;
|
||||||
|
# install -t $out/bin worm
|
||||||
|
# '';
|
||||||
|
|
||||||
|
# meta = with inputs.utils.lib; {
|
||||||
|
# homepage = "https://github.com/icecreammatt/ssu-cs315-worm";
|
||||||
|
# description = ''
|
||||||
|
# Terminal CLI Worm Game
|
||||||
|
# '';
|
||||||
|
# };
|
||||||
|
};
|
||||||
|
# packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello;
|
||||||
|
|
||||||
|
# packages.x86_64-linux.default = self.packages.x86_64-linux.hello;
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
154
image_test.yaml
Normal file
154
image_test.yaml
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
esphome:
|
||||||
|
name: "display-thing"
|
||||||
|
|
||||||
|
esp8266:
|
||||||
|
board: d1_mini
|
||||||
|
framework:
|
||||||
|
version: recommended
|
||||||
|
|
||||||
|
external_components:
|
||||||
|
- source:
|
||||||
|
type: local
|
||||||
|
path: components
|
||||||
|
components: [ wordclock, addressable_light ]
|
||||||
|
|
||||||
|
wifi:
|
||||||
|
ssid: !secret wifi_ssid
|
||||||
|
password: !secret wifi_password
|
||||||
|
ap:
|
||||||
|
ssid: "${devicename}"
|
||||||
|
password: !secret ap_password
|
||||||
|
manual_ip:
|
||||||
|
static_ip: !secret manualip_static_ip
|
||||||
|
gateway: !secret manualip_gateway
|
||||||
|
subnet: !secret manualip_subnet
|
||||||
|
dns1: 1.1.1.1
|
||||||
|
dns2: 1.0.0.1
|
||||||
|
|
||||||
|
api:
|
||||||
|
|
||||||
|
# ota:
|
||||||
|
# password: "${devicename}"
|
||||||
|
|
||||||
|
logger:
|
||||||
|
# esp8266_store_log_strings_in_flash: false
|
||||||
|
web_server:
|
||||||
|
port: 80
|
||||||
|
|
||||||
|
light:
|
||||||
|
- name: NeoPixel Strip 1
|
||||||
|
id: neopixel_strip_1
|
||||||
|
platform: neopixelbus
|
||||||
|
type: GRB
|
||||||
|
variant: WS2812
|
||||||
|
pin: GPIO3
|
||||||
|
num_leds: 199
|
||||||
|
restore_mode: ALWAYS_ON
|
||||||
|
default_transition_length: 0s
|
||||||
|
color_correct: [100%, 100%, 100%]
|
||||||
|
method:
|
||||||
|
type: esp8266_dma
|
||||||
|
|
||||||
|
display:
|
||||||
|
- platform: addressable_light
|
||||||
|
id: led_matrix_display
|
||||||
|
addressable_light_id: neopixel_strip_1
|
||||||
|
width: 18
|
||||||
|
height: 11
|
||||||
|
rotation: 270°
|
||||||
|
update_interval: 16ms
|
||||||
|
auto_clear_enabled: false
|
||||||
|
pixel_mapper: |-
|
||||||
|
int mapping_even[] = {0, 2, 4, 6,8,10,11,13,15,17, 16,16,16,16, 16,16,16,16};
|
||||||
|
int mapping_odd[] = {17,15,13,11,9, 7, 6, 4, 2, 0, 16,16,16,16, 16,16,16,16};
|
||||||
|
if (x > 9) return -1;
|
||||||
|
if (y % 2 == 0) {
|
||||||
|
return (y * 18 + mapping_even[x]); // mapping_even[y];
|
||||||
|
} else {
|
||||||
|
return (y * 18 + mapping_odd[x]); // mapping_odd[y];
|
||||||
|
}
|
||||||
|
# lambda: |-
|
||||||
|
# it.fill(Color(0));
|
||||||
|
# it.image(0, 0, id(foo));
|
||||||
|
# //it.line(0,0,5,5, Color(0x00FF00));
|
||||||
|
|
||||||
|
# image:
|
||||||
|
# - id: foo
|
||||||
|
# file: ./test.png
|
||||||
|
# type: RGB24
|
||||||
|
# resize: 18x11
|
||||||
|
|
||||||
|
|
||||||
|
time:
|
||||||
|
- platform: sntp
|
||||||
|
id: current_time
|
||||||
|
timezone: !secret timezone
|
||||||
|
|
||||||
|
|
||||||
|
wordclock:
|
||||||
|
time_id: current_time
|
||||||
|
display_id: led_matrix_display
|
||||||
|
static_segments: ["IT", "IS"]
|
||||||
|
segments:
|
||||||
|
- {name: "IT", line: {x1: 0, x2: 1, y1: 0}}
|
||||||
|
- {name: "IS", line: {x1: 3, x2: 4, y1: 0}}
|
||||||
|
- {name: "AM", line: {x1: 7, x2: 8, y1: 0}}
|
||||||
|
- {name: "PM", line: {x1: 9, x2: 10, y1: 0}}
|
||||||
|
- {name: "QUARTER", line: {x1: 2, x2: 8, y1: 1}}
|
||||||
|
- {name: "TWENTY", line: {x1: 0, x2: 5, y1: 2}}
|
||||||
|
- {name: "FIVE_MINUTES", line: {x1: 6, x2: 9, y1: 2}}
|
||||||
|
- {name: "HALF", line: {x1: 0, x2: 3, y1: 3}}
|
||||||
|
- {name: "TEN_MINUTES", line: {x1: 5, x2: 7, y1: 3}}
|
||||||
|
- {name: "TO", line: {x1: 9, x2: 10, y1: 3}}
|
||||||
|
- {name: "PAST", line: {x1: 0, x2: 3, y1: 4}}
|
||||||
|
- {name: "NINE", line: {x1: 7, x2: 10, y1: 4}}
|
||||||
|
- {name: "ONE", line: {x1: 0, x2: 2, y1: 5}}
|
||||||
|
- {name: "SIX", line: {x1: 3, x2: 5, y1: 5}}
|
||||||
|
- {name: "THREE", line: {x1: 6, x2: 10, y1: 5}}
|
||||||
|
- {name: "FOUR", line: {x1: 0, x2: 3, y1: 6}}
|
||||||
|
- {name: "FIVE", line: {x1: 4, x2: 7, y1: 6}}
|
||||||
|
- {name: "TWO", line: {x1: 8, x2: 10, y1: 6}}
|
||||||
|
- {name: "EIGHT", line: {x1: 0, x2: 4, y1: 7}}
|
||||||
|
- {name: "ELEVEN", line: {x1: 5, x2: 10, y1: 7}}
|
||||||
|
- {name: "SEVEN", line: {x1: 0, x2: 4, y1: 8}}
|
||||||
|
- {name: "TWELVE", line: {x1: 5, x2: 10, y1: 8}}
|
||||||
|
- {name: "TEN", line: {x1: 0, x2: 2, y1: 9}}
|
||||||
|
- {name: "OCLOCK", line: {x1: 5, x2: 10, y1: 9}}
|
||||||
|
minutes:
|
||||||
|
- {minute: 0, segments: ["OCLOCK"]}
|
||||||
|
- {minute: 5, segments: ["FIVE_MINUTES", "PAST"]}
|
||||||
|
- {minute: 10, segments: ["TEN_MINUTES", "PAST"]}
|
||||||
|
- {minute: 15, segments: ["QUARTER", "PAST"]}
|
||||||
|
- {minute: 20, segments: ["TWENTY", "PAST"]}
|
||||||
|
- {minute: 25, segments: ["TWENTY", "FIVE_MINUTES", "PAST"]}
|
||||||
|
- {minute: 30, segments: ["HALF", "PAST"]}
|
||||||
|
- {minute: 35, segments: ["TWENTY", "FIVE_MINUTES", "TO"]}
|
||||||
|
- {minute: 40, segments: ["TWENTY", "TO"]}
|
||||||
|
- {minute: 45, segments: ["QUARTER", "TO"]}
|
||||||
|
- {minute: 50, segments: ["TEN_MINUTES", "TO"]}
|
||||||
|
- {minute: 55, segments: ["FIVE_MINUTES", "TO"]}
|
||||||
|
hours:
|
||||||
|
- {hour: 0, segments: ["TWELVE", "AM"]}
|
||||||
|
- {hour: 1, segments: ["ONE", "AM"]}
|
||||||
|
- {hour: 2, segments: ["TWO", "AM"]}
|
||||||
|
- {hour: 3, segments: ["THREE", "AM"]}
|
||||||
|
- {hour: 4, segments: ["FOUR", "AM"]}
|
||||||
|
- {hour: 5, segments: ["FIVE", "AM"]}
|
||||||
|
- {hour: 6, segments: ["SIX", "AM"]}
|
||||||
|
- {hour: 7, segments: ["SEVEN", "AM"]}
|
||||||
|
- {hour: 8, segments: ["EIGHT", "AM"]}
|
||||||
|
- {hour: 9, segments: ["NINE", "AM"]}
|
||||||
|
- {hour: 10, segments: ["TEN", "AM"]}
|
||||||
|
- {hour: 11, segments: ["ELEVEN", "AM"]}
|
||||||
|
- {hour: 12, segments: ["TWELVE", "PM"]}
|
||||||
|
- {hour: 13, segments: ["ONE", "PM"]}
|
||||||
|
- {hour: 14, segments: ["TWO", "PM"]}
|
||||||
|
- {hour: 15, segments: ["THREE", "PM"]}
|
||||||
|
- {hour: 16, segments: ["FOUR", "PM"]}
|
||||||
|
- {hour: 17, segments: ["FIVE", "PM"]}
|
||||||
|
- {hour: 18, segments: ["SIX", "PM"]}
|
||||||
|
- {hour: 19, segments: ["SEVEN", "PM"]}
|
||||||
|
- {hour: 20, segments: ["EIGHT", "PM"]}
|
||||||
|
- {hour: 21, segments: ["NINE", "PM"]}
|
||||||
|
- {hour: 22, segments: ["TEN", "PM"]}
|
||||||
|
- {hour: 23, segments: ["ELEVEN", "PM"]}
|
||||||
7
secrets.example.yaml
Normal file
7
secrets.example.yaml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
wifi_password: secret
|
||||||
|
wifi_ssid: example
|
||||||
|
manualip_static_ip: 192.168.1.177
|
||||||
|
manualip_gateway: 192.168.1.1
|
||||||
|
manualip_subnet: 255.255.255.0
|
||||||
|
timezone: Europe/Zurich
|
||||||
|
ap_password: "password for internal fallback accesspoint"
|
||||||
186
susannes_wordclock.yaml
Normal file
186
susannes_wordclock.yaml
Normal file
|
|
@ -0,0 +1,186 @@
|
||||||
|
esphome:
|
||||||
|
name: "wordclock"
|
||||||
|
|
||||||
|
esp8266:
|
||||||
|
board: d1_mini
|
||||||
|
framework:
|
||||||
|
version: recommended
|
||||||
|
# esp32:
|
||||||
|
# board: ttgo-t7-v13-mini32
|
||||||
|
# framework:
|
||||||
|
# type: arduino
|
||||||
|
|
||||||
|
|
||||||
|
external_components:
|
||||||
|
- source:
|
||||||
|
type: local
|
||||||
|
path: components
|
||||||
|
components: [ wordclock ]
|
||||||
|
|
||||||
|
wifi:
|
||||||
|
ssid: !secret wifi_ssid
|
||||||
|
password: !secret wifi_password
|
||||||
|
ap:
|
||||||
|
ssid: "WordClock Configuration"
|
||||||
|
password: !secret ap_password
|
||||||
|
manual_ip:
|
||||||
|
static_ip: !secret manualip_static_ip
|
||||||
|
gateway: !secret manualip_gateway
|
||||||
|
subnet: !secret manualip_subnet
|
||||||
|
dns1: 1.1.1.1
|
||||||
|
dns2: 1.0.0.1
|
||||||
|
|
||||||
|
captive_portal:
|
||||||
|
|
||||||
|
api:
|
||||||
|
|
||||||
|
ota:
|
||||||
|
password: "wordclock"
|
||||||
|
|
||||||
|
logger:
|
||||||
|
|
||||||
|
select:
|
||||||
|
- platform: template
|
||||||
|
name: "Template select"
|
||||||
|
restore_value: true
|
||||||
|
set_action:
|
||||||
|
- lambda: |-
|
||||||
|
return id(wordclock_1)->set_effect_active(false);
|
||||||
|
optimistic: true
|
||||||
|
options:
|
||||||
|
- effect
|
||||||
|
- color
|
||||||
|
initial_option: effect
|
||||||
|
|
||||||
|
web_server:
|
||||||
|
port: 80
|
||||||
|
version: 1
|
||||||
|
|
||||||
|
time:
|
||||||
|
- platform: sntp
|
||||||
|
id: current_time
|
||||||
|
timezone: !secret timezone
|
||||||
|
|
||||||
|
light:
|
||||||
|
- name: NeoPixel Strip 1
|
||||||
|
id: neopixel_strip_1
|
||||||
|
platform: neopixelbus
|
||||||
|
type: GRB
|
||||||
|
variant: WS2812
|
||||||
|
pin: GPIO3
|
||||||
|
# pin: GPIO22
|
||||||
|
num_leds: 198
|
||||||
|
restore_mode: ALWAYS_OFF
|
||||||
|
method:
|
||||||
|
type: esp8266_dma
|
||||||
|
# type: esp32_i2s
|
||||||
|
|
||||||
|
effects:
|
||||||
|
- addressable_random_twinkle:
|
||||||
|
name: "random_twinkle"
|
||||||
|
|
||||||
|
|
||||||
|
display:
|
||||||
|
- platform: addressable_light
|
||||||
|
id: led_matrix_display
|
||||||
|
addressable_light_id: neopixel_strip_1
|
||||||
|
width: 18
|
||||||
|
height: 11
|
||||||
|
rotation: 270°
|
||||||
|
update_interval: 16ms
|
||||||
|
auto_clear_enabled: false
|
||||||
|
pixel_mapper: |-
|
||||||
|
int mapping_even[] = {0, 2, 4, 6,8,10,11,13,15,17, 16,16,16,16, 16,16,16,16};
|
||||||
|
int mapping_odd[] = {17,15,13,11,9, 7, 6, 4, 2, 0, 16,16,16,16, 16,16,16,16};
|
||||||
|
if (x > 9) return -1;
|
||||||
|
if (y % 2 == 0) {
|
||||||
|
return (y * 18 + mapping_even[x]);
|
||||||
|
} else {
|
||||||
|
return (y * 18 + mapping_odd[x]);
|
||||||
|
}
|
||||||
|
|
||||||
|
color:
|
||||||
|
- id: col_on
|
||||||
|
red: 90%
|
||||||
|
green: 50%
|
||||||
|
blue: 0%
|
||||||
|
- id: col_off
|
||||||
|
red: 0%
|
||||||
|
green: 0%
|
||||||
|
blue: 0%
|
||||||
|
|
||||||
|
wordclock:
|
||||||
|
id: wordclock_1
|
||||||
|
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}}
|
||||||
|
- {name: "IS", line: {x1: 3, x2: 4, y1: 0}}
|
||||||
|
- {name: "AM", line: {x1: 7, x2: 8, y1: 0}}
|
||||||
|
- {name: "PM", line: {x1: 9, x2: 10, y1: 0}}
|
||||||
|
- {name: "QUARTER", line: {x1: 2, x2: 8, y1: 1}}
|
||||||
|
- {name: "TWENTY", line: {x1: 0, x2: 5, y1: 2}}
|
||||||
|
- {name: "FIVE_MINUTES", line: {x1: 6, x2: 9, y1: 2}}
|
||||||
|
- {name: "HALF", line: {x1: 0, x2: 3, y1: 3}}
|
||||||
|
- {name: "TEN_MINUTES", line: {x1: 5, x2: 7, y1: 3}}
|
||||||
|
- {name: "TO", line: {x1: 9, x2: 10, y1: 3}}
|
||||||
|
- {name: "PAST", line: {x1: 0, x2: 3, y1: 4}}
|
||||||
|
- {name: "NINE", line: {x1: 7, x2: 10, y1: 4}}
|
||||||
|
- {name: "ONE", line: {x1: 0, x2: 2, y1: 5}}
|
||||||
|
- {name: "SIX", line: {x1: 3, x2: 5, y1: 5}}
|
||||||
|
- {name: "THREE", line: {x1: 6, x2: 10, y1: 5}}
|
||||||
|
- {name: "FOUR", line: {x1: 0, x2: 3, y1: 6}}
|
||||||
|
- {name: "FIVE", line: {x1: 4, x2: 7, y1: 6}}
|
||||||
|
- {name: "TWO", line: {x1: 8, x2: 10, y1: 6}}
|
||||||
|
- {name: "EIGHT", line: {x1: 0, x2: 4, y1: 7}}
|
||||||
|
- {name: "ELEVEN", line: {x1: 5, x2: 10, y1: 7}}
|
||||||
|
- {name: "SEVEN", line: {x1: 0, x2: 4, y1: 8}}
|
||||||
|
- {name: "TWELVE", line: {x1: 5, x2: 10, y1: 8}}
|
||||||
|
- {name: "TEN", line: {x1: 0, x2: 2, y1: 9}}
|
||||||
|
- {name: "OCLOCK", line: {x1: 5, x2: 10, y1: 9}}
|
||||||
|
minutes:
|
||||||
|
- {minute: 0, hour_offset: 0, segments: ["OCLOCK"]}
|
||||||
|
- {minute: 5, hour_offset: 0, segments: ["FIVE_MINUTES", "PAST"]}
|
||||||
|
- {minute: 10, hour_offset: 0, segments: ["TEN_MINUTES", "PAST"]}
|
||||||
|
- {minute: 15, hour_offset: 0, segments: ["QUARTER", "PAST"]}
|
||||||
|
- {minute: 20, hour_offset: 0, segments: ["TWENTY", "PAST"]}
|
||||||
|
- {minute: 25, hour_offset: 0, segments: ["TWENTY", "FIVE_MINUTES", "PAST"]}
|
||||||
|
- {minute: 30, hour_offset: 0, segments: ["HALF", "PAST"]}
|
||||||
|
- {minute: 35, hour_offset: 1, segments: ["TWENTY", "FIVE_MINUTES", "TO"]}
|
||||||
|
- {minute: 40, hour_offset: 1, segments: ["TWENTY", "TO"]}
|
||||||
|
- {minute: 45, hour_offset: 1, segments: ["QUARTER", "TO"]}
|
||||||
|
- {minute: 50, hour_offset: 1, segments: ["TEN_MINUTES", "TO"]}
|
||||||
|
- {minute: 55, hour_offset: 1, segments: ["FIVE_MINUTES", "TO"]}
|
||||||
|
hours:
|
||||||
|
- {hour: 0, segments: "TWELVE"}
|
||||||
|
- {hour: 1, segments: "ONE"}
|
||||||
|
- {hour: 2, segments: "TWO"}
|
||||||
|
- {hour: 3, segments: "THREE"}
|
||||||
|
- {hour: 4, segments: "FOUR"}
|
||||||
|
- {hour: 5, segments: "FIVE"}
|
||||||
|
- {hour: 6, segments: "SIX"}
|
||||||
|
- {hour: 7, segments: "SEVEN"}
|
||||||
|
- {hour: 8, segments: "EIGHT"}
|
||||||
|
- {hour: 9, segments: "NINE"}
|
||||||
|
- {hour: 10, segments: "TEN"}
|
||||||
|
- {hour: 11, segments: "ELEVEN"}
|
||||||
|
- {hour: 12, segments: "TWELVE"}
|
||||||
|
- {hour: 13, segments: "ONE"}
|
||||||
|
- {hour: 14, segments: "TWO"}
|
||||||
|
- {hour: 15, segments: "THREE"}
|
||||||
|
- {hour: 16, segments: "FOUR"}
|
||||||
|
- {hour: 17, segments: "FIVE"}
|
||||||
|
- {hour: 18, segments: "SIX"}
|
||||||
|
- {hour: 19, segments: "SEVEN"}
|
||||||
|
- {hour: 20, segments: "EIGHT"}
|
||||||
|
- {hour: 21, segments: "NINE"}
|
||||||
|
- {hour: 22, segments: "TEN"}
|
||||||
|
- {hour: 23, segments: "ELEVEN"}
|
||||||
|
|
||||||
|
|
||||||
327
susannes_wordclock_esp32.yaml
Normal file
327
susannes_wordclock_esp32.yaml
Normal file
|
|
@ -0,0 +1,327 @@
|
||||||
|
esphome:
|
||||||
|
name: "wordclock"
|
||||||
|
|
||||||
|
esp32:
|
||||||
|
# board: ttgo-t7-v13-mini32
|
||||||
|
board: wemos_d1_mini32
|
||||||
|
framework:
|
||||||
|
type: arduino
|
||||||
|
|
||||||
|
external_components:
|
||||||
|
- source:
|
||||||
|
type: local
|
||||||
|
path: components
|
||||||
|
components: [ wordclock ]
|
||||||
|
|
||||||
|
wifi:
|
||||||
|
ssid: !secret wifi_ssid
|
||||||
|
password: !secret wifi_password
|
||||||
|
ap:
|
||||||
|
ssid: "WordClock Configuration"
|
||||||
|
password: !secret ap_password
|
||||||
|
manual_ip:
|
||||||
|
static_ip: !secret manualip_static_ip
|
||||||
|
gateway: !secret manualip_gateway
|
||||||
|
subnet: !secret manualip_subnet
|
||||||
|
dns1: 1.1.1.1
|
||||||
|
dns2: 1.0.0.1
|
||||||
|
|
||||||
|
captive_portal:
|
||||||
|
|
||||||
|
# api:
|
||||||
|
|
||||||
|
ota:
|
||||||
|
password: "wordclock"
|
||||||
|
|
||||||
|
logger:
|
||||||
|
|
||||||
|
switch:
|
||||||
|
- platform: template
|
||||||
|
name: "5 Rainbow ON/OFF"
|
||||||
|
# lambda: |-
|
||||||
|
# return id(wordclock_1)->get_effect_active();
|
||||||
|
restore_state: true
|
||||||
|
optimistic: true
|
||||||
|
restore_mode: RESTORE_DEFAULT_ON
|
||||||
|
turn_on_action:
|
||||||
|
- lambda: |-
|
||||||
|
id(wordclock_1)->set_effect_active(true);
|
||||||
|
turn_off_action:
|
||||||
|
- lambda: |-
|
||||||
|
id(wordclock_1)->set_effect_active(false);
|
||||||
|
|
||||||
|
# on_...:
|
||||||
|
# - globals.set:
|
||||||
|
# id: my_global_var
|
||||||
|
# value: '10'
|
||||||
|
|
||||||
|
|
||||||
|
# output:
|
||||||
|
# - platform: template
|
||||||
|
# id: dummy_out
|
||||||
|
# type: float
|
||||||
|
# write_action:
|
||||||
|
# - lambda: |-
|
||||||
|
# return id(wordclock_1)->set_effect_active(true);
|
||||||
|
# Example configuration entry
|
||||||
|
globals:
|
||||||
|
- id: my_global_int
|
||||||
|
type: int
|
||||||
|
restore_value: no
|
||||||
|
initial_value: '0'
|
||||||
|
# Example for global string variable
|
||||||
|
- id: my_global_string
|
||||||
|
type: std::string
|
||||||
|
restore_value: no # Strings cannot be saved/restored
|
||||||
|
initial_value: '"Global value is"'
|
||||||
|
|
||||||
|
number:
|
||||||
|
- platform: template
|
||||||
|
name: "1 Red"
|
||||||
|
optimistic: true
|
||||||
|
restore_value: true
|
||||||
|
initial_value: 255
|
||||||
|
# lambda: |-
|
||||||
|
# return id(wordclock_1)->on_color.red;
|
||||||
|
on_value:
|
||||||
|
- lambda: |-
|
||||||
|
id(wordclock_1)->on_color.red = x;
|
||||||
|
set_action:
|
||||||
|
- lambda: |-
|
||||||
|
id(wordclock_1)->on_color.red = x;
|
||||||
|
min_value: 0
|
||||||
|
max_value: 255
|
||||||
|
step: 1
|
||||||
|
- platform: template
|
||||||
|
name: "2 Green"
|
||||||
|
optimistic: true
|
||||||
|
restore_value: true
|
||||||
|
initial_value: 255
|
||||||
|
# lambda: |-
|
||||||
|
# return id(wordclock_1)->on_color.green;
|
||||||
|
on_value:
|
||||||
|
- lambda: |-
|
||||||
|
id(wordclock_1)->on_color.green = x;
|
||||||
|
set_action:
|
||||||
|
- lambda: |-
|
||||||
|
id(wordclock_1)->on_color.green = x;
|
||||||
|
min_value: 0
|
||||||
|
max_value: 255
|
||||||
|
step: 1
|
||||||
|
- platform: template
|
||||||
|
name: "3 Blue"
|
||||||
|
optimistic: true
|
||||||
|
restore_value: true
|
||||||
|
initial_value: 255
|
||||||
|
# lambda: |-
|
||||||
|
# return id(wordclock_1)->on_color.blue;
|
||||||
|
on_value:
|
||||||
|
- lambda: |-
|
||||||
|
id(wordclock_1)->on_color.blue = x;
|
||||||
|
set_action:
|
||||||
|
- lambda: |-
|
||||||
|
id(wordclock_1)->on_color.blue = x;
|
||||||
|
min_value: 0
|
||||||
|
max_value: 255
|
||||||
|
step: 1
|
||||||
|
- platform: template
|
||||||
|
name: "4 Brightness"
|
||||||
|
optimistic: true
|
||||||
|
restore_value: true
|
||||||
|
initial_value: 255
|
||||||
|
# lambda: |-
|
||||||
|
# return id(wordclock_1)->brightness;
|
||||||
|
on_value:
|
||||||
|
- lambda: |-
|
||||||
|
id(wordclock_1)->brightness = x;
|
||||||
|
set_action:
|
||||||
|
- lambda: |-
|
||||||
|
id(wordclock_1)->brightness = x;
|
||||||
|
min_value: 0
|
||||||
|
max_value: 255
|
||||||
|
step: 1
|
||||||
|
- platform: template
|
||||||
|
name: "6 Rainbow Speed"
|
||||||
|
optimistic: true
|
||||||
|
restore_value: true
|
||||||
|
initial_value: 12
|
||||||
|
on_value:
|
||||||
|
- lambda: |-
|
||||||
|
id(wordclock_1)->set_effect_speed(x);
|
||||||
|
set_action:
|
||||||
|
- lambda: |-
|
||||||
|
id(wordclock_1)->set_effect_speed(x);
|
||||||
|
min_value: 1
|
||||||
|
max_value: 4096
|
||||||
|
step: 1
|
||||||
|
- platform: template
|
||||||
|
name: "7 Rainbow Width"
|
||||||
|
optimistic: true
|
||||||
|
restore_value: true
|
||||||
|
initial_value: 50
|
||||||
|
on_value:
|
||||||
|
- lambda: |-
|
||||||
|
id(wordclock_1)->set_effect_width(x);
|
||||||
|
set_action:
|
||||||
|
- lambda: |-
|
||||||
|
id(wordclock_1)->set_effect_width(x);
|
||||||
|
min_value: 1
|
||||||
|
max_value: 65535
|
||||||
|
step: 1
|
||||||
|
|
||||||
|
# select:
|
||||||
|
# - platform: template
|
||||||
|
# name: "Rainbow ON/OFF"
|
||||||
|
# restore_value: true
|
||||||
|
# set_action:
|
||||||
|
# - lambda: |-
|
||||||
|
# return id(wordclock_1)->set_color_value(x);
|
||||||
|
# optimistic: true
|
||||||
|
# options:
|
||||||
|
# - effect
|
||||||
|
# - color
|
||||||
|
# initial_option: effect
|
||||||
|
# - platform: template
|
||||||
|
# name: "Template select"
|
||||||
|
# restore_value: true
|
||||||
|
# set_action:
|
||||||
|
# - lambda: |-
|
||||||
|
# return id(wordclock_1)->set_color_value(x);
|
||||||
|
# optimistic: true
|
||||||
|
# options:
|
||||||
|
# - effect
|
||||||
|
# - color
|
||||||
|
# initial_option: effect
|
||||||
|
|
||||||
|
|
||||||
|
web_server:
|
||||||
|
port: 80
|
||||||
|
version: 2
|
||||||
|
local: true
|
||||||
|
|
||||||
|
time:
|
||||||
|
- platform: sntp
|
||||||
|
id: current_time
|
||||||
|
timezone: !secret timezone
|
||||||
|
|
||||||
|
|
||||||
|
light:
|
||||||
|
- name: NeoPixel Strip 1
|
||||||
|
id: neopixel_strip_1
|
||||||
|
platform: neopixelbus
|
||||||
|
type: GRB
|
||||||
|
variant: WS2812
|
||||||
|
pin: GPIO22
|
||||||
|
num_leds: 198
|
||||||
|
restore_mode: ALWAYS_OFF
|
||||||
|
internal: true
|
||||||
|
method:
|
||||||
|
type: esp32_i2s
|
||||||
|
effects:
|
||||||
|
- addressable_random_twinkle:
|
||||||
|
name: "random_twinkle"
|
||||||
|
|
||||||
|
display:
|
||||||
|
- platform: addressable_light
|
||||||
|
id: led_matrix_display
|
||||||
|
addressable_light_id: neopixel_strip_1
|
||||||
|
width: 18
|
||||||
|
height: 11
|
||||||
|
rotation: 270°
|
||||||
|
update_interval: 16ms
|
||||||
|
auto_clear_enabled: false
|
||||||
|
pixel_mapper: |-
|
||||||
|
int mapping_even[] = {0, 2, 4, 6,8,10,11,13,15,17, 16,16,16,16, 16,16,16,16};
|
||||||
|
int mapping_odd[] = {17,15,13,11,9, 7, 6, 4, 2, 0, 16,16,16,16, 16,16,16,16};
|
||||||
|
if (x > 9) return -1;
|
||||||
|
if (y % 2 == 0) {
|
||||||
|
return (y * 18 + mapping_even[x]);
|
||||||
|
} else {
|
||||||
|
return (y * 18 + mapping_odd[x]);
|
||||||
|
}
|
||||||
|
|
||||||
|
color:
|
||||||
|
- id: col_on
|
||||||
|
red: 90%
|
||||||
|
green: 50%
|
||||||
|
blue: 0%
|
||||||
|
- id: col_off
|
||||||
|
red: 0%
|
||||||
|
green: 0%
|
||||||
|
blue: 0%
|
||||||
|
|
||||||
|
wordclock:
|
||||||
|
id: wordclock_1
|
||||||
|
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}}
|
||||||
|
- {name: "IS", line: {x1: 3, x2: 4, y1: 0}}
|
||||||
|
- {name: "AM", line: {x1: 7, x2: 8, y1: 0}}
|
||||||
|
- {name: "PM", line: {x1: 9, x2: 10, y1: 0}}
|
||||||
|
- {name: "QUARTER", line: {x1: 2, x2: 8, y1: 1}}
|
||||||
|
- {name: "TWENTY", line: {x1: 0, x2: 5, y1: 2}}
|
||||||
|
- {name: "FIVE_MINUTES", line: {x1: 6, x2: 9, y1: 2}}
|
||||||
|
- {name: "HALF", line: {x1: 0, x2: 3, y1: 3}}
|
||||||
|
- {name: "TEN_MINUTES", line: {x1: 5, x2: 7, y1: 3}}
|
||||||
|
- {name: "TO", line: {x1: 9, x2: 10, y1: 3}}
|
||||||
|
- {name: "PAST", line: {x1: 0, x2: 3, y1: 4}}
|
||||||
|
- {name: "NINE", line: {x1: 7, x2: 10, y1: 4}}
|
||||||
|
- {name: "ONE", line: {x1: 0, x2: 2, y1: 5}}
|
||||||
|
- {name: "SIX", line: {x1: 3, x2: 5, y1: 5}}
|
||||||
|
- {name: "THREE", line: {x1: 6, x2: 10, y1: 5}}
|
||||||
|
- {name: "FOUR", line: {x1: 0, x2: 3, y1: 6}}
|
||||||
|
- {name: "FIVE", line: {x1: 4, x2: 7, y1: 6}}
|
||||||
|
- {name: "TWO", line: {x1: 8, x2: 10, y1: 6}}
|
||||||
|
- {name: "EIGHT", line: {x1: 0, x2: 4, y1: 7}}
|
||||||
|
- {name: "ELEVEN", line: {x1: 5, x2: 10, y1: 7}}
|
||||||
|
- {name: "SEVEN", line: {x1: 0, x2: 4, y1: 8}}
|
||||||
|
- {name: "TWELVE", line: {x1: 5, x2: 10, y1: 8}}
|
||||||
|
- {name: "TEN", line: {x1: 0, x2: 2, y1: 9}}
|
||||||
|
- {name: "OCLOCK", line: {x1: 5, x2: 10, y1: 9}}
|
||||||
|
minutes:
|
||||||
|
- {minute: 0, hour_offset: 0, segments: ["OCLOCK"]}
|
||||||
|
- {minute: 5, hour_offset: 0, segments: ["FIVE_MINUTES", "PAST"]}
|
||||||
|
- {minute: 10, hour_offset: 0, segments: ["TEN_MINUTES", "PAST"]}
|
||||||
|
- {minute: 15, hour_offset: 0, segments: ["QUARTER", "PAST"]}
|
||||||
|
- {minute: 20, hour_offset: 0, segments: ["TWENTY", "PAST"]}
|
||||||
|
- {minute: 25, hour_offset: 0, segments: ["TWENTY", "FIVE_MINUTES", "PAST"]}
|
||||||
|
- {minute: 30, hour_offset: 0, segments: ["HALF", "PAST"]}
|
||||||
|
- {minute: 35, hour_offset: 1, segments: ["TWENTY", "FIVE_MINUTES", "TO"]}
|
||||||
|
- {minute: 40, hour_offset: 1, segments: ["TWENTY", "TO"]}
|
||||||
|
- {minute: 45, hour_offset: 1, segments: ["QUARTER", "TO"]}
|
||||||
|
- {minute: 50, hour_offset: 1, segments: ["TEN_MINUTES", "TO"]}
|
||||||
|
- {minute: 55, hour_offset: 1, segments: ["FIVE_MINUTES", "TO"]}
|
||||||
|
hours:
|
||||||
|
- {hour: 0, segments: "TWELVE"}
|
||||||
|
- {hour: 1, segments: "ONE"}
|
||||||
|
- {hour: 2, segments: "TWO"}
|
||||||
|
- {hour: 3, segments: "THREE"}
|
||||||
|
- {hour: 4, segments: "FOUR"}
|
||||||
|
- {hour: 5, segments: "FIVE"}
|
||||||
|
- {hour: 6, segments: "SIX"}
|
||||||
|
- {hour: 7, segments: "SEVEN"}
|
||||||
|
- {hour: 8, segments: "EIGHT"}
|
||||||
|
- {hour: 9, segments: "NINE"}
|
||||||
|
- {hour: 10, segments: "TEN"}
|
||||||
|
- {hour: 11, segments: "ELEVEN"}
|
||||||
|
- {hour: 12, segments: "TWELVE"}
|
||||||
|
- {hour: 13, segments: "ONE"}
|
||||||
|
- {hour: 14, segments: "TWO"}
|
||||||
|
- {hour: 15, segments: "THREE"}
|
||||||
|
- {hour: 16, segments: "FOUR"}
|
||||||
|
- {hour: 17, segments: "FIVE"}
|
||||||
|
- {hour: 18, segments: "SIX"}
|
||||||
|
- {hour: 19, segments: "SEVEN"}
|
||||||
|
- {hour: 20, segments: "EIGHT"}
|
||||||
|
- {hour: 21, segments: "NINE"}
|
||||||
|
- {hour: 22, segments: "TEN"}
|
||||||
|
- {hour: 23, segments: "ELEVEN"}
|
||||||
|
|
||||||
|
|
||||||
BIN
test.png
Normal file
BIN
test.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 780 B |
228
wordclock.h
Normal file
228
wordclock.h
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
#include "esphome.h"
|
||||||
|
#include <FastLED.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
|
||||||
|
|
||||||
|
///// Random Stuff /////
|
||||||
|
|
||||||
|
#ifndef WORDCLOCK_NUM_LEDS
|
||||||
|
#define WORDCLOCK_NUM_LEDS 198
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef WORDCLOCK_DATA_PIN
|
||||||
|
#define WORDCLOCK_DATA_PIN 22
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* NOTE:
|
||||||
|
This section is about mapping LED indices to
|
||||||
|
positions in the grid.
|
||||||
|
|
||||||
|
On EVEN rows, the LED strip is running along the
|
||||||
|
matrix indices, but the physical positions of the
|
||||||
|
LEDs on the strip did not align with the letter
|
||||||
|
cutouts.
|
||||||
|
|
||||||
|
On ODD rows, the string is running backwards, because
|
||||||
|
it is snaking its way back and forth. */
|
||||||
|
int mapping_even[] = {0, 2, 4, 6,8,10,11,13,15,17};
|
||||||
|
int mapping_odd[] = {17,15,13,11,9, 7, 6, 4, 2, 0};
|
||||||
|
int map_coords_to_strip(int row, int column) {
|
||||||
|
if (column % 2) {
|
||||||
|
return (10 - column) * 18 + mapping_odd[row];
|
||||||
|
} else {
|
||||||
|
return (10 - column) * 18 + mapping_even [row];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///// Word Table /////
|
||||||
|
int WORD_IT_IS[5][2] = {{4,0}, {0,0}, {0,1}, {0,3}, {0,4}};
|
||||||
|
|
||||||
|
int WORD_QUARTER[8][2] = {{7,0}, {1,2}, {1,3}, {1,4}, {1,5}, {1,6}, {1,7}, {1,8}};
|
||||||
|
int WORD_TWENTY[7][2] = {{6,0}, {2,0}, {2,1}, {2,2}, {2,3}, {2,4}, {2,5}};
|
||||||
|
int WORD_FIVE_MINUTES[5][2] = {{4,0}, {2,6}, {2,7}, {2,8}, {2,9}};
|
||||||
|
int WORD_HALF[5][2] = {{4,0}, {3,0}, {3,1}, {3,2}, {3,3}};
|
||||||
|
int WORD_TEN_MINUTES[4][2] = {{3,0}, {3,5}, {3,6}, {3,7}};
|
||||||
|
|
||||||
|
int WORD_TO[3][2] = {{2,0}, {3,9}, {3,10}};
|
||||||
|
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}};
|
||||||
|
|
||||||
|
// TODO: It's probably unnecessary to have the "API" in CustomAPIDevice, since this
|
||||||
|
// component doesn't actually register any services anymore.
|
||||||
|
class Wordclock : public Component, public CustomAPIDevice {
|
||||||
|
public:
|
||||||
|
CRGB leds[WORDCLOCK_NUM_LEDS];
|
||||||
|
int hour = -1;
|
||||||
|
int minute = -1;
|
||||||
|
int second = -1;
|
||||||
|
int red = 124;
|
||||||
|
int green = 124;
|
||||||
|
int blue = 124;
|
||||||
|
int brightness = 50;
|
||||||
|
|
||||||
|
void setup() override {
|
||||||
|
FastLED.addLeds<NEOPIXEL, WORDCLOCK_DATA_PIN>(leds, WORDCLOCK_NUM_LEDS);
|
||||||
|
FastLED.setBrightness(brightness);
|
||||||
|
clear_all_leds();
|
||||||
|
FastLED.show();
|
||||||
|
// TODO: Set up some kind of initialization sequence. But it should be based on an effect or similarly supporting the
|
||||||
|
// cooperative multithreading. delay() calls are uncalled for.
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear_all_leds() {
|
||||||
|
for(int i = 0; i < WORDCLOCK_NUM_LEDS; i++) {
|
||||||
|
leds[i].setRGB(0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void display_word(const int word[][2], const CRGB& c) {
|
||||||
|
for (int i=1; i < word[0][0] + 1; i++) {
|
||||||
|
leds[map_coords_to_strip(word[i][0], word[i][1])].setRGB(c.r, c.g, c.b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void display_minutes(int minutes, const CRGB& color) {
|
||||||
|
int five_minute_chunk = minutes / 5;
|
||||||
|
|
||||||
|
switch (five_minute_chunk)
|
||||||
|
{
|
||||||
|
case 0: // sharp
|
||||||
|
display_word(WORD_OCLOCK, color); ESP_LOGD("minute", "oclock "); break;
|
||||||
|
case 1: // five past
|
||||||
|
display_word(WORD_FIVE_MINUTES, color); ESP_LOGD("minute", "five past "); break;
|
||||||
|
case 2: // ten past
|
||||||
|
display_word(WORD_TEN_MINUTES, color); ESP_LOGD("minute", "ten past "); break;
|
||||||
|
case 3: // quarter past
|
||||||
|
display_word(WORD_QUARTER, color); ESP_LOGD("minute", "quarter past "); break;
|
||||||
|
case 4: // twenty past
|
||||||
|
display_word(WORD_TWENTY, color); ESP_LOGD("minute", "twenty past "); break;
|
||||||
|
case 5: // twenty five past
|
||||||
|
display_word(WORD_TWENTY, color); display_word(WORD_FIVE_MINUTES, color);
|
||||||
|
ESP_LOGD("minute", "twenty five past "); break;
|
||||||
|
case 6: // half past
|
||||||
|
display_word(WORD_HALF, color); ESP_LOGD("minute", "half past "); break;
|
||||||
|
case 7: // twenty five to
|
||||||
|
display_word(WORD_TWENTY, color); display_word(WORD_FIVE_MINUTES, color);
|
||||||
|
ESP_LOGD("minute", "twenty five to "); break;
|
||||||
|
case 8: // twenty to
|
||||||
|
display_word(WORD_TWENTY, color); ESP_LOGD("minute", "twenty to "); break;
|
||||||
|
case 9: // quarter to
|
||||||
|
display_word(WORD_QUARTER, color); ESP_LOGD("minute", "quarter to "); break;
|
||||||
|
case 10: // ten to
|
||||||
|
display_word(WORD_TEN_MINUTES, color); ESP_LOGD("minute", "ten to "); break;
|
||||||
|
case 11: // five to
|
||||||
|
display_word(WORD_FIVE_MINUTES, color); ESP_LOGD("minute", "five to "); break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (five_minute_chunk > 6) {
|
||||||
|
display_word(WORD_TO, color);
|
||||||
|
} else if (five_minute_chunk > 0) {
|
||||||
|
display_word(WORD_PAST, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void display_hour(int hour, int minutes, const CRGB& color) {
|
||||||
|
int five_minute_chunk = minutes / 5;
|
||||||
|
if (five_minute_chunk > 6) {
|
||||||
|
hour += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (hour % 12)
|
||||||
|
{
|
||||||
|
case 0: // twelve
|
||||||
|
display_word(WORD_TWELFE, color); ESP_LOGD("hour", "twelve "); break;
|
||||||
|
case 1: // one
|
||||||
|
display_word(WORD_ONE, color); ESP_LOGD("hour", "one "); break;
|
||||||
|
case 2: // two
|
||||||
|
display_word(WORD_TWO, color); ESP_LOGD("hour", "two "); break;
|
||||||
|
case 3: // three
|
||||||
|
display_word(WORD_THREE, color); ESP_LOGD("hour", "three "); break;
|
||||||
|
case 4: // four
|
||||||
|
display_word(WORD_FOUR, color); ESP_LOGD("hour", "four "); break;
|
||||||
|
case 5: // five
|
||||||
|
display_word(WORD_FIVE, color); ESP_LOGD("hour", "five "); break;
|
||||||
|
case 6: // six
|
||||||
|
display_word(WORD_SIX, color); ESP_LOGD("hour", "six "); break;
|
||||||
|
case 7: // seven
|
||||||
|
display_word(WORD_SEVEN, color); ESP_LOGD("hour", "seven "); break;
|
||||||
|
case 8: // eight
|
||||||
|
display_word(WORD_EIGHT, color); ESP_LOGD("hour", "eight "); break;
|
||||||
|
case 9: // nine
|
||||||
|
display_word(WORD_NINE, color); ESP_LOGD("hour", "nine "); break;
|
||||||
|
case 10: // ten
|
||||||
|
display_word(WORD_TEN, color); ESP_LOGD("hour", "ten "); break;
|
||||||
|
case 11: // eleven
|
||||||
|
display_word(WORD_ELEVEN, color); ESP_LOGD("hour", "eleven "); break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void display_time(int hour, int minutes, const CRGB& color) {
|
||||||
|
display_word(WORD_IT_IS, color);
|
||||||
|
display_hour(hour, minutes, color);
|
||||||
|
display_minutes(minutes, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
if (time.is_valid() == false) {
|
||||||
|
ESP_LOGD("loop", "time is not valid");
|
||||||
|
// do something to show that the clock isn't dead. Maybe I can instantiate and effect and use that for this.
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
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){
|
||||||
|
|
||||||
|
clear_all_leds();
|
||||||
|
display_time(time.hour, time.minute, CRGB(red, green, blue));
|
||||||
|
FastLED.show();
|
||||||
|
|
||||||
|
ESP_LOGE("loop", "Update Time: %i:%i Brightness: %i RGB: %i-%i-%i", time.hour, time.minute, brightness, red, green, blue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
74
wordclock.yaml
Normal file
74
wordclock.yaml
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
esphome:
|
||||||
|
name: "${devicename}"
|
||||||
|
platformio_options:
|
||||||
|
build_flags:
|
||||||
|
- -DWORDCLOCK_DATA_PIN=22
|
||||||
|
- -DWORDCLOCK_NUM_LEDS=198
|
||||||
|
includes:
|
||||||
|
- wordclock.h
|
||||||
|
|
||||||
|
external_components:
|
||||||
|
- source:
|
||||||
|
type: local
|
||||||
|
path: components
|
||||||
|
components: [ wordcl ]
|
||||||
|
|
||||||
|
esp8266:
|
||||||
|
board: d1_mini
|
||||||
|
framework:
|
||||||
|
version: recommended
|
||||||
|
|
||||||
|
# esp32:
|
||||||
|
# board: ttgo-t7-v13-mini32
|
||||||
|
# framework:
|
||||||
|
# type: arduino
|
||||||
|
|
||||||
|
substitutions:
|
||||||
|
devicename: wordclock
|
||||||
|
friendly_name: "Wordclock"
|
||||||
|
light_friendly_name: "Wordclock Light"
|
||||||
|
|
||||||
|
wifi:
|
||||||
|
ssid: !secret wifi_ssid
|
||||||
|
password: !secret wifi_password
|
||||||
|
ap:
|
||||||
|
ssid: "${devicename}"
|
||||||
|
password: !secret ap_password
|
||||||
|
manual_ip:
|
||||||
|
static_ip: !secret manualip_static_ip
|
||||||
|
gateway: !secret manualip_gateway
|
||||||
|
subnet: !secret manualip_subnet
|
||||||
|
dns1: 1.1.1.1
|
||||||
|
dns2: 1.0.0.1
|
||||||
|
|
||||||
|
api:
|
||||||
|
|
||||||
|
ota:
|
||||||
|
password: "${devicename}"
|
||||||
|
|
||||||
|
logger:
|
||||||
|
# esp8266_store_log_strings_in_flash: false
|
||||||
|
web_server:
|
||||||
|
port: 80
|
||||||
|
|
||||||
|
light:
|
||||||
|
- platform: fastled_clockless
|
||||||
|
id: fastledlight
|
||||||
|
chipset: WS2812
|
||||||
|
pin: 23
|
||||||
|
num_leds: 198
|
||||||
|
rgb_order: GRB
|
||||||
|
name: ${light_friendly_name}
|
||||||
|
restore_mode: ALWAYS_ON
|
||||||
|
|
||||||
|
time:
|
||||||
|
- platform: sntp
|
||||||
|
id: current_time
|
||||||
|
timezone: !secret timezone
|
||||||
|
|
||||||
|
custom_component:
|
||||||
|
- lambda: |-
|
||||||
|
auto wordclock = new Wordclock();
|
||||||
|
return {wordclock};
|
||||||
|
components:
|
||||||
|
- id: wordclock
|
||||||
89
wordclock8266.yaml
Normal file
89
wordclock8266.yaml
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
esphome:
|
||||||
|
name: "wordclock"
|
||||||
|
|
||||||
|
esp8266:
|
||||||
|
board: d1_mini
|
||||||
|
framework:
|
||||||
|
version: recommended
|
||||||
|
|
||||||
|
external_components:
|
||||||
|
- source:
|
||||||
|
type: local
|
||||||
|
path: components
|
||||||
|
components: [ wordcl ]
|
||||||
|
|
||||||
|
# substitutions:
|
||||||
|
# devicename: wordclock
|
||||||
|
# friendly_name: "Wordclock"
|
||||||
|
# light_friendly_name: "Wordclock Light"
|
||||||
|
|
||||||
|
# wifi:
|
||||||
|
# ssid: !secret wifi_ssid
|
||||||
|
# password: !secret wifi_password
|
||||||
|
# ap:
|
||||||
|
# ssid: "${devicename}"
|
||||||
|
# password: !secret ap_password
|
||||||
|
# manual_ip:
|
||||||
|
# static_ip: !secret manualip_static_ip
|
||||||
|
# gateway: !secret manualip_gateway
|
||||||
|
# subnet: !secret manualip_subnet
|
||||||
|
# dns1: 1.1.1.1
|
||||||
|
# dns2: 1.0.0.1
|
||||||
|
|
||||||
|
# api:
|
||||||
|
|
||||||
|
# ota:
|
||||||
|
# password: "${devicename}"
|
||||||
|
|
||||||
|
# logger:
|
||||||
|
# # esp8266_store_log_strings_in_flash: false
|
||||||
|
# web_server:
|
||||||
|
# port: 80
|
||||||
|
|
||||||
|
wordclock:
|
||||||
|
|
||||||
|
light:
|
||||||
|
- name: neopixel
|
||||||
|
id: neopixel_1
|
||||||
|
platform: neopixelbus
|
||||||
|
type: GRB
|
||||||
|
variant: WS2812
|
||||||
|
pin: GPIO3
|
||||||
|
num_leds: 30
|
||||||
|
default_transition_length: 0.2s
|
||||||
|
# restore_mode: RESTORE_DEFAULT_ON
|
||||||
|
method:
|
||||||
|
type: esp8266_dma
|
||||||
|
on_turn_on:
|
||||||
|
then:
|
||||||
|
- light.turn_on:
|
||||||
|
id: neopixel_1
|
||||||
|
brightness: 35%
|
||||||
|
effect: rainbow
|
||||||
|
effects:
|
||||||
|
# - random:
|
||||||
|
# - pulse:
|
||||||
|
# - strobe:
|
||||||
|
# - flicker:
|
||||||
|
- addressable_rainbow:
|
||||||
|
name: rainbow
|
||||||
|
# - addressable_color_wipe:
|
||||||
|
# - addressable_scan:
|
||||||
|
# - addressable_twinkle:
|
||||||
|
# - addressable_random_twinkle:
|
||||||
|
# - addressable_fireworks:
|
||||||
|
# - addressable_flicker:
|
||||||
|
# - wled:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# time:
|
||||||
|
# - platform: sntp
|
||||||
|
# id: current_time
|
||||||
|
# timezone: !secret timezone
|
||||||
|
|
||||||
|
# - light.turn_on:
|
||||||
|
# id: light_1
|
||||||
|
# brightness: 100%
|
||||||
|
# effect: addressable_rainbow
|
||||||
29
wordclock_new.yaml
Normal file
29
wordclock_new.yaml
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
esphome:
|
||||||
|
name: wordclock-livingroom
|
||||||
|
|
||||||
|
esp32:
|
||||||
|
board: ttgo-t7-v13-mini32
|
||||||
|
framework:
|
||||||
|
type: arduino
|
||||||
|
|
||||||
|
# Enable logging
|
||||||
|
logger:
|
||||||
|
|
||||||
|
# Enable Home Assistant API
|
||||||
|
api:
|
||||||
|
password: ""
|
||||||
|
|
||||||
|
ota:
|
||||||
|
password: ""
|
||||||
|
|
||||||
|
wifi:
|
||||||
|
ssid: "wifithing"
|
||||||
|
password: "lostandfound1"
|
||||||
|
|
||||||
|
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||||
|
ap:
|
||||||
|
ssid: "Wordclock-Livingroom"
|
||||||
|
password: "wGfGBPnJcbzE"
|
||||||
|
|
||||||
|
captive_portal:
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue