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

This commit is contained in:
Lukas Heiligenbrunner 2021-03-17 16:57:54 +00:00
parent 78b38fc759
commit 9b58bea546
10 changed files with 315 additions and 100 deletions

View File

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

View File

@ -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<InformationState>;
class DemoInformation extends Component<InformationStateWebSocketControllerProps> {
render() {
return (
<SectionContent title='Demo Information' titleGutter>
<SectionContent title='WordClock Information' titleGutter>
<Typography variant="body1" paragraph>
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
</Typography>
<Typography variant="body1" paragraph>
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.
</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."
<WebSocketFormLoader
{...this.props}
render={props => (
<LightStateWebSocketControllerForm {...props} />
)}
/>
</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>
)
}
}
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 {
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.
//
#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<uint8_t>& 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<uint8_t> 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)
} 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)
} 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<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;
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)
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));
}
}
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
#define LEDSTRIPINTERFACE_CLOCK_H
#include "Arduino.h"
#include <Ticker.h>
#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<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();
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();
};

View File

@ -5,7 +5,6 @@
#pragma once
#include <HttpEndpoint.h>
#include <MqttPubSub.h>
#include <WebSocketTxRx.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) {
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

View File

@ -1,5 +1,6 @@
#include <ESP8266React.h>
#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();