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