From 4c612fc462b882a763f3ea4960d6920926042c49 Mon Sep 17 00:00:00 2001 From: Philip Stark Date: Wed, 25 May 2022 02:06:52 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=89=20add=20first=20try=20at?= =?UTF-8?q?=20an=20esphome=20component=20for=20this=20wordclock=20firmware?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The code is loosely based on https://github.com/leinich/ha-wordclock-esphome --- wordclock.h | 222 +++++++++++++++++++++++++++++++++++++++++++++++++ wordclock.yaml | 61 ++++++++++++++ 2 files changed, 283 insertions(+) create mode 100644 wordclock.h create mode 100644 wordclock.yaml diff --git a/wordclock.h b/wordclock.h new file mode 100644 index 0000000..758ef0b --- /dev/null +++ b/wordclock.h @@ -0,0 +1,222 @@ +#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 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) { + // 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); + } + } + } + } +}; diff --git a/wordclock.yaml b/wordclock.yaml new file mode 100644 index 0000000..cd6027e --- /dev/null +++ b/wordclock.yaml @@ -0,0 +1,61 @@ +esphome: + name: "${devicename}" + platformio_options: + build_flags: + - -DWORDCLOCK_DATA_PIN=22 + - -DWORDCLOCK_NUM_LEDS=198 + includes: + - wordclock.h + +esp32: + board: wemos_d1_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 + +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