#include "esphome.h" #include // 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(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); } } } } };