Merge branch 'twentyaftersyntyx' into 'master'

add option to switch to the TwentyAfter Syntax and show UHR always

See merge request lukas/ledstripinterface!1
This commit is contained in:
Lukas Heiligenbrunner 2021-03-17 16:57:54 +00:00
commit 4ed74164e8
10 changed files with 315 additions and 100 deletions

View File

@ -1,5 +1,5 @@
{ {
"name": "esp8266-react", "name": "wordclock",
"version": "0.1.0", "version": "0.1.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,

View File

@ -1,77 +1,91 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Typography, Box, List, ListItem, ListItemText } from '@material-ui/core'; import {Typography, Switch} from '@material-ui/core';
import { SectionContent } from '../components'; 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<InformationState>;
class DemoInformation extends Component<InformationStateWebSocketControllerProps> {
render() { render() {
return ( return (
<SectionContent title='Demo Information' titleGutter> <SectionContent title='WordClock Information' titleGutter>
<Typography variant="body1" paragraph> <Typography variant="body1" paragraph>
This simple demo project allows you to control the built-in LED. Welcome to the WordClock Overview page. Here you can turn on and off your WordClock and set Basic Settings
It demonstrates how the esp8266-react framework may be extended for your own IoT project.
</Typography> </Typography>
<Typography variant="body1" paragraph> <WebSocketFormLoader
It is recommended that you keep your project interface code under the project directory. {...this.props}
This serves to isolate your project code from the from the rest of the user interface which should render={props => (
simplify merges should you wish to update your project with future framework changes. <LightStateWebSocketControllerForm {...props} />
</Typography> )}
<Typography variant="body1" paragraph>
The demo project interface code is stored in the 'interface/src/project' directory:
</Typography>
<List>
<ListItem>
<ListItemText
primary="ProjectMenu.tsx"
secondary="You can add your project's screens to the side bar here."
/> />
</ListItem>
<ListItem>
<ListItemText
primary="ProjectRouting.tsx"
secondary="The routing which controls the screens of your project."
/>
</ListItem>
<ListItem>
<ListItemText
primary="DemoProject.tsx"
secondary="This screen, with tabs and tab routing."
/>
</ListItem>
<ListItem>
<ListItemText
primary="DemoInformation.tsx"
secondary="The demo information page."
/>
</ListItem>
<ListItem>
<ListItemText
primary="LightStateRestController.tsx"
secondary="A form which lets the user control the LED over a REST service."
/>
</ListItem>
<ListItem>
<ListItemText
primary="LightStateWebSocketController.tsx"
secondary="A form which lets the user control and monitor the status of the LED over WebSockets."
/>
</ListItem>
<ListItem>
<ListItemText
primary="LightMqttSettingsController.tsx"
secondary="A form which lets the user change the MQTT settings for MQTT based control of the LED."
/>
</ListItem>
</List>
<Box mt={2}>
<Typography variant="body1">
See the project <a href="https://github.com/rjwats/esp8266-react/">README</a> for a full description of the demo project.
</Typography>
</Box>
</SectionContent> </SectionContent>
) )
} }
} }
export default DemoInformation; export default webSocketController(INFORMATION_WEBSOCKET_URL, 100, DemoInformation);
type InformationWebSocketControllerFormProps = WebSocketFormProps<InformationState>;
function LightStateWebSocketControllerForm(props: InformationWebSocketControllerFormProps) {
const { data, saveData, setData } = props;
const changeWordClockOn = (event: React.ChangeEvent<HTMLInputElement>) => {
setData({wordclockOn: event.target.checked,twentyAfterSyntax: data.twentyAfterSyntax, uhrAlwaysOn: data.uhrAlwaysOn }, saveData);
}
const changeTwentyAfterSyntax = (event: React.ChangeEvent<HTMLInputElement>) => {
setData({wordclockOn: data.wordclockOn, twentyAfterSyntax: event.target.checked, uhrAlwaysOn: data.uhrAlwaysOn }, saveData);
}
const changeUhrAlwaysOn = (event: React.ChangeEvent<HTMLInputElement>) => {
setData({wordclockOn: data.wordclockOn,twentyAfterSyntax: data.twentyAfterSyntax, uhrAlwaysOn: event.target.checked }, saveData);
}
return (
<ValidatorForm onSubmit={saveData}>
<BlockFormControlLabel
control={
<Switch
checked={data.wordclockOn}
onChange={changeWordClockOn}
color="primary"
/>
}
label="WordClock turned on?"
/>
<BlockFormControlLabel
control={
<Switch
checked={data.twentyAfterSyntax}
onChange={changeTwentyAfterSyntax}
color="primary"
/>
}
label="Use Twenty-After Syntax?"
/>
<BlockFormControlLabel
control={
<Switch
checked={data.uhrAlwaysOn}
onChange={changeUhrAlwaysOn}
color="primary"
/>
}
label="Always Display UHR label"
/>
</ValidatorForm>
);
}

View File

@ -1,3 +1,9 @@
export interface LightState { export interface LightState {
led_on: boolean; led_on: boolean;
} }
export interface InformationState {
wordclockOn: boolean;
twentyAfterSyntax: boolean;
uhrAlwaysOn: boolean;
}

View File

@ -2,10 +2,10 @@
// Created by lukas on 06.03.21. // Created by lukas on 06.03.21.
// //
#include "Clock.h"
#include "Types.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() { void Clock::init() {
@ -28,13 +28,13 @@ void Clock::turnOn() {
this->refreshTime(); this->refreshTime();
} }
void Clock::paintAllwaysOnLeds() { void Clock::paintAlwaysOnLeds() {
printWord(types::es, Adafruit_NeoPixel::Color(150, 150, 0)); printWord(types::es, Adafruit_NeoPixel::Color(150, 150, 0));
printWord(types::ist, Adafruit_NeoPixel::Color(150, 0, 150)); printWord(types::ist, Adafruit_NeoPixel::Color(150, 0, 150));
} }
void Clock::printWord(const std::vector<uint8_t>& word, uint32_t color) { void Clock::printWord(const std::vector<uint8_t>& word, uint32_t color) {
for (const int i : word) { for (const uint8_t i : word) {
strip.setPixelColor(i, color); strip.setPixelColor(i, color);
} }
} }
@ -54,17 +54,18 @@ void Clock::refreshTime() {
const uint8_t hour = loctime->tm_hour; const uint8_t hour = loctime->tm_hour;
const uint8_t minute = loctime->tm_min; const uint8_t minute = loctime->tm_min;
paintAllwaysOnLeds(); paintAlwaysOnLeds();
setTime(hour, minute); setTime(hour, minute, this->twentyAfterSyntax, this->clockAlwaysOn);
strip.show(); 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; const uint8_t minuteselector = minute / 5;
// if minuteselector >= 4 +1 to hour // if minuteselector >= 4 +1 to hour
if (minuteselector >= 4) if (twentyAfterSyntax ? minuteselector >= 5 : minuteselector >= 4)
hour++; hour++;
// convert to 12h format // convert to 12h format
@ -86,36 +87,69 @@ void Clock::setTime(uint8_t hour, uint8_t minute) {
: hour == 0 || hour == 12 ? types::zwoelf : hour == 0 || hour == 12 ? types::zwoelf
: hourWord; : 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 // fuenf / zehn / viertl word
std::vector<uint8_t> minuteWord; std::vector<uint8_t> minuteWord;
if (minuteselector == 1 || minuteselector == 5 || minuteselector == 7 || minuteselector == 11) if (minuteSelector == 1 || minuteSelector == 5 || minuteSelector == 7 || minuteSelector == 11) {
minuteWord = types::fuenf; minuteWord = types::fuenf;
else if (minuteselector == 2 || minuteselector == 4 || minuteselector == 8 || minuteselector == 10) } 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; minuteWord = types::zehn;
else if (minuteselector == 3) } else if (minuteSelector == 3)
minuteWord = types::viertel; minuteWord = types::viertel;
else if (minuteselector == 9) else if (minuteSelector == 9)
minuteWord = types::dreiviertel; minuteWord = types::dreiviertel;
else if (minuteselector == 6) else if (minuteSelector == 6)
minuteWord = types::halb; minuteWord = types::halb;
printWord(minuteWord, Adafruit_NeoPixel::Color(0, 150, 0)); printWord(minuteWord, Adafruit_NeoPixel::Color(0, 150, 0));
// vor / nach // vor / nach
std::vector<uint8_t> vornachWord; std::vector<uint8_t> 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; 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; vornachWord = types::vor;
printWord(vornachWord, Adafruit_NeoPixel::Color(150, 150, 150)); printWord(vornachWord, Adafruit_NeoPixel::Color(150, 150, 150));
// halb // halb
if (minuteselector >= 4 && minuteselector <= 8) 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)); printWord(types::halb, Adafruit_NeoPixel::Color(150, 0, 0));
}
// uhr }
if (minuteselector == 0)
printWord(types::uhr, Adafruit_NeoPixel::Color(0, 0, 150)); void Clock::useTwentyAfterSyntax(bool twentyAFterSyntax) {
this->twentyAfterSyntax = twentyAFterSyntax;
}
void Clock::useAlwaysOnUhrSyntax(bool alwaysOnUhr) {
this->clockAlwaysOn = alwaysOnUhr;
}
void Clock::update() {
if (refreshTicker.active())
this->refreshTime();
} }

View File

@ -5,22 +5,57 @@
#ifndef LEDSTRIPINTERFACE_CLOCK_H #ifndef LEDSTRIPINTERFACE_CLOCK_H
#define LEDSTRIPINTERFACE_CLOCK_H #define LEDSTRIPINTERFACE_CLOCK_H
#include "Arduino.h"
#include <Ticker.h> #include <Ticker.h>
#include "Adafruit_NeoPixel.h" #include "Adafruit_NeoPixel.h"
#include "Arduino.h" #include "Arduino.h"
#include "LoadAnimator.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 { class Clock {
private: private:
Adafruit_NeoPixel strip{}; Adafruit_NeoPixel strip{};
LoadAnimator animator; 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<uint8_t>& word, uint32_t color); void printWord(const std::vector<uint8_t>& 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(); void refreshTime();
public: public:
@ -32,7 +67,7 @@ class Clock {
void init(); void init();
/** /**
* turn of the wordclock * turn off the wordclock
*/ */
void turnOff(); void turnOff();
@ -41,7 +76,10 @@ class Clock {
*/ */
void turnOn(); void turnOn();
Ticker refreshTicker; void useTwentyAfterSyntax(bool twentyAFterSyntax);
void useAlwaysOnUhrSyntax(bool alwaysOnUhr);
void update();
}; };

View File

@ -5,7 +5,6 @@
#pragma once #pragma once
#include <HttpEndpoint.h> #include <HttpEndpoint.h>
#include <MqttPubSub.h>
#include <WebSocketTxRx.h> #include <WebSocketTxRx.h>
#include "Clock.h" #include "Clock.h"

View File

@ -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();
}

79
src/InformationService.h Normal file
View File

@ -0,0 +1,79 @@
//
// Created by lukas on 09.03.21.
//
#ifndef LEDSTRIPINTERFACE_INFORMATIONSERVICE_H
#define LEDSTRIPINTERFACE_INFORMATIONSERVICE_H
#include <HttpEndpoint.h>
#include <WebSocketTxRx.h>
#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<InformationState>{
public:
InformationService(AsyncWebServer* server, SecurityManager* securityManager);
void begin();
private:
WebSocketTxRx<InformationState> _webSocket;
Clock clock;
void onConfigUpdated();
};
#endif // LEDSTRIPINTERFACE_INFORMATIONSERVICE_H

View File

@ -34,7 +34,7 @@ const std::vector<uint8_t> types::uhr = calcPixels(9, 8, 3);
uint8_t types::convert(uint8_t x, uint8_t y) { uint8_t types::convert(uint8_t x, uint8_t y) {
const bool upRow = (x % 2) == 0; 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 its a row upwards we need to calculate the additional down and up pixels
if (upRow) if (upRow)
// we need to go the rest down (9 -y) and up (*2) and subtract the too many added 10 // we need to go the rest down (9 -y) and up (*2) and subtract the too many added 10

View File

@ -1,5 +1,6 @@
#include <ESP8266React.h> #include <ESP8266React.h>
#include "ClockService.h" #include "ClockService.h"
#include "InformationService.h"
#define SERIAL_BAUD_RATE 115200 #define SERIAL_BAUD_RATE 115200
@ -9,18 +10,21 @@
AsyncWebServer server(80); AsyncWebServer server(80);
ESP8266React esp8266React(&server); ESP8266React esp8266React(&server);
ClockService cservice = ClockService(&server, esp8266React.getSecurityManager()); //ClockService cservice = ClockService(&server, esp8266React.getSecurityManager());
InformationService iservice = InformationService(&server, esp8266React.getSecurityManager());
void setup() { void setup() {
// start serial and filesystem // start serial and filesystem
Serial.begin(SERIAL_BAUD_RATE); Serial.begin(SERIAL_BAUD_RATE);
Serial.printf("Starting WordClock %s", VERSION); Serial.printf("Starting WordClock %s\n", VERSION);
// start the framework and demo project // start the framework and demo project
esp8266React.begin(); esp8266React.begin();
// load the initial light settings // load the initial light settings
cservice.begin(); // cservice.begin();
iservice.begin();
// start the server // start the server
server.begin(); server.begin();