From 7dbef0b49857b85c8b27613de5eb6e23d26ae743 Mon Sep 17 00:00:00 2001 From: lukas Date: Sat, 6 Mar 2021 22:06:09 +0100 Subject: [PATCH] add WordClock Features --- platformio.ini | 3 +- src/Clock.cpp | 159 ++++++++++++++++++++ src/Clock.h | 48 ++++++ src/ClockService.cpp | 38 +++++ src/{LightStateService.h => ClockService.h} | 24 ++- src/LightMqttSettingsService.cpp | 16 -- src/LightMqttSettingsService.h | 41 ----- src/LightStateService.cpp | 73 --------- src/main.cpp | 16 +- 9 files changed, 261 insertions(+), 157 deletions(-) create mode 100644 src/Clock.cpp create mode 100644 src/Clock.h create mode 100644 src/ClockService.cpp rename src/{LightStateService.h => ClockService.h} (76%) delete mode 100644 src/LightMqttSettingsService.cpp delete mode 100644 src/LightMqttSettingsService.h delete mode 100644 src/LightStateService.cpp diff --git a/platformio.ini b/platformio.ini index d1a91cb..76aba5f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -29,13 +29,14 @@ lib_compat_mode = strict framework = arduino monitor_speed = 115200 -extra_scripts = +extra_scripts = pre:scripts/build_interface.py lib_deps = ArduinoJson@>=6.0.0,<7.0.0 ESP Async WebServer@>=1.2.0,<2.0.0 AsyncMqttClient@>=0.8.2,<1.0.0 + adafruit/Adafruit NeoPixel@^1.7.0 [env:esp12e] platform = espressif8266 diff --git a/src/Clock.cpp b/src/Clock.cpp new file mode 100644 index 0000000..cda0e5b --- /dev/null +++ b/src/Clock.cpp @@ -0,0 +1,159 @@ +// +// Created by lukas on 06.03.21. +// + +#include "Clock.h" + +Clock::Clock() : strip(NUMPIXELS, D5, NEO_GRB + NEO_KHZ800), refreshTicker() { +} + +void Clock::init() { + strip.begin(); + strip.clear(); + strip.show(); + + refreshTicker.attach(10, [this]() { this->refreshTime(); }); + this->refreshTime(); +} + +void Clock::paintAllwaysOnLeds() { + printWord(types::es, Adafruit_NeoPixel::Color(150, 150, 0)); + printWord(types::ist, Adafruit_NeoPixel::Color(150, 0, 150)); + printWord(types::uhr, Adafruit_NeoPixel::Color(0, 0, 150)); +} + +void Clock::printWord(std::vector word, uint32_t color) { + for (const int i : word) { + strip.setPixelColor(i, color); + } +} + +void Clock::refreshTime() { + strip.clear(); + + // grab the current instant in unix seconds + time_t now = time(nullptr); + if (now <= 604800) { + strip.show(); + return; + } + + tm* loctime = localtime(&now); + + const uint8_t hour = loctime->tm_hour; + const uint8_t minute = loctime->tm_min; + + paintAllwaysOnLeds(); + setTime(hour, minute); + strip.show(); + + Serial.print("time now: "); + Serial.print(hour); + Serial.print("h "); + Serial.print(minute); + Serial.println("M"); +} + +void Clock::setTime(uint8_t hour, uint8_t minute) { + const uint8_t minuteselector = minute / 5; + + // if minuteselector >= 4 +1 to hour + if (minuteselector >= 4) + hour++; + + // convert to 12h format + if (hour >= 12) + hour = hour - 12; + + std::vector hourWord; + + hourWord = hour == 1 ? types::ein + : hour == 2 ? types::zwei + : hour == 3 ? types::drei + : hour == 4 ? types::vier + : hour == 5 ? types::fuenf2 + : hour == 6 ? types::sechs + : hour == 7 ? types::sieben + : hour == 8 ? types::acht + : hour == 9 ? types::neun + : hour == 10 ? types::zehn2 + : hour == 11 ? types::elf + : hour == 0 ? types::zwoelf + : hourWord; + + printWord(hourWord, Adafruit_NeoPixel::Color(0, 150, 0)); + + // fuenf / zehn / viertl word + std::vector minuteWord; + if (minuteselector == 1 || minuteselector == 5 || minuteselector == 7 || minuteselector == 11) + minuteWord = types::fuenf; + else if (minuteselector == 2 || minuteselector == 4 || minuteselector == 8 || minuteselector == 10) + minuteWord = types::zehn; + else if (minuteselector == 3) + minuteWord = types::viertel; + else if (minuteselector == 9) + minuteWord = types::dreiviertel; + else if (minuteselector == 6) + minuteWord = types::halb; + + printWord(minuteWord, Adafruit_NeoPixel::Color(0, 150, 0)); + + // vor / nach + std::vector vornachWord; + if (minuteselector == 1 || minuteselector == 2 || minuteselector == 3 || minuteselector == 7 || minuteselector == 8) + vornachWord = types::nach; + else if (minuteselector == 4 || minuteselector == 5 || minuteselector == 10 || minuteselector == 11) + vornachWord = types::vor; + printWord(vornachWord, Adafruit_NeoPixel::Color(150, 150, 150)); + + // halb + if (minuteselector >= 4 && minuteselector <= 8) + printWord(types::halb, Adafruit_NeoPixel::Color(150, 0, 0)); +} + +const std::vector types::es = calcPixels(0, 0, 2); +const std::vector types::ist = calcPixels(3, 0, 3); +const std::vector types::fuenf = calcPixels(8, 0, 4); +const std::vector types::zehn = calcPixels(0, 1, 4); +const std::vector types::zwanzig = calcPixels(5, 1, 7); +const std::vector types::drei = calcPixels(1, 2, 3); +const std::vector types::viertel = calcPixels(5, 2, 7); +const std::vector types::dreiviertel = calcPixels(1, 2, 11); +const std::vector types::vor = calcPixels(0, 3, 3); +const std::vector types::nach = calcPixels(3, 3, 4); +const std::vector types::halb = calcPixels(8, 3, 4); +const std::vector types::zwoelf = calcPixels(0, 4, 5); +const std::vector types::sieben = calcPixels(6, 4, 6); +const std::vector types::ein = calcPixels(0, 5, 3); +const std::vector types::eins = calcPixels(0, 5, 4); +const std::vector types::vier = calcPixels(4, 5, 4); +const std::vector types::acht = calcPixels(8, 5, 4); +const std::vector types::sechs = calcPixels(0, 6, 5); +const std::vector types::zwei = calcPixels(6, 6, 5); +const std::vector types::fuenf2 = calcPixels(1, 7, 5); +const std::vector types::elf = calcPixels(5, 7, 3); +const std::vector types::zehn2 = calcPixels(8, 7, 4); +const std::vector types::neun = calcPixels(0, 8, 4); +const std::vector types::drei2 = calcPixels(4, 8, 4); +const std::vector types::ein2 = calcPixels(6, 8, 3); +const std::vector types::uhr = calcPixels(9, 8, 3); + +uint8_t types::convert(uint8_t x, uint8_t y) { + const bool upRow = (x % 2) == 0; + + int val = x * 9 + y; + // if its a row upwards we need to calculate the additional down and up pixels + if (upRow) + // we need to go the rest down (9 -y) and up (*2) and subtract the too many added 10 + val += ((9 - y) * 2) - 10; + return val; +} + +std::vector types::calcPixels(uint8_t x, uint8_t y, uint8_t nrPixels) { + std::vector vec; + for (uint8_t i = 0; i < nrPixels; i++) { + vec.push_back(convert(x + i, y)); + } + + return vec; +} diff --git a/src/Clock.h b/src/Clock.h new file mode 100644 index 0000000..7befcec --- /dev/null +++ b/src/Clock.h @@ -0,0 +1,48 @@ +// +// Created by lukas on 06.03.21. +// + +#ifndef LEDSTRIPINTERFACE_CLOCK_H +#define LEDSTRIPINTERFACE_CLOCK_H + +#include +#include "Adafruit_NeoPixel.h" +#include "Arduino.h" +#include "../.pio/libdeps/node32s/Adafruit NeoPixel/Adafruit_NeoPixel.h" + +#define NUMPIXELS 108 // Popular NeoPixel ring size + +class Clock { + private: + Adafruit_NeoPixel strip{}; + + void paintAllwaysOnLeds(); + void printWord(std::vector word, uint32_t color); + void setTime(uint8_t hour, uint8_t minute); + + public: + Clock(); + void init(); + void refreshTime(); + + Ticker refreshTicker; +}; +struct types { + static const std::vector + es, ist, fuenf, + zehn, zwanzig, drei, + viertel, dreiviertel, vor, + nach, halb, zwoelf, + sieben, ein, eins, + vier, acht, sechs, + zwei, fuenf2, elf, + zehn2, neun, drei2, + ein2, uhr; + + static uint8_t convert(uint8_t x, uint8_t y); + + static std::vector calcPixels(uint8_t x, uint8_t y, uint8_t nrPixels); +}; + + +#endif // LEDSTRIPINTERFACE_CLOCK_H diff --git a/src/ClockService.cpp b/src/ClockService.cpp new file mode 100644 index 0000000..c8896f8 --- /dev/null +++ b/src/ClockService.cpp @@ -0,0 +1,38 @@ +// +// Created by lukas on 06.03.21. +// + +#include "ClockService.h" + +ClockService::ClockService(AsyncWebServer* server, SecurityManager* securityManager) : + _httpEndpoint(LightState::read, + LightState::update, + this, + server, + LIGHT_SETTINGS_ENDPOINT_PATH, + securityManager, + AuthenticationPredicates::IS_AUTHENTICATED), + _webSocket(LightState::read, + LightState::update, + this, + server, + LIGHT_SETTINGS_SOCKET_PATH, + securityManager, + AuthenticationPredicates::IS_AUTHENTICATED), + clock() { + // configure led to be output + pinMode(LED_PIN, OUTPUT); + + // configure settings service update handler to update LED state + addUpdateHandler([&](const String& originId) { onConfigUpdated(); }, false); +} + +void ClockService::begin() { + _state.ledOn = DEFAULT_LED_STATE; + onConfigUpdated(); + clock.init(); +} + +void ClockService::onConfigUpdated() { + digitalWrite(LED_PIN, _state.ledOn ? LED_ON : LED_OFF); +} diff --git a/src/LightStateService.h b/src/ClockService.h similarity index 76% rename from src/LightStateService.h rename to src/ClockService.h index e9c9a9c..28797ae 100644 --- a/src/LightStateService.h +++ b/src/ClockService.h @@ -1,11 +1,13 @@ -#ifndef LightStateService_h -#define LightStateService_h +// +// Created by lukas on 06.03.21. +// -#include +#pragma once #include #include #include +#include "Clock.h" #define LED_PIN 2 #define PRINT_DELAY 5000 @@ -50,7 +52,7 @@ class LightState { static StateUpdateResult haUpdate(JsonObject& root, LightState& lightState) { String state = root["state"]; - // parse new led state + // parse new led state boolean newState = false; if (state.equals(ON_STATE)) { newState = true; @@ -66,23 +68,17 @@ class LightState { } }; -class LightStateService : public StatefulService { +class ClockService : public StatefulService { public: - LightStateService(AsyncWebServer* server, - SecurityManager* securityManager, - AsyncMqttClient* mqttClient, - LightMqttSettingsService* lightMqttSettingsService); + ClockService(AsyncWebServer* server, SecurityManager* securityManager); void begin(); private: HttpEndpoint _httpEndpoint; - MqttPubSub _mqttPubSub; WebSocketTxRx _webSocket; - AsyncMqttClient* _mqttClient; - LightMqttSettingsService* _lightMqttSettingsService; - void registerConfig(); + Clock clock; + void onConfigUpdated(); }; -#endif diff --git a/src/LightMqttSettingsService.cpp b/src/LightMqttSettingsService.cpp deleted file mode 100644 index bddd909..0000000 --- a/src/LightMqttSettingsService.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include - -LightMqttSettingsService::LightMqttSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : - _httpEndpoint(LightMqttSettings::read, - LightMqttSettings::update, - this, - server, - LIGHT_BROKER_SETTINGS_PATH, - securityManager, - AuthenticationPredicates::IS_AUTHENTICATED), - _fsPersistence(LightMqttSettings::read, LightMqttSettings::update, this, fs, LIGHT_BROKER_SETTINGS_FILE) { -} - -void LightMqttSettingsService::begin() { - _fsPersistence.readFromFS(); -} diff --git a/src/LightMqttSettingsService.h b/src/LightMqttSettingsService.h deleted file mode 100644 index e7b66b0..0000000 --- a/src/LightMqttSettingsService.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef LightMqttSettingsService_h -#define LightMqttSettingsService_h - -#include -#include -#include - -#define LIGHT_BROKER_SETTINGS_FILE "/config/brokerSettings.json" -#define LIGHT_BROKER_SETTINGS_PATH "/rest/brokerSettings" - -class LightMqttSettings { - public: - String mqttPath; - String name; - String uniqueId; - - static void read(LightMqttSettings& settings, JsonObject& root) { - root["mqtt_path"] = settings.mqttPath; - root["name"] = settings.name; - root["unique_id"] = settings.uniqueId; - } - - static StateUpdateResult update(JsonObject& root, LightMqttSettings& settings) { - settings.mqttPath = root["mqtt_path"] | SettingValue::format("homeassistant/light/#{unique_id}"); - settings.name = root["name"] | SettingValue::format("light-#{unique_id}"); - settings.uniqueId = root["unique_id"] | SettingValue::format("light-#{unique_id}"); - return StateUpdateResult::CHANGED; - } -}; - -class LightMqttSettingsService : public StatefulService { - public: - LightMqttSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager); - void begin(); - - private: - HttpEndpoint _httpEndpoint; - FSPersistence _fsPersistence; -}; - -#endif // end LightMqttSettingsService_h diff --git a/src/LightStateService.cpp b/src/LightStateService.cpp deleted file mode 100644 index 8169622..0000000 --- a/src/LightStateService.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#include - -LightStateService::LightStateService(AsyncWebServer* server, - SecurityManager* securityManager, - AsyncMqttClient* mqttClient, - LightMqttSettingsService* lightMqttSettingsService) : - _httpEndpoint(LightState::read, - LightState::update, - this, - server, - LIGHT_SETTINGS_ENDPOINT_PATH, - securityManager, - AuthenticationPredicates::IS_AUTHENTICATED), - _mqttPubSub(LightState::haRead, LightState::haUpdate, this, mqttClient), - _webSocket(LightState::read, - LightState::update, - this, - server, - LIGHT_SETTINGS_SOCKET_PATH, - securityManager, - AuthenticationPredicates::IS_AUTHENTICATED), - _mqttClient(mqttClient), - _lightMqttSettingsService(lightMqttSettingsService) { - // configure led to be output - pinMode(LED_PIN, OUTPUT); - - // configure MQTT callback - _mqttClient->onConnect(std::bind(&LightStateService::registerConfig, this)); - - // configure update handler for when the light settings change - _lightMqttSettingsService->addUpdateHandler([&](const String& originId) { registerConfig(); }, false); - - // configure settings service update handler to update LED state - addUpdateHandler([&](const String& originId) { onConfigUpdated(); }, false); -} - -void LightStateService::begin() { - _state.ledOn = DEFAULT_LED_STATE; - onConfigUpdated(); -} - -void LightStateService::onConfigUpdated() { - digitalWrite(LED_PIN, _state.ledOn ? LED_ON : LED_OFF); -} - -void LightStateService::registerConfig() { - if (!_mqttClient->connected()) { - return; - } - String configTopic; - String subTopic; - String pubTopic; - - DynamicJsonDocument doc(256); - _lightMqttSettingsService->read([&](LightMqttSettings& settings) { - configTopic = settings.mqttPath + "/config"; - subTopic = settings.mqttPath + "/set"; - pubTopic = settings.mqttPath + "/state"; - doc["~"] = settings.mqttPath; - doc["name"] = settings.name; - doc["unique_id"] = settings.uniqueId; - }); - doc["cmd_t"] = "~/set"; - doc["stat_t"] = "~/state"; - doc["schema"] = "json"; - doc["brightness"] = false; - - String payload; - serializeJson(doc, payload); - _mqttClient->publish(configTopic.c_str(), 0, false, payload.c_str()); - - _mqttPubSub.configureTopics(pubTopic, subTopic); -} diff --git a/src/main.cpp b/src/main.cpp index 0b1081a..90af6c0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,17 +1,12 @@ #include -#include -#include +#include "ClockService.h" #define SERIAL_BAUD_RATE 115200 AsyncWebServer server(80); ESP8266React esp8266React(&server); -LightMqttSettingsService lightMqttSettingsService = - LightMqttSettingsService(&server, esp8266React.getFS(), esp8266React.getSecurityManager()); -LightStateService lightStateService = LightStateService(&server, - esp8266React.getSecurityManager(), - esp8266React.getMqttClient(), - &lightMqttSettingsService); + +ClockService cservice = ClockService(&server, esp8266React.getSecurityManager()); void setup() { // start serial and filesystem @@ -21,10 +16,7 @@ void setup() { esp8266React.begin(); // load the initial light settings - lightStateService.begin(); - - // start the light service - lightMqttSettingsService.begin(); + cservice.begin(); // start the server server.begin();