feat: 🎉 add first try at an esphome component for this wordclock firmware

The code is loosely based on https://github.com/leinich/ha-wordclock-esphome
This commit is contained in:
Philip Stark 2022-05-25 02:06:52 +02:00
parent 34656ff942
commit 4c612fc462
2 changed files with 283 additions and 0 deletions

222
wordclock.h Normal file
View file

@ -0,0 +1,222 @@
#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 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) {
// do something to show that the clock isn't dead. Maybe I can instantiate and effect and use that for this.
}
else {
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);
}
}
}
}
};