From 9b58bea546053f4c55701e1b3bba0e7dd99f7e8d Mon Sep 17 00:00:00 2001 From: Lukas Heiligenbrunner Date: Wed, 17 Mar 2021 16:57:54 +0000 Subject: [PATCH] add option to switch to the TwentyAfter Syntax and show UHR always --- interface/package-lock.json | 2 +- interface/src/project/DemoInformation.tsx | 144 ++++++++++++---------- interface/src/project/types.ts | 6 + src/Clock.cpp | 82 ++++++++---- src/Clock.h | 48 +++++++- src/ClockService.h | 1 - src/InformationService.cpp | 41 ++++++ src/InformationService.h | 79 ++++++++++++ src/Types.cpp | 2 +- src/main.cpp | 10 +- 10 files changed, 315 insertions(+), 100 deletions(-) create mode 100644 src/InformationService.cpp create mode 100644 src/InformationService.h diff --git a/interface/package-lock.json b/interface/package-lock.json index a7a2438..8117e41 100644 --- a/interface/package-lock.json +++ b/interface/package-lock.json @@ -1,5 +1,5 @@ { - "name": "esp8266-react", + "name": "wordclock", "version": "0.1.0", "lockfileVersion": 1, "requires": true, diff --git a/interface/src/project/DemoInformation.tsx b/interface/src/project/DemoInformation.tsx index 2b9967b..12bfbdd 100644 --- a/interface/src/project/DemoInformation.tsx +++ b/interface/src/project/DemoInformation.tsx @@ -1,77 +1,91 @@ import React, { Component } from 'react'; -import { Typography, Box, List, ListItem, ListItemText } from '@material-ui/core'; -import { SectionContent } from '../components'; +import {Typography, Switch} from '@material-ui/core'; +import { + BlockFormControlLabel, + SectionContent, + webSocketController, + WebSocketControllerProps, + WebSocketFormLoader, + WebSocketFormProps +} from '../components'; -class DemoInformation extends Component { +import {InformationState} from "./types"; +import {WEB_SOCKET_ROOT} from "../api"; +import {ValidatorForm} from "react-material-ui-form-validator"; + +export const INFORMATION_WEBSOCKET_URL = WEB_SOCKET_ROOT + "information"; +type InformationStateWebSocketControllerProps = WebSocketControllerProps; + +class DemoInformation extends Component { render() { return ( - + - This simple demo project allows you to control the built-in LED. - It demonstrates how the esp8266-react framework may be extended for your own IoT project. + Welcome to the WordClock Overview page. Here you can turn on and off your WordClock and set Basic Settings - - It is recommended that you keep your project interface code under the project directory. - This serves to isolate your project code from the from the rest of the user interface which should - simplify merges should you wish to update your project with future framework changes. - - - The demo project interface code is stored in the 'interface/src/project' directory: - - - - - - - - - - - - - - - - - - - - - - - - - - - See the project README for a full description of the demo project. - - + ( + + )} + /> ) } - } -export default DemoInformation; +export default webSocketController(INFORMATION_WEBSOCKET_URL, 100, DemoInformation); + +type InformationWebSocketControllerFormProps = WebSocketFormProps; + +function LightStateWebSocketControllerForm(props: InformationWebSocketControllerFormProps) { + const { data, saveData, setData } = props; + + const changeWordClockOn = (event: React.ChangeEvent) => { + setData({wordclockOn: event.target.checked,twentyAfterSyntax: data.twentyAfterSyntax, uhrAlwaysOn: data.uhrAlwaysOn }, saveData); + } + + const changeTwentyAfterSyntax = (event: React.ChangeEvent) => { + setData({wordclockOn: data.wordclockOn, twentyAfterSyntax: event.target.checked, uhrAlwaysOn: data.uhrAlwaysOn }, saveData); + } + + const changeUhrAlwaysOn = (event: React.ChangeEvent) => { + setData({wordclockOn: data.wordclockOn,twentyAfterSyntax: data.twentyAfterSyntax, uhrAlwaysOn: event.target.checked }, saveData); + } + + return ( + + + } + label="WordClock turned on?" + /> + + } + label="Use Twenty-After Syntax?" + /> + + } + label="Always Display UHR label" + /> + + ); +} diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index df45ca0..dbf94f9 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -1,3 +1,9 @@ export interface LightState { led_on: boolean; +} + +export interface InformationState { + wordclockOn: boolean; + twentyAfterSyntax: boolean; + uhrAlwaysOn: boolean; } \ No newline at end of file diff --git a/src/Clock.cpp b/src/Clock.cpp index af617c5..01ef58a 100644 --- a/src/Clock.cpp +++ b/src/Clock.cpp @@ -2,10 +2,10 @@ // Created by lukas on 06.03.21. // -#include "Clock.h" #include "Types.h" +#include "Clock.h" -Clock::Clock() : strip(NUMPIXELS, D5, NEO_GRB + NEO_KHZ800), animator(), refreshTicker() { +Clock::Clock() : strip(NUMPIXELS, STRIPPIN, NEO_GRB + NEO_KHZ800), animator(), refreshTicker() { } void Clock::init() { @@ -28,13 +28,13 @@ void Clock::turnOn() { this->refreshTime(); } -void Clock::paintAllwaysOnLeds() { +void Clock::paintAlwaysOnLeds() { printWord(types::es, Adafruit_NeoPixel::Color(150, 150, 0)); printWord(types::ist, Adafruit_NeoPixel::Color(150, 0, 150)); } void Clock::printWord(const std::vector& word, uint32_t color) { - for (const int i : word) { + for (const uint8_t i : word) { strip.setPixelColor(i, color); } } @@ -54,17 +54,18 @@ void Clock::refreshTime() { const uint8_t hour = loctime->tm_hour; const uint8_t minute = loctime->tm_min; - paintAllwaysOnLeds(); - setTime(hour, minute); + paintAlwaysOnLeds(); + setTime(hour, minute, this->twentyAfterSyntax, this->clockAlwaysOn); + strip.show(); - Serial.printf("Time now: %sh %sM", &hour, &minute); + Serial.printf("Time now: %uh %uM\n", hour, minute); } -void Clock::setTime(uint8_t hour, uint8_t minute) { +void Clock::setTime(uint8_t hour, uint8_t minute, bool twentyAfterSyntax, bool alwaysClockWord) { const uint8_t minuteselector = minute / 5; // if minuteselector >= 4 +1 to hour - if (minuteselector >= 4) + if (twentyAfterSyntax ? minuteselector >= 5 : minuteselector >= 4) hour++; // convert to 12h format @@ -86,36 +87,69 @@ void Clock::setTime(uint8_t hour, uint8_t minute) { : hour == 0 || hour == 12 ? types::zwoelf : hourWord; - printWord(hourWord, Adafruit_NeoPixel::Color(0, 150, 0)); + printWord(hourWord, Adafruit_NeoPixel::Color(0, 0, 150)); + // print the minute words and the corresponding after/before half words + printMinutes(minuteselector, twentyAfterSyntax); + + // uhr + if (minuteselector == 0 || alwaysClockWord) + printWord(types::uhr, Adafruit_NeoPixel::Color(150, 0, 0)); +} + +void Clock::printMinutes(uint8_t minuteSelector, bool twentyAfterSyntax) { // fuenf / zehn / viertl word std::vector minuteWord; - if (minuteselector == 1 || minuteselector == 5 || minuteselector == 7 || minuteselector == 11) + 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) + } else if (minuteSelector == 2 || minuteSelector == 4 || minuteSelector == 8 || minuteSelector == 10) { + // check if we use the twenty after syntax + if ((minuteSelector == 4 || minuteSelector == 8) && twentyAfterSyntax) + minuteWord = types::zwanzig; + else + minuteWord = types::zehn; + } else if (minuteSelector == 3) minuteWord = types::viertel; - else if (minuteselector == 9) + else if (minuteSelector == 9) minuteWord = types::dreiviertel; - else if (minuteselector == 6) + 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) + if (minuteSelector == 1 || minuteSelector == 2 || minuteSelector == 3 || + ((minuteSelector == 8) && + !twentyAfterSyntax) || // check if twentyafter syntax is enabled - if not display the the after + minuteSelector == 7 || + ((minuteSelector == 4) && + twentyAfterSyntax)) // check if twentyafter syntax is enabled - if yes display the the after vornachWord = types::nach; - else if (minuteselector == 4 || minuteselector == 5 || minuteselector == 10 || minuteselector == 11) + else if (minuteSelector == 5 || + ((minuteSelector == 4) && !twentyAfterSyntax) // check if twentyafter enabled if not -- display vor + || minuteSelector == 10 || minuteSelector == 11 || + ((minuteSelector == 8) && twentyAfterSyntax)) // check if twentyafter enabled if yess -- display vor 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)); + if (minuteSelector >= 4 && minuteSelector <= 8) { + // only 3 times if twentyafter syntax is on + if (!twentyAfterSyntax || (minuteSelector >= 5 && minuteSelector <= 7)) + printWord(types::halb, Adafruit_NeoPixel::Color(150, 0, 0)); + } +} - // uhr - if (minuteselector == 0) - printWord(types::uhr, Adafruit_NeoPixel::Color(0, 0, 150)); -} \ No newline at end of file +void Clock::useTwentyAfterSyntax(bool twentyAFterSyntax) { + this->twentyAfterSyntax = twentyAFterSyntax; +} + +void Clock::useAlwaysOnUhrSyntax(bool alwaysOnUhr) { + this->clockAlwaysOn = alwaysOnUhr; +} + +void Clock::update() { + if (refreshTicker.active()) + this->refreshTime(); +} diff --git a/src/Clock.h b/src/Clock.h index ffd2132..74b40b7 100644 --- a/src/Clock.h +++ b/src/Clock.h @@ -5,22 +5,57 @@ #ifndef LEDSTRIPINTERFACE_CLOCK_H #define LEDSTRIPINTERFACE_CLOCK_H +#include "Arduino.h" #include #include "Adafruit_NeoPixel.h" #include "Arduino.h" #include "LoadAnimator.h" -#define NUMPIXELS 108 // Popular NeoPixel ring size +// define the strip length of the wordclock +#define NUMPIXELS 108 +// define the esp pin the stip is attached to +#define STRIPPIN D5 class Clock { private: Adafruit_NeoPixel strip{}; LoadAnimator animator; + Ticker refreshTicker; - void paintAllwaysOnLeds(); + bool twentyAfterSyntax; + bool clockAlwaysOn; + + /** + * display the words which are always on (Es ist...) + */ + void paintAlwaysOnLeds(); + + /** + * paint a specific word + * @param word word pointer to print + * @param color the color in which to print it + */ void printWord(const std::vector& word, uint32_t color); - void setTime(uint8_t hour, uint8_t minute); + /** + * logic to print the correct minute word and its corresponding wods + * @param minuteSelector a number between 0 - 11 representing the minute in 5min distances + * @param twentyAfterSyntax whether to use a twenty after syntax or a 10min before half syntax + */ + void printMinutes(uint8_t minuteSelector, bool twentyAfterSyntax); + + /** + * set the whole time of the clock + * @param hour hour to set (0-24) + * @param minute minute to set(0-59) + * @param twentyAfterSyntax whether to use a twenty after syntax or a 10min before half syntax + * @param alwaysClockWord print the UHR word always and not only on full hours + */ + void setTime(uint8_t hour, uint8_t minute, bool twentyAfterSyntax = false, bool alwaysClockWord = false); + + /** + * load the current time and set it onto the clock + */ void refreshTime(); public: @@ -32,7 +67,7 @@ class Clock { void init(); /** - * turn of the wordclock + * turn off the wordclock */ void turnOff(); @@ -41,7 +76,10 @@ class Clock { */ void turnOn(); - Ticker refreshTicker; + void useTwentyAfterSyntax(bool twentyAFterSyntax); + void useAlwaysOnUhrSyntax(bool alwaysOnUhr); + + void update(); }; diff --git a/src/ClockService.h b/src/ClockService.h index a0d2ac8..c70edee 100644 --- a/src/ClockService.h +++ b/src/ClockService.h @@ -5,7 +5,6 @@ #pragma once #include -#include #include #include "Clock.h" diff --git a/src/InformationService.cpp b/src/InformationService.cpp new file mode 100644 index 0000000..30aa29e --- /dev/null +++ b/src/InformationService.cpp @@ -0,0 +1,41 @@ +// +// Created by lukas on 09.03.21. +// + +#include "InformationService.h" + +InformationService::InformationService(AsyncWebServer* server, SecurityManager* securityManager) : + _webSocket(InformationState::read, + InformationState::update, + this, + server, + INFORMATION_SETTINGS_SOCKET_PATH, + securityManager, + AuthenticationPredicates::IS_AUTHENTICATED), + clock() { + // configure settings service update handler to update LED state + addUpdateHandler([&](const String& originId) { onConfigUpdated(); }, false); +} + +void InformationService::begin() { + _state.wordClockOn = DEFAULT_CLOCK_STATE; + _state.twentyAfterSyntax = true; + _state.uhrAlwaysOn = false; + + onConfigUpdated(); + clock.init(); +} + +void InformationService::onConfigUpdated() { + // digitalWrite(LED_PIN, _state.ledOn ? LED_ON : LED_OFF); + if (_state.wordClockOn) { + clock.turnOn(); + } else { + clock.turnOff(); + } + + clock.useAlwaysOnUhrSyntax(_state.uhrAlwaysOn); + clock.useTwentyAfterSyntax(_state.twentyAfterSyntax); + + clock.update(); +} diff --git a/src/InformationService.h b/src/InformationService.h new file mode 100644 index 0000000..d5bcd32 --- /dev/null +++ b/src/InformationService.h @@ -0,0 +1,79 @@ +// +// Created by lukas on 09.03.21. +// + +#ifndef LEDSTRIPINTERFACE_INFORMATIONSERVICE_H +#define LEDSTRIPINTERFACE_INFORMATIONSERVICE_H + +#include +#include +#include "Clock.h" + +#define DEFAULT_CLOCK_STATE true +#define OFF_STATE "OFF" +#define ON_STATE "ON" + +#define INFORMATION_SETTINGS_SOCKET_PATH "/ws/information" + +class InformationState { + public: + bool wordClockOn; + bool twentyAfterSyntax; + bool uhrAlwaysOn; + + static void read(InformationState& settings, JsonObject& root) { + root["wordclockOn"] = settings.wordClockOn; + root["twentyAfterSyntax"] = settings.twentyAfterSyntax; + root["uhrAlwaysOn"] = settings.uhrAlwaysOn; + } + + static StateUpdateResult update(JsonObject& root, InformationState& lightState) { + const bool newOnState = root["wordclockOn"] | DEFAULT_CLOCK_STATE; + const bool newTwentyAfterState = root["twentyAfterSyntax"] | DEFAULT_CLOCK_STATE; + const bool newUhrAlwaysOnState = root["uhrAlwaysOn"] | DEFAULT_CLOCK_STATE; + + if (lightState.wordClockOn != newOnState || lightState.twentyAfterSyntax != newTwentyAfterState || lightState.uhrAlwaysOn != newUhrAlwaysOnState) { + lightState.wordClockOn = newOnState; + lightState.twentyAfterSyntax = newTwentyAfterState; + lightState.uhrAlwaysOn = newUhrAlwaysOnState; + return StateUpdateResult::CHANGED; + } + return StateUpdateResult::UNCHANGED; + } + +// static void haRead(InformationState& settings, JsonObject& root) { +// root["state"] = settings.wordClockOn ? ON_STATE : OFF_STATE; +// } +// +// static StateUpdateResult haUpdate(JsonObject& root, InformationState& lightState) { +// String state = root["state"]; +// // parse new led state +// boolean newState = false; +// if (state.equals(ON_STATE)) { +// newState = true; +// } else if (!state.equals(OFF_STATE)) { +// return StateUpdateResult::ERROR; +// } +// // change the new state, if required +// if (lightState.wordClockOn != newState) { +// lightState.wordClockOn = newState; +// return StateUpdateResult::CHANGED; +// } +// return StateUpdateResult::UNCHANGED; +// } +}; + +class InformationService : public StatefulService{ + public: + InformationService(AsyncWebServer* server, SecurityManager* securityManager); + void begin(); + + private: + WebSocketTxRx _webSocket; + + Clock clock; + + void onConfigUpdated(); +}; + +#endif // LEDSTRIPINTERFACE_INFORMATIONSERVICE_H diff --git a/src/Types.cpp b/src/Types.cpp index cc7a35a..72bb913 100644 --- a/src/Types.cpp +++ b/src/Types.cpp @@ -34,7 +34,7 @@ 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; + uint8_t 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 diff --git a/src/main.cpp b/src/main.cpp index 2cb0e0b..744e26e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,6 @@ #include #include "ClockService.h" +#include "InformationService.h" #define SERIAL_BAUD_RATE 115200 @@ -9,18 +10,21 @@ AsyncWebServer server(80); ESP8266React esp8266React(&server); -ClockService cservice = ClockService(&server, esp8266React.getSecurityManager()); +//ClockService cservice = ClockService(&server, esp8266React.getSecurityManager()); +InformationService iservice = InformationService(&server, esp8266React.getSecurityManager()); void setup() { // start serial and filesystem Serial.begin(SERIAL_BAUD_RATE); - Serial.printf("Starting WordClock %s", VERSION); + Serial.printf("Starting WordClock %s\n", VERSION); // start the framework and demo project esp8266React.begin(); // load the initial light settings - cservice.begin(); +// cservice.begin(); + + iservice.begin(); // start the server server.begin();