Compare commits
19 Commits
88e480716a
...
master
Author | SHA1 | Date | |
---|---|---|---|
57cfbe9a9c | |||
d0b0041a40 | |||
f4fbd95183 | |||
30a30b30de | |||
d2ac7c4903 | |||
2bed562f44 | |||
23df52cbb6 | |||
d3da31d6db | |||
59e84a6146 | |||
0d1dff14f7 | |||
c4b9b93d95 | |||
93e82c535e | |||
362e0760db | |||
cb0a0f89dc | |||
b773437d45 | |||
22df009807 | |||
07984234ab | |||
4f28b11cf7 | |||
c3b80e668b |
12
README.md
12
README.md
@@ -6,11 +6,17 @@ But it might as well be used as a fast clock display for model railroads.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
* WS-2812 based adressable LED chain forming the 7-segment display
|
||||
* number of LEDs per segment is configurable
|
||||
- WS-2812 based adressable LED chain forming the 7-segment display
|
||||
- number of LEDs per segment is configurable
|
||||
|
||||
This is, how it could look like (fyi: the big cargo car is 1:45 scale, the small one is 1:160):
|
||||
|
||||

|
||||
|
||||
The configuration menu allows the selection of real or fast clock and some options like colors, fastclock name or UTC time offset for real time:
|
||||
|
||||

|
||||
|
||||
## Links / References
|
||||
|
||||
- example of 3d printable segment frames
|
||||
- example of 3d printable segment frames
|
||||
|
BIN
doc/Clock1a.png
Normal file
BIN
doc/Clock1a.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 178 KiB |
BIN
doc/WebConfig.png
Normal file
BIN
doc/WebConfig.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
@@ -17,4 +17,5 @@ upload_port = /dev/cu.wchusbserial1420
|
||||
[lib_deps]
|
||||
library =
|
||||
WifiManager,
|
||||
Adafruit NeoPixel
|
||||
Adafruit NeoPixel,
|
||||
NTPClient
|
||||
|
163
src/ClockClient.cpp
Normal file
163
src/ClockClient.cpp
Normal file
@@ -0,0 +1,163 @@
|
||||
//
|
||||
// FILE: ClockClient.cpp
|
||||
// PURPOSE: UDP broadcast listener for fastclock (FREMO clock)
|
||||
//
|
||||
//
|
||||
|
||||
#include "ClockClient.h"
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <WiFiUdp.h>
|
||||
|
||||
#ifdef ESP8266
|
||||
extern "C" {
|
||||
#include "user_interface.h"
|
||||
}
|
||||
#endif
|
||||
|
||||
//const char * const PROGMEM clockConfig[] = {"ipMulticast:string:239.50.50.20", "ipInterface:string:192.168.0.100", "listenPort:int:2000", "interpolate:boolean:true"};
|
||||
//const char * const PROGMEM clockConfig[] = {"ipMulticast:string:239.50.50.20", "ipInterface:string:127.0.0.1", "listenPort:int:2000", "listenToClock:string:MRclock#2"};
|
||||
const char * const PROGMEM clockConfig[] = {
|
||||
"ipMulticast:string:239.50.50.20",
|
||||
"ipInterface:string:127.0.0.1",
|
||||
"listenPort:int:2000",
|
||||
"listenToClock:string:DefaultClock"
|
||||
};
|
||||
|
||||
static WiFiUDP udp;
|
||||
|
||||
#define CLOCK_PACKET_SIZE 1024
|
||||
static byte packetBuffer[CLOCK_PACKET_SIZE+1]; //buffer to hold incoming and outgoing packets
|
||||
|
||||
const char * const ClockClient::getLastMessage() { return (const char *) packetBuffer; }
|
||||
|
||||
String ClockClient::name{""};
|
||||
String ClockClient::text{""};
|
||||
String ClockClient::clocktype{""};
|
||||
boolean ClockClient::active{false};
|
||||
float ClockClient::speed{1.0};
|
||||
String ClockClient::clock{""};
|
||||
String ClockClient::weekday{""};
|
||||
int ClockClient::clockHours{0};
|
||||
int ClockClient::clockMinutes{0};
|
||||
int ClockClient::clockSeconds{0};
|
||||
int ClockClient::numClockChangeCallbacks{0};
|
||||
ClockChangeCallback ClockClient::clockChangeCallback[];
|
||||
|
||||
void ClockClient::addClockChangeCallback(ClockChangeCallback _clockChangeCallback) {
|
||||
if (numClockChangeCallbacks >= MAX_CLOCK_CHANGE_CALLBACKS) {
|
||||
Debug::outln(F("ERROR: Too many clock change callbacks registered!"));
|
||||
return;
|
||||
}
|
||||
clockChangeCallback[numClockChangeCallbacks++] = _clockChangeCallback;
|
||||
}
|
||||
|
||||
|
||||
static IPAddress interfaceAddr;
|
||||
static IPAddress multicast;
|
||||
static uint16_t port = 2000;
|
||||
|
||||
IPAddress ClockClient::getMulticastIP() { return multicast; }
|
||||
|
||||
int ClockClient::getListenPort() { return port; }
|
||||
|
||||
void ClockClient::begin() {
|
||||
debug.outln("Beginning fastclock client", DEBUG_MAX_INFO);
|
||||
config.loadFile("clockclient.cfg", clockConfig, sizeof(clockConfig)/sizeof(clockConfig[0]));
|
||||
name = config.getString("listenToClock");
|
||||
// WiFi.mode(WIFI_STA);
|
||||
multicast.fromString(config.getString("ipMulticast"));
|
||||
port = config.getInt("listenPort");
|
||||
logHeap();
|
||||
|
||||
delay(100);
|
||||
if (!udp.beginMulticast(WiFi.localIP(), multicast, port)) {
|
||||
debug.outln(F("ERROR: failed to begin UDP"));
|
||||
} else {
|
||||
debug.outln(F("Successfully started multicast receiver"));
|
||||
}
|
||||
debug.out(F("interfaceAddr=")); debug.out(WiFi.localIP().toString());
|
||||
debug.out(F(", multicast IP=")); debug.out(config.getString("ipMulticast"));
|
||||
debug.out(F(", Port=")); debug.outln(port);
|
||||
}
|
||||
|
||||
void ClockClient::interpretClockMessage(const char *_msg) {
|
||||
String msg = String(_msg);
|
||||
if (!msg.startsWith("fastclock\r\n")) {
|
||||
debug.out(F("ERROR: This is not a fastclock message! Got message=")); debug.outln(msg.substring(0,30));
|
||||
}
|
||||
msg = msg.substring(11);
|
||||
if (!msg.startsWith("version=2\r\n")) {
|
||||
debug.out(F("WARNING: Version of fastclock not supported! Got ")); debug.outln(msg.substring(0,10));
|
||||
}
|
||||
msg = msg.substring(msg.indexOf('\n')+1);
|
||||
|
||||
String checkName{""};
|
||||
if (msg.startsWith("name=")) {
|
||||
checkName = msg.substring(msg.indexOf('=')+1, msg.indexOf('\r'));
|
||||
fastclockScanner.addClock(checkName);
|
||||
msg = msg.substring(msg.indexOf('\n')+1);
|
||||
} else { debug.outln(F("ERROR: Clock Message Format invalid! Expected name field.")); return; }
|
||||
if (!checkName.equals(name)) {
|
||||
// this is another clock, we are not following this one
|
||||
debug.out(F("Ignoring clock with name="), DEBUG_MAX_INFO); debug.out(checkName.c_str(), DEBUG_MAX_INFO); debug.out(F("; looking for "), DEBUG_MAX_INFO); debug.outln(name.c_str(), DEBUG_MAX_INFO);
|
||||
return;
|
||||
}
|
||||
if (msg.startsWith("ip-address=")) {
|
||||
msg = msg.substring(msg.indexOf('\n')+1);
|
||||
} else { debug.outln(F("ERROR: Clock Message Format invalid! Expected ip-address field.")); return; }
|
||||
if (msg.startsWith("ip-port=")) {
|
||||
msg = msg.substring(msg.indexOf('\n')+1);
|
||||
} else { debug.outln(F("ERROR: Clock Message Format invalid! Expected ip-port field.")); return; }
|
||||
if (msg.startsWith("text=")) {
|
||||
text = msg.substring(msg.indexOf('=')+1, msg.indexOf('\r'));
|
||||
msg = msg.substring(msg.indexOf('\n')+1);
|
||||
} else { debug.outln(F("ERROR: Clock Message Format invalid! Expected text field.")); return; }
|
||||
if (msg.startsWith("clocktype=")) {
|
||||
clocktype = msg.substring(msg.indexOf('=')+1, msg.indexOf('\r'));
|
||||
msg = msg.substring(msg.indexOf('\n')+1);
|
||||
} else { debug.outln(F("ERROR: Clock Message Format invalid! Expected clocktype field.")); return; }
|
||||
if (msg.startsWith("active=")) {
|
||||
if (msg.startsWith("active=yes\r\n")) {
|
||||
active = true;
|
||||
} else {
|
||||
active = false;
|
||||
}
|
||||
msg = msg.substring(msg.indexOf('\n')+1);
|
||||
} else { debug.outln(F("ERROR: Clock Message Format invalid! Expected active field.")); return; }
|
||||
if (msg.startsWith("speed=")) {
|
||||
speed = msg.substring(msg.indexOf('=')+1, msg.indexOf('\r')).toFloat();
|
||||
msg = msg.substring(msg.indexOf('\n')+1);
|
||||
} else { debug.outln(F("ERROR: Clock Message Format invalid! Expected speed field.")); return; }
|
||||
if (msg.startsWith("clock=")) {
|
||||
clock = msg.substring(msg.indexOf('=')+1, msg.indexOf('\r'));
|
||||
int firstColonPos = clock.indexOf(':');
|
||||
int secondColonPos = clock.lastIndexOf(':');
|
||||
clockHours = clock.substring(0,firstColonPos).toInt();
|
||||
clockMinutes = clock.substring(firstColonPos+1, secondColonPos).toInt();
|
||||
clockSeconds = clock.substring(secondColonPos+1).toInt();
|
||||
msg = msg.substring(msg.indexOf('\n')+1);
|
||||
} else { debug.outln(F("ERROR: Clock Message Format invalid! Expected clock field.")); return; }
|
||||
if (msg.startsWith("weekday=")) {
|
||||
weekday = msg.substring(msg.indexOf('=')+1, msg.indexOf('\r'));
|
||||
msg = msg.substring(msg.indexOf('\n')+1);
|
||||
} else { debug.outln(F("ERROR: Clock Message Format invalid! Expected weekday field.")); return; }
|
||||
|
||||
for (int i=0; i<numClockChangeCallbacks; ++i) {
|
||||
clockChangeCallback[i](clockHours, clockMinutes, clockSeconds);
|
||||
}
|
||||
|
||||
// Debug::out(F("Clock Name=")); Debug::out(ClockClient::name.c_str()); Debug::out(F(", ")); Debug::outln(ClockClient::clock.c_str());
|
||||
}
|
||||
|
||||
void ClockClient::loop() {
|
||||
int length = 0;
|
||||
|
||||
length = udp.parsePacket();
|
||||
if (length > 0) {
|
||||
// debug.out(F("ClockClient received: ")); debug.out(length); debug.outln(F(" bytes."));
|
||||
udp.read(packetBuffer, CLOCK_PACKET_SIZE);
|
||||
packetBuffer[length] = '\0';
|
||||
interpretClockMessage((char *) packetBuffer);
|
||||
// debug.out(F("> ")); debug.outln( (char *) packetBuffer);
|
||||
}
|
||||
}
|
72
src/ClockClient.h
Normal file
72
src/ClockClient.h
Normal file
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// FILE: ClockClient.h
|
||||
// VERSION: 0.1
|
||||
// PURPOSE: FREMO Clock Client
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef _clockClientLoaded
|
||||
#define _clockClientLoaded
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "DjDebug.h"
|
||||
#include "DjFastclockScanner.h"
|
||||
#include "DjConfig.h"
|
||||
//#include <Ticker.h>
|
||||
#include <WiFiUdp.h>
|
||||
|
||||
#define MAX_CLOCK_CHANGE_CALLBACKS 5
|
||||
|
||||
typedef void (*ClockChangeCallback)(int h, int m, int s);
|
||||
|
||||
class ClockClient
|
||||
{
|
||||
public:
|
||||
ClockClient(Debug& _debug, Config& _config):debug(_debug), config(_config), fastclockScanner(_debug) {};
|
||||
void begin();
|
||||
void loop();
|
||||
static void setListenToClock(const char *_name) { name = String(_name); }
|
||||
static void setListenToClock(String _name) { name = String(_name); }
|
||||
static const char * const getLastMessage();
|
||||
static String const getText() { return text; }
|
||||
static String const getClock() { return clock; }
|
||||
static String const getName() { return name; }
|
||||
static boolean const isActive() { return active; }
|
||||
static float const getSpeed() { return speed; }
|
||||
static int const getClockHours() { return clockHours; }
|
||||
static int const getClockMinutes() { return clockMinutes; }
|
||||
static int const getClockSeconds() { return clockSeconds; }
|
||||
static void addClockChangeCallback(ClockChangeCallback callback);
|
||||
int getNumberOfKnownClocks() { return fastclockScanner.getNumberOfKnownClocks(); };
|
||||
String *getKnownClocks() { return fastclockScanner.getKnownClocks(); };
|
||||
int getListenPort();
|
||||
IPAddress getMulticastIP();
|
||||
static String const getClockString() {
|
||||
String output = String(clockHours) + ":" + String(clockMinutes) + ":" + String(clockSeconds);
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
Debug& debug;
|
||||
Config& config;
|
||||
FastclockScanner fastclockScanner;
|
||||
//Ticker clockTrigger;
|
||||
static int numClockChangeCallbacks;
|
||||
static ClockChangeCallback clockChangeCallback[MAX_CLOCK_CHANGE_CALLBACKS];
|
||||
static String name;
|
||||
static String text;
|
||||
static String clocktype;
|
||||
static boolean active;
|
||||
static float speed;
|
||||
static String clock;
|
||||
static int clockHours;
|
||||
static int clockMinutes;
|
||||
static int clockSeconds;
|
||||
static String weekday;
|
||||
void interpretClockMessage(const char *msg);
|
||||
void addClock(const char * clockName);
|
||||
void addClock(String clockName);
|
||||
};
|
||||
|
||||
#endif
|
40
src/ConfigTemp.h
Normal file
40
src/ConfigTemp.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#ifndef _config_h_included
|
||||
#define _config_h_included
|
||||
|
||||
#define MAX_CLOCK_NAME_LEN 16
|
||||
#define MAX_CLOCK_CHANNEL_STRING_LEN 3
|
||||
#define MAX_CLOCK_COLOR_LEN 16
|
||||
#define DEFAULT_CLOCK_NAME "fastclk"
|
||||
#define DEFAULT_CLOCK_CHANNEL_STRING "1"
|
||||
#define DEFAULT_CLOCK_CHANNEL 1
|
||||
#define DEFAULT_CLOCK_COLOR "green"
|
||||
#define DEFAULT_BRIGHTNESS 31
|
||||
//#define DEFAULT_COLOR SevenSegmentClock::Green
|
||||
#define DEFAULT_COLOR "Green"
|
||||
|
||||
class Config {
|
||||
public:
|
||||
Config() {
|
||||
strncpy(clockName, DEFAULT_CLOCK_NAME, MAX_CLOCK_NAME_LEN);
|
||||
strncpy(clockChannelString, DEFAULT_CLOCK_CHANNEL_STRING, MAX_CLOCK_CHANNEL_STRING_LEN);
|
||||
clockChannel = DEFAULT_CLOCK_CHANNEL;
|
||||
appMode = MODE_REALCLOCK;
|
||||
utcTimeOffsetMinutes = 120;
|
||||
brightness = DEFAULT_BRIGHTNESS;
|
||||
clockColorName = DEFAULT_COLOR;
|
||||
};
|
||||
enum AppMode { MODE_DEMO, MODE_REALCLOCK, MODE_FASTCLOCK };
|
||||
private:
|
||||
char clockName[MAX_CLOCK_NAME_LEN+1];
|
||||
char clockChannelString[MAX_CLOCK_CHANNEL_STRING_LEN+1];
|
||||
uint8_t clockChannel;
|
||||
AppMode appMode;
|
||||
int utcTimeOffsetMinutes;
|
||||
uint8_t brightness;
|
||||
String clockColorName;
|
||||
};
|
||||
|
||||
//SevenSegmentClock::Color clockColor = DEFAULT_COLOR;
|
||||
//uint8_t brightness = DEFAULT_BRIGHTNESS;
|
||||
|
||||
#endif
|
270
src/DjConfig.cpp
Normal file
270
src/DjConfig.cpp
Normal file
@@ -0,0 +1,270 @@
|
||||
//
|
||||
// FILE: Config.h
|
||||
// VERSION: 0.1
|
||||
// PURPOSE: Configuration of controller, wifi and application basics
|
||||
//
|
||||
//
|
||||
|
||||
#include "DjConfig.h"
|
||||
#include <FS.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#ifdef ESP8266
|
||||
extern "C" {
|
||||
#include "user_interface.h"
|
||||
}
|
||||
#endif
|
||||
|
||||
#define DEFAULT_CONFIG_JSON "{default:true}"
|
||||
struct ConfigItem {
|
||||
String section;
|
||||
String name;
|
||||
String type;
|
||||
String value;
|
||||
boolean changed;
|
||||
};
|
||||
static struct ConfigItem configItems[MAX_NUMBER_OF_CONFIG_ITEMS];
|
||||
static int numberOfConfigItems = 0;
|
||||
|
||||
|
||||
static StaticJsonDocument<2000> jsonDoc;
|
||||
|
||||
void Config::loadFile(const char *filename, const char * const sectionConfigItemDescriptions[], int numberOfSectionConfigs)
|
||||
{
|
||||
boolean fileExists = false;
|
||||
boolean fileConfigIsComplete = true;
|
||||
|
||||
static const char *defaultJsonString = "{\"empty\"=\"empty\"}";
|
||||
|
||||
logHeap();
|
||||
debug.out(F(">>> loadFile ")); debug.outln(filename);
|
||||
_filename = String(filename);
|
||||
if (!_filename.startsWith("/")) _filename = "/" + _filename;
|
||||
|
||||
boolean jsonLoaded = false;
|
||||
if (SPIFFS.exists(_filename)) {
|
||||
//file exists, reading and loading
|
||||
fileExists = true;
|
||||
debug.out(F("reading config file "),DEBUG_MIN_INFO); debug.outln(_filename);
|
||||
File configFile = SPIFFS.open(_filename, "r");
|
||||
if (configFile) {
|
||||
size_t size = configFile.size();
|
||||
configFile.seek(0, SeekSet);
|
||||
debug.out(F("opened config file "),DEBUG_MIN_INFO); debug.out(_filename,DEBUG_MIN_INFO); debug.out(F(" size=")); debug.outln(size);
|
||||
DeserializationError error = deserializeJson(jsonDoc, configFile);
|
||||
configFile.close();
|
||||
if (error) {
|
||||
debug.out(F("Failed to read file, using default configuration"), DEBUG_ERROR);
|
||||
deserializeJson(jsonDoc, defaultJsonString);
|
||||
} else {
|
||||
debug.out(F("readBytes result=")); debug.outln(size);
|
||||
jsonLoaded = true;
|
||||
}
|
||||
} else {
|
||||
debug.outln(F("ERROR: config file exists but cannot be opened."),DEBUG_ERROR);
|
||||
deserializeJson(jsonDoc, defaultJsonString);
|
||||
}
|
||||
} else {
|
||||
debug.outln(F("config file not found ..."),DEBUG_MED_INFO);
|
||||
deserializeJson(jsonDoc, defaultJsonString);
|
||||
} // end of if file exists
|
||||
// end of mounted file system (we want to have default config, if no FS available)
|
||||
|
||||
// start setting the config variables
|
||||
for (int i=0; i<numberOfSectionConfigs; ++i) {
|
||||
String configItemDescription = sectionConfigItemDescriptions[i];
|
||||
if (configItemDescription != NULL && configItemDescription.length() > 0) {
|
||||
configItems[numberOfConfigItems].section = filename;
|
||||
int firstColon = configItemDescription.indexOf(":");
|
||||
int secondColon = configItemDescription.indexOf(":", firstColon+1);
|
||||
configItems[numberOfConfigItems].name = configItemDescription.substring(0, firstColon);
|
||||
configItems[numberOfConfigItems].type = configItemDescription.substring(firstColon+1, secondColon);
|
||||
debug.out(F("Adding config item: [")); debug.out(configItems[numberOfConfigItems].section); debug.out(F("] "));
|
||||
debug.out(configItems[numberOfConfigItems].name);
|
||||
if (jsonLoaded && jsonDoc[configItems[numberOfConfigItems].name] != NULL) {
|
||||
if (configItems[numberOfConfigItems].type.equals("int")) {
|
||||
configItems[numberOfConfigItems].value = String((int) jsonDoc[configItems[numberOfConfigItems].name]);
|
||||
} else
|
||||
if (configItems[numberOfConfigItems].type.equals("boolean")) {
|
||||
configItems[numberOfConfigItems].value = String(jsonDoc[configItems[numberOfConfigItems].name] ? "true" : "false");
|
||||
} else
|
||||
if (configItems[numberOfConfigItems].type.equals("string")) {
|
||||
configItems[numberOfConfigItems].value = String((const char *) jsonDoc[configItems[numberOfConfigItems].name]);
|
||||
} else {
|
||||
debug.out(F("ERROR: Unknown type in config definition - "), DEBUG_ERROR);
|
||||
debug.outln(configItems[numberOfConfigItems].type, DEBUG_ERROR);
|
||||
}
|
||||
debug.out(F(" (from file): "));
|
||||
} else {
|
||||
// parameter does not exist in json config file, thus we add the default value
|
||||
fileConfigIsComplete = false;
|
||||
configItems[numberOfConfigItems].value = configItemDescription.substring(secondColon+1);
|
||||
debug.out(F(" (from default): "));
|
||||
}
|
||||
debug.out(configItems[numberOfConfigItems].value);
|
||||
debug.out(F(" (")); debug.out(configItems[numberOfConfigItems].type); debug.out(F(")"));
|
||||
configItems[numberOfConfigItems].changed = false;
|
||||
|
||||
numberOfConfigItems++;
|
||||
if (numberOfConfigItems >= MAX_NUMBER_OF_CONFIG_ITEMS) {
|
||||
debug.outln(F("ERROR: Too many configuration items!"),DEBUG_ERROR);
|
||||
break;
|
||||
} else {
|
||||
debug.out(F(" [")); debug.out(numberOfConfigItems); debug.out(F("] "));
|
||||
}
|
||||
logHeap();
|
||||
}
|
||||
} // end for ... numberOfSectionConfigs ...
|
||||
|
||||
logHeap();
|
||||
// Finally, if the configuration file does not exist so far or new entries have been added, then create/update it:
|
||||
if (!fileExists || !fileConfigIsComplete) {
|
||||
debug.outln(F("Writing config file due to "));
|
||||
if (!fileExists) debug.outln(F(" - file does not exist "));
|
||||
if (!fileConfigIsComplete) debug.outln(F(" - file config is not complete"));
|
||||
debug.outln(_filename);
|
||||
writeConfigFile(_filename);
|
||||
}
|
||||
}
|
||||
|
||||
void Config::writeAllConfigs(void) {
|
||||
logHeap();
|
||||
debug.out(F("Write all changed configs, checking ")); debug.out(numberOfConfigItems); debug.outln(F(" items."));
|
||||
for (int i=0; i<numberOfConfigItems; ++i) {
|
||||
debug.out("["); debug.out(i); debug.out("] ");
|
||||
debug.out(configItems[i].section);
|
||||
debug.out(".");
|
||||
debug.out(configItems[i].name);
|
||||
debug.out("(");
|
||||
debug.out(configItems[i].changed ? "changed" : "-");
|
||||
debug.out(")=");
|
||||
debug.outln(configItems[i].value);
|
||||
if (configItems[i].changed) {
|
||||
writeConfigFile(configItems[i].section);
|
||||
}
|
||||
}
|
||||
logHeap();
|
||||
}
|
||||
|
||||
void Config::writeConfigFile(String filename) {
|
||||
logHeap();
|
||||
if (!filename.startsWith("/")) {
|
||||
filename = "/" + filename;
|
||||
}
|
||||
File configFile = SPIFFS.open(filename, "w");
|
||||
|
||||
if (configFile) {
|
||||
StaticJsonDocument<2000> json;
|
||||
for (int i=0; i<numberOfConfigItems; ++i) {
|
||||
if (configItems[i].section.equals(filename.substring(1))) {
|
||||
debug.out("writeConfig "); debug.outln(configItems[i].section + "." + configItems[i].name + "=" + configItems[i].type + ":" + configItems[i].value);
|
||||
if (configItems[i].type.equals("int")) {
|
||||
json[configItems[i].name] = configItems[i].value.toInt();;
|
||||
} else if (configItems[i].type.equals("boolean")) {
|
||||
if (configItems[i].value.equals("true") || configItems[i].value.equals("TRUE") || configItems[i].value.toInt() != 0) {
|
||||
json[configItems[i].name] = true;
|
||||
} else {
|
||||
json[configItems[i].name] = false;
|
||||
}
|
||||
} else {
|
||||
json[configItems[i].name] = configItems[i].value;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (serializeJson(json, configFile) == 0) {
|
||||
debug.outln(F("Failed to write configuration to file"));
|
||||
}
|
||||
configFile.close();
|
||||
} else {
|
||||
debug.out(F("ERROR: Cannot write config file ")); debug.outln(filename);
|
||||
}
|
||||
}
|
||||
|
||||
void Config::begin(const char * filename, const char * const *configItemDescriptions, unsigned int numberOfConfigItems)
|
||||
{
|
||||
debug.out(F("Config::Config called with configuration details for "));
|
||||
debug.out(filename); debug.out(" having "); debug.out(numberOfConfigItems); debug.outln(" configuration items.");
|
||||
loadFile(filename, configItemDescriptions, numberOfConfigItems);
|
||||
}
|
||||
|
||||
static int _getConfigItemIndex(const String configSection, const String name)
|
||||
{
|
||||
for (unsigned int i=0; i<sizeof(configItems); ++i) {
|
||||
if (/*configItems[i].section.equals(configSection) &&*/ configItems[i].name.equals(name)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
String Config::getString(String key)
|
||||
{
|
||||
int index = _getConfigItemIndex(/*"system"*/ _filename, key);
|
||||
|
||||
//logHeap();
|
||||
// debug.out("Config.getString("); debug.out(key); debug.outln(")");
|
||||
if (index < 0) {
|
||||
debug.out(F("ERROR: cannot find config item ")); debug.outln(key);
|
||||
return String("");
|
||||
} else {
|
||||
// debug.outln(configItems[index].value);
|
||||
return String(configItems[index].value);
|
||||
}
|
||||
}
|
||||
|
||||
const char * Config::getCString(String key)
|
||||
{
|
||||
return getString(key).c_str();
|
||||
}
|
||||
|
||||
void Config::setString(String key, String value) {
|
||||
int index = _getConfigItemIndex(/*"system"*/ _filename, key);
|
||||
logHeap();
|
||||
// debug.out("Config.getString("); debug.out(key); debug.outln(")");
|
||||
if (index < 0) {
|
||||
debug.out(F("ERROR: Tried to set new value, but cannot find config item ")); debug.outln(key);
|
||||
} else {
|
||||
// debug.outln(configItems[index].value);
|
||||
if (!configItems[index].value.equals(value)) {
|
||||
configItems[index].value = String(value);
|
||||
configItems[index].changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int Config::getInt(String key)
|
||||
{
|
||||
return this->getString(key).toInt();
|
||||
}
|
||||
|
||||
void Config::setInt(String parameter, int value) {
|
||||
this->setString(parameter, String(value));
|
||||
}
|
||||
|
||||
boolean Config::getBoolean(String key)
|
||||
{
|
||||
String sval = getString(key);
|
||||
boolean bval = false;
|
||||
|
||||
if (sval.length() > 0) {
|
||||
if (sval.equals("true") || sval.equals("TRUE")) {
|
||||
bval = true;
|
||||
} else if (sval.equals("false") || sval.equals("FALSE")) {
|
||||
bval = false;
|
||||
} else {
|
||||
int ival = sval.toInt();
|
||||
if (ival) {
|
||||
bval = true;
|
||||
}
|
||||
}
|
||||
|
||||
return bval;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Config::setBoolean(String parameter, boolean value) {
|
||||
this->setString(parameter, value ? "true" : "false");
|
||||
}
|
35
src/DjConfig.h
Normal file
35
src/DjConfig.h
Normal file
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// FILE: Config.h
|
||||
// VERSION: 0.1
|
||||
// PURPOSE: Configuration of controller, wifi and application basics
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef lightningConfigLoaded
|
||||
#define lightningConfigLoaded true
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "DjDebug.h"
|
||||
|
||||
#define MAX_NUMBER_OF_CONFIG_ITEMS 50
|
||||
|
||||
class Config {
|
||||
public:
|
||||
Config(Debug& _debug):debug(_debug) { debug.outln("Config constructor", DEBUG_MAX_INFO); logHeap(); };
|
||||
void begin(const char * filename, const char * const *configItemDescriptions, unsigned int numberOfConfigItems);
|
||||
void loadFile(const char *filename, const char * const configDescriptions[], int numConfigs);
|
||||
int getInt(String parameter);
|
||||
String getString(String parameter);
|
||||
const char * getCString(String parameter);
|
||||
boolean getBoolean(String parameter);
|
||||
void setString(String parameter, String value);
|
||||
void setInt(String parameter, int value);
|
||||
void setBoolean(String parameter, boolean value);
|
||||
void writeAllConfigs(void) ;
|
||||
private:
|
||||
String _filename = "";
|
||||
Debug& debug;
|
||||
void writeConfigFile(String filename);
|
||||
};
|
||||
|
||||
#endif
|
176
src/DjDebug.cpp
Normal file
176
src/DjDebug.cpp
Normal file
@@ -0,0 +1,176 @@
|
||||
#include <Arduino.h>
|
||||
#include "DjDebug.h"
|
||||
|
||||
/*****************************************************************/
|
||||
/* Debug output */
|
||||
/*****************************************************************/
|
||||
|
||||
int Debug::outputUptoLevel = DEBUG_MAX_INFO;
|
||||
int Debug::defaultDebugLevel = DEBUG_MIN_INFO;
|
||||
|
||||
Debug::Debug() {
|
||||
Debug(115200, "Serial");
|
||||
}
|
||||
|
||||
Debug::Debug(int baudRate) {
|
||||
Debug(baudRate, "Serial");
|
||||
}
|
||||
|
||||
Debug::Debug(const char * outputChannel) {
|
||||
Debug(115200, outputChannel);
|
||||
}
|
||||
|
||||
Debug::Debug(int baudRate, const char * outputChannel)
|
||||
{
|
||||
Serial.begin(baudRate);
|
||||
Serial.println(F("\n"));
|
||||
Serial.println(F("=== Debug output starts"));
|
||||
Serial.print(F("Output to: "));
|
||||
Serial.print(outputChannel);
|
||||
Serial.print(", speed ");
|
||||
Serial.println(baudRate);
|
||||
logHeap();
|
||||
}
|
||||
|
||||
void Debug::setOutputUptoLevel(int level)
|
||||
{
|
||||
outputUptoLevel = level;
|
||||
}
|
||||
|
||||
void Debug::setDefaultDebugLevel(int level)
|
||||
{
|
||||
defaultDebugLevel = level;
|
||||
}
|
||||
|
||||
void Debug::heapLogger(const char *fileName, const int line, const char *functionName)
|
||||
{
|
||||
out(F("> Heap: "));
|
||||
out(ESP.getFreeHeap());
|
||||
out(F(" - "));
|
||||
// search filename beginning without path, look for slash character
|
||||
const char *slash = fileName + strlen(fileName);
|
||||
while (*slash != '/' && slash > fileName)
|
||||
--slash;
|
||||
out(slash);
|
||||
out(F(": "));
|
||||
outln(line);
|
||||
#if 0
|
||||
if (functionName) {
|
||||
out(F(" in "));
|
||||
outln(functionName);
|
||||
}
|
||||
#endif
|
||||
delay(10);
|
||||
}
|
||||
|
||||
void Debug::out(const String& text, const int level)
|
||||
{
|
||||
if (level <= outputUptoLevel) {
|
||||
Serial.print(text);
|
||||
}
|
||||
}
|
||||
|
||||
void Debug::out(int number)
|
||||
{
|
||||
if (defaultDebugLevel <= outputUptoLevel) {
|
||||
Serial.print(number);
|
||||
}
|
||||
}
|
||||
|
||||
void Debug::outln(int number)
|
||||
{
|
||||
if (defaultDebugLevel <= outputUptoLevel) {
|
||||
Serial.println(number);
|
||||
}
|
||||
}
|
||||
|
||||
void Debug::out(int number, int level)
|
||||
{
|
||||
if (level <= outputUptoLevel) {
|
||||
Serial.print(number);
|
||||
}
|
||||
}
|
||||
|
||||
void Debug::outln(int number, int level)
|
||||
{
|
||||
if (level <= outputUptoLevel) {
|
||||
Serial.println(number);
|
||||
}
|
||||
}
|
||||
|
||||
void Debug::out(const char * text)
|
||||
{
|
||||
if (defaultDebugLevel <= outputUptoLevel) {
|
||||
Serial.print(text);
|
||||
}
|
||||
}
|
||||
|
||||
void Debug::out_p(PGM_P text)
|
||||
{
|
||||
if (defaultDebugLevel <= outputUptoLevel) {
|
||||
Serial.print(text);
|
||||
}
|
||||
}
|
||||
|
||||
void Debug::out(const char * text, const int level)
|
||||
{
|
||||
if (level <= outputUptoLevel) {
|
||||
Serial.print(text);
|
||||
}
|
||||
}
|
||||
|
||||
void Debug::out_p(PGM_P text, const int level)
|
||||
{
|
||||
if (level <= outputUptoLevel) {
|
||||
Serial.print(text);
|
||||
}
|
||||
}
|
||||
|
||||
void Debug::out(const String& text)
|
||||
{
|
||||
if (defaultDebugLevel <= outputUptoLevel) {
|
||||
Serial.print(text);
|
||||
}
|
||||
}
|
||||
|
||||
void Debug::outln(const String& text, const int level)
|
||||
{
|
||||
if (level <= outputUptoLevel) {
|
||||
Serial.println(text);
|
||||
}
|
||||
}
|
||||
|
||||
void Debug::outln(const String& text)
|
||||
{
|
||||
if (defaultDebugLevel <= outputUptoLevel) {
|
||||
Serial.println(text);
|
||||
}
|
||||
}
|
||||
|
||||
void Debug::outln(const char * text, const int level)
|
||||
{
|
||||
if (level <= outputUptoLevel) {
|
||||
Serial.println(text);
|
||||
}
|
||||
}
|
||||
|
||||
void Debug::outln_p(PGM_P text, const int level)
|
||||
{
|
||||
if (level <= outputUptoLevel) {
|
||||
Serial.println(text);
|
||||
}
|
||||
}
|
||||
|
||||
void Debug::outln(const char * text)
|
||||
{
|
||||
if (defaultDebugLevel <= outputUptoLevel) {
|
||||
Serial.println(text);
|
||||
}
|
||||
}
|
||||
|
||||
void Debug::outln_p(PGM_P text)
|
||||
{
|
||||
if (defaultDebugLevel <= outputUptoLevel) {
|
||||
Serial.println(text);
|
||||
}
|
||||
}
|
52
src/DjDebug.h
Normal file
52
src/DjDebug.h
Normal file
@@ -0,0 +1,52 @@
|
||||
|
||||
#ifndef debug_h
|
||||
#define debug_h
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <pgmspace.h>
|
||||
|
||||
#define logHeap() Debug::heapLogger(__FILE__,__LINE__,__func__)
|
||||
|
||||
class Debug {
|
||||
public:
|
||||
Debug();
|
||||
Debug(int baudRate);
|
||||
Debug(const char * outputChannel);
|
||||
Debug(int baudRate, const char * outputChannel);
|
||||
static void heapLogger(const char *fileName, const int line, const char *functionName);
|
||||
static void out(int number);
|
||||
static void outln(int number);
|
||||
static void out(int number, int level);
|
||||
static void outln(int number, int level);
|
||||
static void out(const String& text, const int level);
|
||||
static void out(const String& text);
|
||||
static void outln(const String& text, const int level);
|
||||
static void outln(const String& text);
|
||||
static void out(const char * text, const int level);
|
||||
static void out(const char * text);
|
||||
static void outln(const char * text, const int level);
|
||||
static void outln(const char * text);
|
||||
static void out_p(PGM_P text, const int level);
|
||||
static void out_p(PGM_P text);
|
||||
static void outln_p(PGM_P text, const int level);
|
||||
static void outln_p(PGM_P text);
|
||||
static void setOutputUptoLevel(int level);
|
||||
static void setDefaultDebugLevel(int level);
|
||||
|
||||
private:
|
||||
static int outputUptoLevel;
|
||||
static int defaultDebugLevel;
|
||||
};
|
||||
|
||||
// Wieviele Informationen sollen über die serielle Schnittstelle ausgegeben werden?
|
||||
#define DEBUG 3
|
||||
|
||||
// Definition der Debuglevel
|
||||
#define DEBUG_ERROR 1
|
||||
#define DEBUG_WARNING 2
|
||||
#define DEBUG_MIN_INFO 3
|
||||
#define DEBUG_MED_INFO 4
|
||||
#define DEBUG_MAX_INFO 5
|
||||
|
||||
#endif
|
||||
|
31
src/DjFastclockScanner.cpp
Normal file
31
src/DjFastclockScanner.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// FILE: DjFastclockScanner.h
|
||||
// VERSION: 1.0
|
||||
// PURPOSE: Scans the broadcasts for clocks and returns the clocknames found
|
||||
//
|
||||
//
|
||||
|
||||
#include <DjFastclockScanner.h>
|
||||
|
||||
|
||||
String FastclockScanner::knownClocks[MAX_CLOCKS];
|
||||
int FastclockScanner::numberOfKnownClocks = 0;
|
||||
|
||||
void FastclockScanner::addClock(String clockName) {
|
||||
for (int i=0; i<numberOfKnownClocks; ++i) {
|
||||
if (clockName.equals(knownClocks[i])) return;
|
||||
}
|
||||
if (numberOfKnownClocks < MAX_CLOCKS) {
|
||||
knownClocks[numberOfKnownClocks] = String(clockName);
|
||||
++numberOfKnownClocks;
|
||||
debug.out(F("Added new clock with name=")); debug.outln(clockName);
|
||||
}
|
||||
logHeap();
|
||||
}
|
||||
|
||||
void FastclockScanner::addClock(const char * clockName) {
|
||||
addClock(String(clockName));
|
||||
}
|
||||
|
||||
|
||||
|
25
src/DjFastclockScanner.h
Normal file
25
src/DjFastclockScanner.h
Normal file
@@ -0,0 +1,25 @@
|
||||
/* DjFastclockScanner -- collect fastclock server names
|
||||
*/
|
||||
|
||||
#ifndef _DjFastclockScannerIncluded
|
||||
#define _DjFastclockScannerIncluded true
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "DjDebug.h"
|
||||
|
||||
#define MAX_CLOCKS 10
|
||||
|
||||
class FastclockScanner {
|
||||
public:
|
||||
FastclockScanner(Debug& _debug):debug(_debug) {};
|
||||
void addClock(String clockName);
|
||||
void addClock(const char * clockName);
|
||||
String *getKnownClocks() { return knownClocks; }
|
||||
int getNumberOfKnownClocks() { return numberOfKnownClocks; }
|
||||
private:
|
||||
Debug& debug;
|
||||
static String knownClocks[];
|
||||
static int numberOfKnownClocks;
|
||||
};
|
||||
|
||||
#endif
|
78
src/DjSimpleFS.cpp
Normal file
78
src/DjSimpleFS.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// FILE: DjSimpleFS.cpp
|
||||
// VERSION: 1.0
|
||||
// PURPOSE: File system
|
||||
//
|
||||
//
|
||||
|
||||
#include "DjSimpleFS.h"
|
||||
|
||||
boolean SimpleFS::_initialized = false;
|
||||
FSInfo SimpleFS::fsInfo;
|
||||
|
||||
//format bytes
|
||||
String SimpleFS::formatBytes(size_t bytes) {
|
||||
if (bytes < 1024){
|
||||
return String(bytes)+"B";
|
||||
} else if(bytes < (1024 * 1024)){
|
||||
return String(bytes/1024.0)+"KB";
|
||||
} else if(bytes < (1024 * 1024 * 1024)){
|
||||
return String(bytes/1024.0/1024.0)+"MB";
|
||||
} else {
|
||||
return String(bytes/1024.0/1024.0/1024.0)+"GB";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SimpleFS::print_filesystem_info()
|
||||
{
|
||||
debug.outln(F("---------------"));
|
||||
debug.outln(F("Filesystem Info"));
|
||||
debug.outln(F("---------------"));
|
||||
debug.out(F("Filesystem capacity: ")); debug.outln(formatBytes(fsInfo.totalBytes));
|
||||
debug.out(F("Used capacity: ")); debug.outln(formatBytes(fsInfo.usedBytes));
|
||||
debug.out(F("maxOpenFiles: ")); debug.outln(fsInfo.maxOpenFiles);
|
||||
debug.outln(F("File system directory:"));
|
||||
debug.outln(F("----------------------"));
|
||||
Dir dir = SPIFFS.openDir("/");
|
||||
while (dir.next()) {
|
||||
String fileName = dir.fileName();
|
||||
size_t fileSize = dir.fileSize();
|
||||
debug.out(F("FS File: ")); debug.out(fileName.c_str());
|
||||
debug.out(F(", size: ")); debug.outln(formatBytes(fileSize).c_str());
|
||||
}
|
||||
debug.outln(F("__________________"));
|
||||
delay(10);
|
||||
logHeap();
|
||||
}
|
||||
|
||||
void SimpleFS::begin() {
|
||||
if (_initialized) return;
|
||||
|
||||
if (SPIFFS.begin() == false) {
|
||||
debug.outln(F("Formatting file system ..."));
|
||||
SPIFFS.format();
|
||||
}
|
||||
SPIFFS.info(fsInfo);
|
||||
print_filesystem_info();
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
static size_t minFreeSpace = 1024 * 1024 * 1024;
|
||||
|
||||
void SimpleFS::loop() {
|
||||
SPIFFS.info(fsInfo);
|
||||
size_t freeSpace = fsInfo.totalBytes - fsInfo.usedBytes;
|
||||
|
||||
if (freeSpace < minFreeSpace) {
|
||||
minFreeSpace = freeSpace;
|
||||
debug.out(F("New minFreeSpace=")); debug.outln(formatBytes(minFreeSpace));
|
||||
}
|
||||
}
|
||||
|
||||
size_t SimpleFS::getMaxPathLength() {
|
||||
if (!_initialized) {
|
||||
return -1; // ERROR case
|
||||
}
|
||||
return fsInfo.maxPathLength;
|
||||
}
|
31
src/DjSimpleFS.h
Normal file
31
src/DjSimpleFS.h
Normal file
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// FILE: DjSimpleFS.h
|
||||
// VERSION: 1.0
|
||||
// PURPOSE: File system
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef _djSimpleFSLoaded
|
||||
#define _djSimpleFSLoaded true
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <FS.h>
|
||||
#include "DjDebug.h"
|
||||
|
||||
class SimpleFS {
|
||||
public:
|
||||
SimpleFS(Debug& _debug):debug(_debug) { logHeap(); };
|
||||
void begin();
|
||||
void loop();
|
||||
boolean initialized() { return _initialized; };
|
||||
static size_t getMaxPathLength();
|
||||
void print_filesystem_info();
|
||||
|
||||
private:
|
||||
Debug& debug;
|
||||
String formatBytes(size_t bytes);
|
||||
static boolean _initialized;
|
||||
static FSInfo fsInfo;
|
||||
};
|
||||
|
||||
#endif
|
@@ -2,7 +2,21 @@
|
||||
|
||||
static const uint16_t PixelCount = 4*7*3+3;
|
||||
|
||||
#define colorSaturation 63
|
||||
#define colorSaturation 31
|
||||
|
||||
SevenSegmentClock::ColorSelection
|
||||
SevenSegmentClock::colorSelection[] = {
|
||||
{ Black, "Black", 0, 0, 0 },
|
||||
{ Blue, "Blue", 0, 0, 255 },
|
||||
{ Red, "Red", 255, 0, 0 },
|
||||
{ Green, "Green", 0, 255, 0 },
|
||||
{ White, "White", 255, 255, 255 },
|
||||
{ Yellow, "Yellow", 255, 255, 0 },
|
||||
{ Magenta, "Magenta", 255, 0, 255 },
|
||||
{ Magenta, "Cyan", 0, 255, 255 }
|
||||
};
|
||||
|
||||
int SevenSegmentClock::numberOfSupportedColors = sizeof(SevenSegmentClock::colorSelection) / sizeof(SevenSegmentClock::colorSelection[0]);
|
||||
|
||||
// Seven Segment Layout: 3 LEDs per segment
|
||||
// order of segments:
|
||||
@@ -34,11 +48,11 @@ static const uint8_t digitOffset[] = { 0, LedsPerDigit, 2*LedsPerDigit+Seperator
|
||||
#define Seg_f 0x20
|
||||
#define Seg_g 0x40
|
||||
|
||||
#define decimalPointLed (2*LedsPerDigit)
|
||||
#define clockSeperatorLed1 (2*LedsPerDigit+1)
|
||||
#define clockSeperatorLed2 (2*LedsPerDigit+2)
|
||||
#define clockSeperatorLed1 (2*LedsPerDigit)
|
||||
#define clockSeperatorLed2 (2*LedsPerDigit+1)
|
||||
#define decimalPointLed (2*LedsPerDigit+2)
|
||||
|
||||
#define firstCharacterMapped 32u /* first char to be mapped is "space" */
|
||||
#define firstCharacterMapped 0x20 /* first char to be mapped is "space" */
|
||||
#define lastCharacterMapped (sizeof(charMapping) + firstCharacterMapped)
|
||||
|
||||
static const unsigned char PROGMEM charMapping[] = {
|
||||
@@ -54,6 +68,7 @@ static const unsigned char PROGMEM charMapping[] = {
|
||||
/* ) */ Seg_b + Seg_c + Seg_f + Seg_g,
|
||||
/* * */ 0,
|
||||
/* + */ 0,
|
||||
/* , */ 0,
|
||||
/* - */ Seg_d,
|
||||
/* . */ 0,
|
||||
/* / */ Seg_e,
|
||||
@@ -63,7 +78,7 @@ static const unsigned char PROGMEM charMapping[] = {
|
||||
/* 3 */ Seg_b + Seg_c + Seg_d + Seg_f + Seg_g,
|
||||
/* 4 */ Seg_a + Seg_c + Seg_d + Seg_g,
|
||||
/* 5 */ Seg_a + Seg_b + Seg_d + Seg_f + Seg_g,
|
||||
/* 6 */ Seg_a + Seg_d + Seg_e + Seg_f + Seg_g,
|
||||
/* 6 */ Seg_a + Seg_b + Seg_d + Seg_e + Seg_f + Seg_g,
|
||||
/* 7 */ Seg_b + Seg_c + Seg_g,
|
||||
/* 8 */ Seg_a + Seg_b + Seg_c + Seg_d + Seg_e + Seg_f + Seg_g,
|
||||
/* 9 */ Seg_a + Seg_b + Seg_c + Seg_d + Seg_g,
|
||||
@@ -111,6 +126,8 @@ static const unsigned char PROGMEM charMapping[] = {
|
||||
/* || */ Seg_a + Seg_c + Seg_e + Seg_g
|
||||
};
|
||||
|
||||
uint8_t SevenSegmentClock::brightness;
|
||||
|
||||
void SevenSegmentClock::displaySegment(unsigned int ledAddress, uint32_t color) {
|
||||
//Serial.print("displaySegment led="); Serial.print(ledAddress); Serial.print(" color=0x"); Serial.println(color, HEX);
|
||||
for (int i=0; i<LedsPerSegment; i++) {
|
||||
@@ -158,19 +175,28 @@ void SevenSegmentClock::displayDigit(unsigned int digitNum, char charToDisplay)
|
||||
}
|
||||
|
||||
void SevenSegmentClock::displaySeperator(char seperatorCharacter) {
|
||||
displaySeperator(seperatorCharacter, currentColor);
|
||||
}
|
||||
|
||||
void SevenSegmentClock::displaySeperator(char seperatorCharacter, uint32_t color) {
|
||||
//Serial.print("displaySeperator: seperator="); Serial.println(seperatorCharacter);
|
||||
switch (seperatorCharacter) {
|
||||
case '.':
|
||||
case ',':
|
||||
strip->setPixelColor(decimalPointLed, currentColor);
|
||||
strip->setPixelColor(decimalPointLed, color);
|
||||
strip->setPixelColor(clockSeperatorLed1, black);
|
||||
strip->setPixelColor(clockSeperatorLed2, black);
|
||||
break;
|
||||
case ':':
|
||||
strip->setPixelColor(decimalPointLed, black);
|
||||
strip->setPixelColor(clockSeperatorLed1, currentColor);
|
||||
strip->setPixelColor(clockSeperatorLed2, currentColor);
|
||||
strip->setPixelColor(clockSeperatorLed1, color);
|
||||
strip->setPixelColor(clockSeperatorLed2, color);
|
||||
break;
|
||||
case '|':
|
||||
strip->setPixelColor(decimalPointLed, color);
|
||||
strip->setPixelColor(clockSeperatorLed1, color);
|
||||
strip->setPixelColor(clockSeperatorLed2, color);
|
||||
break;
|
||||
default:
|
||||
Serial.print("SevenSegmentClock::displaySeperator: Unknown character to be displayed: ");
|
||||
Serial.println(seperatorCharacter);
|
||||
@@ -183,15 +209,27 @@ void SevenSegmentClock::displaySeperator(char seperatorCharacter) {
|
||||
}
|
||||
}
|
||||
|
||||
void SevenSegmentClock::displaySeperator(uint32_t color) {
|
||||
strip->setPixelColor(clockSeperatorLed1, color);
|
||||
strip->setPixelColor(clockSeperatorLed2, color);
|
||||
}
|
||||
|
||||
void SevenSegmentClock::displayDecimalPoint(uint32_t color) {
|
||||
strip->setPixelColor(decimalPointLed, color);
|
||||
}
|
||||
|
||||
void SevenSegmentClock::displayTime(int hour, int minute) {
|
||||
clockHour = hour;
|
||||
clockMinute = minute;
|
||||
Serial.print("SevenSegmentClock: new time ");
|
||||
Serial.print(clockHour); Serial.print(":"); Serial.println(clockMinute);
|
||||
if (clockHour != hour || clockMinute != minute) {
|
||||
clockHour = hour;
|
||||
clockMinute = minute;
|
||||
Serial.print("SevenSegmentClock: new time ");
|
||||
Serial.print(clockHour); Serial.print(":"); Serial.println(clockMinute);
|
||||
}
|
||||
displayUpdate();
|
||||
};
|
||||
|
||||
SevenSegmentClock::BlinkMode SevenSegmentClock::blinkMode;
|
||||
|
||||
void SevenSegmentClock::displayUpdate(void) {
|
||||
char displayText[4];
|
||||
static int lastHour=0, lastMinute=0;
|
||||
@@ -202,17 +240,19 @@ void SevenSegmentClock::displayUpdate(void) {
|
||||
if (clockHour != lastHour || clockMinute != lastMinute || millis()-lastUpdate_ms > TIME_BETWEEN_DISPLAY_UPDATES_ms) {
|
||||
lastHour = clockHour;
|
||||
lastMinute = clockMinute;
|
||||
displayText[0] = (hour > 9) ? '0' + (hour/10) : ' ';
|
||||
displayText[1] = '0' + hour % 10;
|
||||
displayText[2] = '0' + minute / 10;
|
||||
displayText[3] = '0' + minute % 10;
|
||||
displayText[0] = (clockHour > 9) ? '0' + (clockHour/10) : ' ';
|
||||
displayText[1] = '0' + clockHour % 10;
|
||||
displayText[2] = '0' + clockMinute / 10;
|
||||
displayText[3] = '0' + clockMinute % 10;
|
||||
switch (blinkMode) {
|
||||
case NoBlinking:
|
||||
displayDigit(0, displayText[0]);
|
||||
displayDigit(1, displayText[1]);
|
||||
displayDigit(2, displayText[2]);
|
||||
displayDigit(3, displayText[3]);
|
||||
displaySeperator(':');
|
||||
//displaySeperator(':');
|
||||
displaySeperator(currentColor);
|
||||
displayDecimalPoint(black);
|
||||
break;
|
||||
case ClockBlinking:
|
||||
if (currentlyBlinkOn) {
|
||||
@@ -220,17 +260,15 @@ void SevenSegmentClock::displayUpdate(void) {
|
||||
displayDigit(1, displayText[1]);
|
||||
displayDigit(2, displayText[2]);
|
||||
displayDigit(3, displayText[3]);
|
||||
displaySeperator(':');
|
||||
//displaySeperator(':');
|
||||
displaySeperator(currentColor);
|
||||
} else {
|
||||
displayDigit(0, ' ');
|
||||
displayDigit(1, ' ');
|
||||
displayDigit(2, ' ');
|
||||
displayDigit(3, ' ');
|
||||
displaySeperator(' ');
|
||||
}
|
||||
if (millis() > nextBlinkSwitch_ms) {
|
||||
currentlyBlinkOn = !currentlyBlinkOn;
|
||||
nextBlinkSwitch_ms = millis() + (currentlyBlinkOn ? BLINK_ON_TIME_ms : BLINK_OFF_TIME_ms);
|
||||
//displaySeperator(' ');
|
||||
displaySeperator(black);
|
||||
}
|
||||
break;
|
||||
case SeperatorBlinking:
|
||||
@@ -239,13 +277,13 @@ void SevenSegmentClock::displayUpdate(void) {
|
||||
displayDigit(2, displayText[2]);
|
||||
displayDigit(3, displayText[3]);
|
||||
if (currentlyBlinkOn) {
|
||||
displaySeperator(':');
|
||||
//displaySeperator('|');
|
||||
displaySeperator(currentColor);
|
||||
displayDecimalPoint(currentColor);
|
||||
} else {
|
||||
displaySeperator(' ');
|
||||
}
|
||||
if (millis() > nextBlinkSwitch_ms) {
|
||||
currentlyBlinkOn = !currentlyBlinkOn;
|
||||
nextBlinkSwitch_ms = millis() + (currentlyBlinkOn ? BLINK_ON_TIME_ms : BLINK_OFF_TIME_ms);
|
||||
//displaySeperator(' ');
|
||||
displaySeperator(black);
|
||||
displayDecimalPoint(black);
|
||||
}
|
||||
break;
|
||||
case DecimalPointBlinking:
|
||||
@@ -254,47 +292,113 @@ void SevenSegmentClock::displayUpdate(void) {
|
||||
displayDigit(2, displayText[2]);
|
||||
displayDigit(3, displayText[3]);
|
||||
if (currentlyBlinkOn) {
|
||||
displaySeperator('.');
|
||||
//displaySeperator('.');
|
||||
displayDecimalPoint(currentColor);
|
||||
} else {
|
||||
displaySeperator(' ');
|
||||
//displaySeperator(' ');
|
||||
displayDecimalPoint(black);
|
||||
}
|
||||
if (millis() > nextBlinkSwitch_ms) {
|
||||
currentlyBlinkOn = !currentlyBlinkOn;
|
||||
nextBlinkSwitch_ms = millis() + (currentlyBlinkOn ? BLINK_ON_TIME_ms : BLINK_OFF_TIME_ms);
|
||||
break;
|
||||
case DecimalPointColoredBlinking:
|
||||
displayDigit(0, displayText[0]);
|
||||
displayDigit(1, displayText[1]);
|
||||
displayDigit(2, displayText[2]);
|
||||
displayDigit(3, displayText[3]);
|
||||
if (currentlyBlinkOn) {
|
||||
//displaySeperator('.', currentColor);
|
||||
displayDecimalPoint(currentColor);
|
||||
} else {
|
||||
//displaySeperator('.', blinkColor);
|
||||
displayDecimalPoint(blinkColor);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (millis() > nextBlinkSwitch_ms) {
|
||||
currentlyBlinkOn = !currentlyBlinkOn;
|
||||
nextBlinkSwitch_ms = millis() + (currentlyBlinkOn ? BLINK_ON_TIME_ms : BLINK_OFF_TIME_ms);
|
||||
}
|
||||
strip->show();
|
||||
Serial.print("Shown: "); Serial.print(displayText[0]); Serial.print(displayText[1]);
|
||||
Serial.print(':'); Serial.print(displayText[2]); Serial.println(displayText[3]);
|
||||
//Serial.print("Shown: "); Serial.print(displayText[0]); Serial.print(displayText[1]);
|
||||
//Serial.print(':'); Serial.print(displayText[2]); Serial.println(displayText[3]);
|
||||
lastUpdate_ms = millis();
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t SevenSegmentClock::red, SevenSegmentClock::green, SevenSegmentClock::blue, SevenSegmentClock::white, SevenSegmentClock::black;
|
||||
uint8_t SevenSegmentClock::LedDataPin;
|
||||
Adafruit_NeoPixel *SevenSegmentClock::strip;
|
||||
|
||||
void SevenSegmentClock::setColor(Color color) {
|
||||
currentColorHandle = color;
|
||||
currentColor = getColorByHandle(color);
|
||||
}
|
||||
|
||||
void SevenSegmentClock::setBlinkColor(Color color) {
|
||||
blinkColorHandle=color;
|
||||
blinkColor=getColorByHandle(color);
|
||||
debug.out(F("setBlinkColor to ")); debug.outln(getColorName(color));
|
||||
}
|
||||
|
||||
String SevenSegmentClock::getColorName(Color handle) {
|
||||
for (int i=0; i<numberOfSupportedColors; ++i) {
|
||||
if (colorSelection[i].handle == handle)
|
||||
return colorSelection[i].colorName;
|
||||
}
|
||||
debug.outln(F("ERROR: Unknown color / handle not known"), DEBUG_ERROR);
|
||||
return String(F("ERROR: Unknown color handle"));
|
||||
}
|
||||
|
||||
SevenSegmentClock::Color SevenSegmentClock::getColorHandle(int index) {
|
||||
return colorSelection[index].handle;
|
||||
}
|
||||
|
||||
uint32_t SevenSegmentClock::getAdjustedStripColor(uint8_t red, uint8_t green, uint8_t blue) {
|
||||
return strip->Color((red * brightness) / 255, (green * brightness) / 255, (blue * brightness) / 255);
|
||||
}
|
||||
|
||||
uint32 SevenSegmentClock::getColorByName(String name) {
|
||||
for (int i=0; i<numberOfSupportedColors; ++i) {
|
||||
if (colorSelection[i].colorName.equals(name)) {
|
||||
return getAdjustedStripColor(colorSelection[i].red, colorSelection[i].green, colorSelection[i].blue);
|
||||
}
|
||||
}
|
||||
debug.out(F("ERROR: Unknown color name "), DEBUG_ERROR);
|
||||
debug.outln(name, DEBUG_ERROR);
|
||||
return 0xffffffff;
|
||||
}
|
||||
|
||||
uint32 SevenSegmentClock::getColorByHandle(Color handle) {
|
||||
for (int i=0; i<numberOfSupportedColors; ++i) {
|
||||
if (colorSelection[i].handle == handle) {
|
||||
return getAdjustedStripColor(colorSelection[i].red, colorSelection[i].green, colorSelection[i].blue);
|
||||
}
|
||||
}
|
||||
debug.outln(F("ERROR: Unknown color handle"), DEBUG_ERROR);
|
||||
debug.out(F("Currently I know about ")); debug.out(numberOfSupportedColors); debug.outln(F(" colors."));
|
||||
return 0xffffffff;
|
||||
}
|
||||
|
||||
SevenSegmentClock::Color SevenSegmentClock::getColorHandleByName(String name) {
|
||||
for (int i=0; i<numberOfSupportedColors; ++i) {
|
||||
if (colorSelection[i].colorName.equals(name)) {
|
||||
return colorSelection[i].handle;
|
||||
}
|
||||
}
|
||||
debug.out(F("ERROR: Unknown color name "), DEBUG_ERROR);
|
||||
debug.outln(name, DEBUG_ERROR);
|
||||
return Green; // default
|
||||
}
|
||||
|
||||
void SevenSegmentClock::begin(void) {
|
||||
Serial.println("Init Neopixels ...");
|
||||
Serial.print("LED pin="); Serial.println(LedDataPin);
|
||||
Serial.print("Pixels="); Serial.println(PixelCount);
|
||||
SevenSegmentClock::strip = new Adafruit_NeoPixel(PixelCount, LedDataPin, NEO_GRB + NEO_KHZ800);
|
||||
strip->begin();
|
||||
SevenSegmentClock::red = strip->Color(colorSaturation, 0, 0);
|
||||
SevenSegmentClock::green = strip->Color(0, colorSaturation, 0);
|
||||
SevenSegmentClock::blue = strip->Color(0, 0, colorSaturation);
|
||||
SevenSegmentClock::white = strip->Color(colorSaturation, colorSaturation, colorSaturation);
|
||||
SevenSegmentClock::black = strip->Color(0, 0, 0);
|
||||
SevenSegmentClock::currentColor = SevenSegmentClock::white;
|
||||
// strip->show();
|
||||
// boot animation
|
||||
uint32_t colors[] = { red, green, blue, white };
|
||||
unsigned int colorIndex = 0;
|
||||
for (int i=0; i<PixelCount; ++i) {
|
||||
strip->setPixelColor(i, colors[colorIndex++]);
|
||||
if (colorIndex > sizeof(colors)) colorIndex = 0;
|
||||
}
|
||||
setClockHalted(true);
|
||||
setBrightness(20);
|
||||
setColor(Green);
|
||||
setBlinkColor(Red);
|
||||
black = strip->Color(0, 0, 0);
|
||||
strip->clear();
|
||||
strip->show();
|
||||
delay(2000);
|
||||
}
|
||||
|
@@ -2,37 +2,65 @@
|
||||
#define sevenSegmentClock_h_included
|
||||
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
#include "DjDebug.h"
|
||||
#include "DjConfig.h"
|
||||
|
||||
// avoid flickering of the display:
|
||||
#define TIME_BETWEEN_DISPLAY_UPDATES_ms 300
|
||||
#define BLINK_OFF_TIME_ms 600
|
||||
#define TIME_BETWEEN_DISPLAY_UPDATES_ms 100
|
||||
#define BLINK_OFF_TIME_ms 400
|
||||
#define BLINK_ON_TIME_ms 400
|
||||
#define defaultLedDataPin 2
|
||||
class SevenSegmentClock {
|
||||
public:
|
||||
SevenSegmentClock() { LedDataPin=defaultLedDataPin; init(); };
|
||||
SevenSegmentClock(uint8_t dataPin) { LedDataPin=dataPin; init(); };
|
||||
SevenSegmentClock(Debug& _debug, Config& _config):debug(_debug), config(_config) { LedDataPin=defaultLedDataPin; };
|
||||
SevenSegmentClock(Debug& _debug, Config& _config, uint8_t dataPin):debug(_debug), config(_config) { LedDataPin=dataPin; };
|
||||
void begin(void);
|
||||
void displayTime(int hour, int minute);
|
||||
void displayUpdate(void);
|
||||
//void setClockSpeed(int _msPerModelSecond) { msPerModelSecond = _msPerModelSecond; setClockSpeed("x"); };
|
||||
enum BlinkMode { NoBlinking, ClockBlinking, SeperatorBlinking, DecimalPointBlinking };
|
||||
enum BlinkMode { NoBlinking, ClockBlinking, SeperatorBlinking, DecimalPointBlinking, DecimalPointColoredBlinking };
|
||||
void setBlinkMode(BlinkMode _blinkMode) { blinkMode = _blinkMode; };
|
||||
void setClockHalted(bool halted) { clockHalted = halted; };
|
||||
static uint32_t red, green, blue, white, black;
|
||||
enum ClockDisplayStatus { Off, Booting, Halted, StandardClock, FastClock };
|
||||
enum Color { Black, Red, Green, Blue, White, Yellow, Magenta, Cyan };
|
||||
void setColor(Color color);
|
||||
void setBlinkColor(Color color);
|
||||
Color getColor(void) { return currentColorHandle; };
|
||||
void displayDigit(unsigned int digitNum, char c);
|
||||
void displaySeperator(char seperatorCharacter);
|
||||
void displaySeperator(char seperatorCharacter, uint32_t color);
|
||||
void displaySeperator(uint32_t color);
|
||||
void displayDecimalPoint(uint32_t color);
|
||||
void setBrightness(uint8_t b) { brightness=b; };
|
||||
uint8_t getBrightness(void) { return brightness; };
|
||||
int getNumberSupportedColors(void) { return numberOfSupportedColors; };
|
||||
String getColorName(int index) { return String(colorSelection[index].colorName); };
|
||||
String getColorName(Color handle);
|
||||
Color getColorHandle(int index);
|
||||
uint32_t getAdjustedStripColor(uint8_t red, uint8_t green, uint8_t blue);
|
||||
uint32 getColorByName(String name);
|
||||
uint32 getColorByHandle(Color handle);
|
||||
Color getColorHandleByName(String name);
|
||||
private:
|
||||
void init(void) { displayStatus = Off; clockHour=12; clockMinute=34; setClockHalted(true); };
|
||||
Debug& debug;
|
||||
Config& config;
|
||||
static uint8_t LedDataPin;
|
||||
static Adafruit_NeoPixel *strip;
|
||||
static BlinkMode blinkMode;
|
||||
ClockDisplayStatus displayStatus;
|
||||
static uint8_t brightness;
|
||||
int clockHour;
|
||||
int clockMinute;
|
||||
bool clockHalted;
|
||||
Color currentColorHandle;
|
||||
Color blinkColorHandle;
|
||||
uint32_t currentColor;
|
||||
uint32_t blinkColor;
|
||||
uint32_t black;
|
||||
void displaySegment(unsigned int ledAddress, uint32_t color);
|
||||
static struct ColorSelection {
|
||||
Color handle;
|
||||
String colorName;
|
||||
uint8_t red, green, blue;
|
||||
} colorSelection[];
|
||||
static int numberOfSupportedColors;
|
||||
};
|
||||
#endif
|
||||
|
202
src/WebUI.cpp
Normal file
202
src/WebUI.cpp
Normal file
@@ -0,0 +1,202 @@
|
||||
#include "WebUI.h"
|
||||
|
||||
static const String appName{"7SegDisplay-XL"};
|
||||
static const char _HEAD[] PROGMEM = "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\" name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\"/><title>{v}</title>";
|
||||
static const char _STYLE[] PROGMEM = "<style>.c{text-align: center;} div,input{padding:5px;font-size:1em;} input{width:95%;} input.r{width:20%;} body{text-align: center;font-family:verdana;} button{border:0;border-radius:0.3rem;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%;} .q{float: right;width: 64px;text-align: right;} .l{background: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAALVBMVEX///8EBwfBwsLw8PAzNjaCg4NTVVUjJiZDRUUUFxdiZGSho6OSk5Pg4eFydHTCjaf3AAAAZElEQVQ4je2NSw7AIAhEBamKn97/uMXEGBvozkWb9C2Zx4xzWykBhFAeYp9gkLyZE0zIMno9n4g19hmdY39scwqVkOXaxph0ZCXQcqxSpgQpONa59wkRDOL93eAXvimwlbPbwwVAegLS1HGfZAAAAABJRU5ErkJggg==\") no-repeat left center;background-size: 1em;}</style>";
|
||||
static const char _SCRIPT[] PROGMEM = "<script>function c(l){document.getElementById('s').value=l.innerText||l.textContent;document.getElementById('p').focus();}</script>";
|
||||
static const char _HEAD_END[] PROGMEM = "</head><body><div style='text-align:left;display:inline-block;min-width:260px;'>";
|
||||
static const char _PORTAL_OPTIONS[] PROGMEM = "<form action=\"/wifi\" method=\"get\"><button>Configure WiFi</button></form><br/><form action=\"/0wifi\" method=\"get\"><button>Configure WiFi (No Scan)</button></form><br/><form action=\"/i\" method=\"get\"><button>Info</button></form><br/><form action=\"/r\" method=\"post\"><button>Reset</button></form>";
|
||||
static const char _ITEM[] PROGMEM = "<div><a href='#p' onclick='c(this)'>{v}</a> <span class='q {i}'>{r}%</span></div>";
|
||||
static const char _FORM_START[] PROGMEM = "<form method='get' action='configSave'>";
|
||||
static const char _FORM_CLOCKNAME[] PROGMEM = "<label for='n'>Fastclock name</label><input id='n' name='n' length=32 placeholder='clock name' value='{n}'><br/>";
|
||||
static const char _FORM_CLOCKSFOUND_START[] PROGMEM = "Fastclocks seen:<br/><ul>";
|
||||
static const char _FORM_CLOCKSFOUND_ITEM[] PROGMEM = "<li>{fc}</li>";
|
||||
static const char _FORM_CLOCKSFOUND_END[] PROGMEM = "</ul><br/>";
|
||||
static const char _FORM_CLOCKMODE_HEADLINE[] PROGMEM = "<br/>Clock mode:<br/>";
|
||||
static const char _FORM_CLOCKMODE_DEMO[] PROGMEM = "<input class='r' id='md' name='m' type='radio' value='demo' {check}><label for='md'>Demo</label><br/>";
|
||||
static const char _FORM_CLOCKMODE_REAL[] PROGMEM = "<input class='r' id='mr' name='m' type='radio' value='real' {check}><label for='md'>Real Clock</label><br/>";
|
||||
static const char _FORM_CLOCKMODE_FAST[] PROGMEM = "<input class='r' id='mf' name='m' type='radio' value='fast' {check}><label for='md'>Fast Clock</label><br/>";
|
||||
static const char _FORM_UTC_OFFSET[] PROGMEM = "<label for='utc'>UTC offset (minutes)</label><input id='utc' name='utc' length=4 placeholder='120'><br/>";
|
||||
static const char _FORM_PARAM[] PROGMEM = "<br/><input id='{i}' name='{n}' maxlength={l} placeholder='{p}' value='{v}' {c}>";
|
||||
static const char _FORM_COLOR_HEADLINE[] PROGMEM = "Display color:<br/>";
|
||||
static const char _FORM_COLOR_template[] PROGMEM = "<input class='r' id='{cid}' name='c' type='radio' value='{cname}' {check}><label for='{cid}'>{cname}</label><br/>";
|
||||
static const char _FORM_BRIGHTNESS[] PROGMEM = "<label for='b'>Brightness:</label><input id='b' name='b' type='range' min='10' max='255' value='{bright}'><br/>";
|
||||
static const char _FORM_FASTCLOCK_INFO[] PROGMEM = "<div>Number of fastclocks found: {nfc}</div><br/>";
|
||||
static const char _FORM_END[] PROGMEM = "<br/><button type='submit'>apply</button></form><br/>";
|
||||
static const char _SAVE_PERM_BUTTON[] PROGMEM = "<br/><form action=\"/configSavePermanent\" method=\"get\"><button>Save permanently</button></form><br/>";
|
||||
static const char _CONFIG_BUTTON[] PROGMEM = "<br/><form action=\"/config\" method=\"get\"><button>Configure</button></form><br/>";
|
||||
static const char _VSPACE[] PROGMEM = "<br/><br/>";
|
||||
static const char _SAVED[] PROGMEM = "<div>Credentials Saved<br />Trying to connect ESP to network.<br />If it fails reconnect to AP to try again</div>";
|
||||
static const char _END[] PROGMEM = "</div></body></html>";
|
||||
|
||||
void WebUI::appConfig() {
|
||||
String page = FPSTR(_HEAD);
|
||||
String input;
|
||||
String value;
|
||||
int ivalue;
|
||||
|
||||
page.replace("{v}", "7Seg Config");
|
||||
page += FPSTR(_SCRIPT);
|
||||
page += FPSTR(_STYLE);
|
||||
//page += _customHeadElement;
|
||||
page += FPSTR(_HEAD_END);
|
||||
page += String(F("<h1>"));
|
||||
page += appName;
|
||||
page += String(F("</h1>"));
|
||||
page += String(F("<h3>Clock Options</h3>"));
|
||||
//page += FPSTR(_PORTAL_OPTIONS);
|
||||
page += FPSTR(_FORM_START);
|
||||
page += FPSTR(_FORM_CLOCKMODE_HEADLINE);
|
||||
input = FPSTR(_FORM_CLOCKMODE_DEMO);
|
||||
input.replace("{check}", (config.getString("appMode").equals("Demo")) ? "checked" : "");
|
||||
page += input;
|
||||
input = FPSTR(_FORM_CLOCKMODE_REAL);
|
||||
input.replace("{check}", (config.getString("appMode").equals("Realclock")) ? "checked" : "");
|
||||
page += input;
|
||||
input = FPSTR(_FORM_CLOCKMODE_FAST);
|
||||
input.replace("{check}", (config.getString("appMode").equals("Fastclock")) ? "checked" : "");
|
||||
page += input;
|
||||
page += FPSTR(_VSPACE);
|
||||
page += FPSTR(_FORM_UTC_OFFSET);
|
||||
page += FPSTR(_VSPACE);
|
||||
input = FPSTR(_FORM_CLOCKNAME);
|
||||
value = config.getString("listenToClock");
|
||||
input.replace("{n}", value);
|
||||
page += input;
|
||||
page += FPSTR(_FORM_CLOCKSFOUND_START);
|
||||
String *knownClocks = fastclock.getKnownClocks();
|
||||
for (int i=0; i<fastclock.getNumberOfKnownClocks(); ++i) {
|
||||
input = FPSTR(_FORM_CLOCKSFOUND_ITEM);
|
||||
input.replace("{fc}", knownClocks[i]);
|
||||
page += input;
|
||||
}
|
||||
page += FPSTR(_FORM_CLOCKSFOUND_END);
|
||||
|
||||
page += FPSTR(_VSPACE);
|
||||
page += FPSTR(_FORM_COLOR_HEADLINE);
|
||||
for (int i=0; i<sevenSegmentClock.getNumberSupportedColors(); ++i) {
|
||||
input = FPSTR(_FORM_COLOR_template);
|
||||
input.replace("{cid}", sevenSegmentClock.getColorName(i));
|
||||
input.replace("{cname}", sevenSegmentClock.getColorName(i));
|
||||
input.replace("{check}", (config.getString("clockColor").equals(sevenSegmentClock.getColorName(i))) ? "checked" : "");
|
||||
page += input;
|
||||
}
|
||||
|
||||
page += FPSTR(_VSPACE);
|
||||
input = FPSTR(_FORM_BRIGHTNESS);
|
||||
value = String(sevenSegmentClock.getBrightness());
|
||||
input.replace("{bright}", value);
|
||||
page += input;
|
||||
|
||||
page += FPSTR(_VSPACE);
|
||||
input = FPSTR(_FORM_FASTCLOCK_INFO);
|
||||
ivalue = fastclock.getNumberOfKnownClocks();
|
||||
value = String(ivalue);
|
||||
//value = String("unknown");
|
||||
input.replace("{nfc}", value);
|
||||
page += input;
|
||||
|
||||
page += FPSTR(_FORM_END);
|
||||
page += FPSTR(_END);
|
||||
|
||||
server->sendHeader("Content-Length", String(page.length()));
|
||||
server->send(200, "text/html", page);
|
||||
}
|
||||
|
||||
void WebUI::appConfigSave(void) {
|
||||
String page = FPSTR(_HEAD);
|
||||
|
||||
page.replace("{v}", "7Seg Config");
|
||||
page += FPSTR(_SCRIPT);
|
||||
page += FPSTR(_STYLE);
|
||||
page += FPSTR(_HEAD_END);
|
||||
page += String(F("<h1>"));
|
||||
page += appName;
|
||||
page += String(F("</h1>"));
|
||||
|
||||
debug.out(server->args(), DEBUG_MED_INFO); debug.outln(" arguments", DEBUG_MED_INFO);
|
||||
for (int i=0; i<server->args(); ++i) {
|
||||
debug.out(server->argName(i), DEBUG_MAX_INFO);
|
||||
debug.out(": ", DEBUG_MAX_INFO);
|
||||
debug.outln(server->arg(i), DEBUG_MAX_INFO);
|
||||
}
|
||||
if (server->hasArg("n")) {
|
||||
String clockName = server->arg("n");
|
||||
config.setString("listenToClock", clockName);
|
||||
fastclock.setListenToClock(clockName);
|
||||
page += F("<div>Set fastclock to listen to clock with name ");
|
||||
page += clockName;
|
||||
page += F(".</div>");
|
||||
}
|
||||
if (server->hasArg("b")) {
|
||||
int brightness = server->arg("b").toInt();
|
||||
sevenSegmentClock.setBrightness(brightness);
|
||||
config.setInt("brightness", brightness);
|
||||
page += F("<div>Set brightness to ");
|
||||
page += server->arg("b");
|
||||
page += F(".</div>");
|
||||
}
|
||||
if (server->hasArg("c")) {
|
||||
String colorName = server->arg("c");
|
||||
SevenSegmentClock::Color colorHandle = sevenSegmentClock.getColorHandleByName(server->arg("c"));
|
||||
sevenSegmentClock.setColor(colorHandle);
|
||||
config.setString("clockColor", colorName);
|
||||
page += F("<div>Set color to ");
|
||||
page += server->arg("c");
|
||||
page += F(".</div>");
|
||||
}
|
||||
if (server->hasArg("m")) {
|
||||
debug.out("setting clock mode to ", DEBUG_MAX_INFO); debug.outln(server->arg("m"), DEBUG_MAX_INFO);
|
||||
page += F("<div>Set clock mode to ");
|
||||
page += server->arg("m");
|
||||
page += F(".</div>");
|
||||
if (server->arg("m").equals("real")) config.setString("appMode", MODE_REALCLOCK);
|
||||
else if (server->arg("m").equals("fast")) config.setString("appMode", MODE_FASTCLOCK);
|
||||
else if (server->arg("m").equals("demo")) config.setString("appMode", MODE_DEMO);
|
||||
else {
|
||||
debug.outln("ERROR: Unknown application mode, going into demo mode", DEBUG_ERROR);
|
||||
config.setString("appMode", MODE_DEMO);
|
||||
page += F("<div>ERROR: Unknown clockmode, using default: demo.</div>");
|
||||
}
|
||||
}
|
||||
if (server->hasArg("utc")) {
|
||||
page += F("<div>Set real clock offset to ");
|
||||
int timeOffset;
|
||||
if (server->arg("utc").equals("")) {
|
||||
page += "120";
|
||||
timeOffset = 120;
|
||||
} else {
|
||||
page += server->arg("utc");
|
||||
timeOffset = server->arg("utc").toInt();
|
||||
}
|
||||
config.setInt("utcTimeOffsetMinutes", timeOffset);
|
||||
timeClient.setTimeOffset(timeOffset * 60);
|
||||
page += F(" minutes.</div>");
|
||||
}
|
||||
page += String(F("<div>Configuration updated.</div>"));
|
||||
page += FPSTR(_CONFIG_BUTTON);
|
||||
page += FPSTR(_SAVE_PERM_BUTTON);
|
||||
page += FPSTR(_END);
|
||||
server->sendHeader("Content-Length", String(page.length()));
|
||||
server->send(200, "text/html", page);
|
||||
}
|
||||
|
||||
void WebUI::appConfigSavePermanent(void) {
|
||||
String page = FPSTR(_HEAD);
|
||||
|
||||
page.replace("{v}", "7Seg Config");
|
||||
page += FPSTR(_SCRIPT);
|
||||
page += FPSTR(_STYLE);
|
||||
page += FPSTR(_HEAD_END);
|
||||
page += String(F("<h1>"));
|
||||
page += appName;
|
||||
page += String(F("</h1>"));
|
||||
|
||||
debug.outln("Writing configs to save them permanently", DEBUG_MAX_INFO);
|
||||
config.writeAllConfigs();
|
||||
page += String(F("<div>Configuration permanently saved.</div>"));
|
||||
page += FPSTR(_CONFIG_BUTTON);
|
||||
page += FPSTR(_END);
|
||||
server->sendHeader("Content-Length", String(page.length()));
|
||||
server->send(200, "text/html", page);
|
||||
}
|
44
src/WebUI.h
Normal file
44
src/WebUI.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#ifndef _webui_included
|
||||
#define _webui_included
|
||||
|
||||
#include "app.h"
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <NTPClient.h>
|
||||
#include "DjDebug.h"
|
||||
#include "DjConfig.h"
|
||||
#include "SevenSegmentClock.h"
|
||||
#include "ClockClient.h"
|
||||
|
||||
class WebUI {
|
||||
public:
|
||||
WebUI(Debug& _debug, Config& _config, NTPClient& _timeClient, ClockClient& _fastclock, SevenSegmentClock& _sevenSegmentClock):
|
||||
debug(_debug), config(_config), timeClient(_timeClient), fastclock(_fastclock), sevenSegmentClock(_sevenSegmentClock) {
|
||||
debug.outln(F("WebUI constructor called"));
|
||||
};
|
||||
void begin(void) {
|
||||
server = new ESP8266WebServer(80);
|
||||
server->on("/config", std::bind(&WebUI::appConfig, this));
|
||||
server->on("/configSave", std::bind(&WebUI::appConfigSave, this));
|
||||
server->on("/configSavePermanent", std::bind(&WebUI::appConfigSavePermanent, this));
|
||||
//server->on("/config", HTTP_GET, appConfig);
|
||||
//server->on("/configSave", HTTP_GET, appConfigSave);
|
||||
//server->on("/configSavePermanent", HTTP_GET, appConfigSavePermanent);
|
||||
|
||||
server->begin();
|
||||
};
|
||||
void loop(void) {
|
||||
server->handleClient();
|
||||
};
|
||||
void appConfig();
|
||||
void appConfigSave();
|
||||
void appConfigSavePermanent();
|
||||
private:
|
||||
Debug& debug;
|
||||
Config& config;
|
||||
NTPClient& timeClient;
|
||||
ClockClient& fastclock;
|
||||
SevenSegmentClock& sevenSegmentClock;
|
||||
ESP8266WebServer *server;
|
||||
};
|
||||
|
||||
#endif
|
10
src/app.h
Normal file
10
src/app.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifndef _app_included
|
||||
#define _app_included
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#define MODE_DEMO "Demo"
|
||||
#define MODE_REALCLOCK "Realclock"
|
||||
#define MODE_FASTCLOCK "Fastclock"
|
||||
|
||||
#endif
|
243
src/main.cpp
243
src/main.cpp
@@ -1,38 +1,51 @@
|
||||
#include <Arduino.h>
|
||||
#include "app.h"
|
||||
#include <FS.h> //this needs to be first, or it all crashes and burns...
|
||||
#include <ESP8266WiFi.h> //https://github.com/esp8266/Arduino
|
||||
|
||||
//needed for library
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <NTPClient.h> //https://github.com/esp8266/Arduino
|
||||
#include <WiFiUdp.h>
|
||||
#include <DNSServer.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <WiFiManager.h>
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <SPI.h>
|
||||
#include "DjDebug.h"
|
||||
#include "DjConfig.h"
|
||||
#include "SevenSegmentClock.h"
|
||||
#include "DjDebug.h"
|
||||
#include "ClockClient.h"
|
||||
#include "WebUI.h"
|
||||
#include "DjSimpleFS.h"
|
||||
|
||||
#define USE_CONFIG false
|
||||
// NTP
|
||||
WiFiUDP ntpUDP;
|
||||
// You can specify the time server pool and the offset (in seconds, can be
|
||||
// changed later with setTimeOffset() ). Additionaly you can specify the
|
||||
// update interval (in milliseconds, can be changed using setUpdateInterval() ).
|
||||
NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 7200, 60000);
|
||||
|
||||
static const char *appName = "FastclockClient7Seg";
|
||||
const char * const PROGMEM mainConfig[] = {
|
||||
"appMode:string:" MODE_DEMO,
|
||||
"utcTimeOffsetMinutes:int:120",
|
||||
"listenPort:int:2000",
|
||||
"clockColor:string:green",
|
||||
"brightness:int:20"
|
||||
};
|
||||
|
||||
#define MAX_CLOCK_NAME_LEN 16
|
||||
#define DEFAULT_CLOCK_CHANNEL 1
|
||||
|
||||
SevenSegmentClock sevenSegmentClock;
|
||||
|
||||
char static_ip[16] = "10.0.1.56";
|
||||
char static_gw[16] = "10.0.1.1";
|
||||
char static_sn[16] = "255.255.255.0";
|
||||
|
||||
char clockName[MAX_CLOCK_NAME_LEN+1] = "fastclk";
|
||||
uint8_t clockChannel = DEFAULT_CLOCK_CHANNEL;
|
||||
Debug debug;
|
||||
Config config(debug);
|
||||
ClockClient fastclock(debug, config);
|
||||
SevenSegmentClock sevenSegmentClock(debug, config);
|
||||
ClockClient fastclockClient(debug, config);
|
||||
//ESP8266WebServer *server;
|
||||
WebUI *webUI;
|
||||
SimpleFS filesystem(debug);
|
||||
|
||||
//flag for saving data
|
||||
bool shouldSaveConfig = false;
|
||||
|
||||
//callback notifying us of the need to save config
|
||||
void saveConfigCallback () {
|
||||
Serial.println("Should save config");
|
||||
debug.outln("Should save config", DEBUG_MAX_INFO);
|
||||
shouldSaveConfig = true;
|
||||
}
|
||||
|
||||
@@ -43,29 +56,15 @@ void setupWifiConnection() {
|
||||
wifiManager.setSaveConfigCallback(saveConfigCallback);
|
||||
wifiManager.setConfigPortalTimeout(300);
|
||||
|
||||
//set static ip
|
||||
IPAddress _ip,_gw,_sn;
|
||||
_ip.fromString(static_ip);
|
||||
_gw.fromString(static_gw);
|
||||
_sn.fromString(static_sn);
|
||||
|
||||
//wifiManager.setSTAStaticIPConfig(_ip, _gw, _sn);
|
||||
|
||||
//add all your parameters here
|
||||
//**wifiManager.addParameter(&custom_mqtt_server);
|
||||
//**wifiManager.addParameter(&custom_mqtt_port);
|
||||
//wifiManager.addParameter(&custom_blynk_token);
|
||||
|
||||
//reset settings - for testing
|
||||
//wifiManager.resetSettings();
|
||||
|
||||
//set minimu quality of signal so it ignores AP's under that quality
|
||||
//defaults to 8%
|
||||
wifiManager.setMinimumSignalQuality();
|
||||
wifiManager.setMinimumSignalQuality(15);
|
||||
|
||||
Serial.println("Starting autoConnect ...");
|
||||
if (!wifiManager.autoConnect("FastclockClient7Seg", "password")) {
|
||||
Serial.println("failed to connect and hit timeout");
|
||||
debug.outln("Starting autoConnect ...", DEBUG_MAX_INFO);
|
||||
//if (!wifiManager.autoConnect("FastclockClient7Seg", "password")) {
|
||||
//if (!wifiManager.autoConnect("fc7seg", "password")) {
|
||||
if (!wifiManager.autoConnect("fc7seg")) {
|
||||
debug.outln("failed to connect and hit timeout", DEBUG_WARNING);
|
||||
delay(3000);
|
||||
//reset and try again, or maybe put it to deep sleep
|
||||
ESP.reset();
|
||||
@@ -73,125 +72,85 @@ void setupWifiConnection() {
|
||||
}
|
||||
|
||||
//if you get here you have connected to the WiFi
|
||||
Serial.println("connected...yeey :)");
|
||||
debug.outln("connected...yeey :)", DEBUG_MAX_INFO);
|
||||
|
||||
//save the custom parameters to FS
|
||||
#if USE_CONFIG
|
||||
if (shouldSaveConfig) {
|
||||
Serial.println("saving config");
|
||||
DynamicJsonDocument jsonBuffer(2048);
|
||||
JsonObject json = jsonBuffer.createObject();
|
||||
//**json["mqtt_server"] = mqtt_server;
|
||||
//**json["mqtt_port"] = mqtt_port;
|
||||
//json["blynk_token"] = blynk_token;
|
||||
|
||||
json["ip"] = WiFi.localIP().toString();
|
||||
json["gateway"] = WiFi.gatewayIP().toString();
|
||||
json["subnet"] = WiFi.subnetMask().toString();
|
||||
|
||||
File configFile = SPIFFS.open("/config.json", "w");
|
||||
if (!configFile) {
|
||||
Serial.println("failed to open config file for writing");
|
||||
}
|
||||
|
||||
serializeJsonPretty(json, Serial);
|
||||
serializeJson(json, configFile);
|
||||
configFile.close();
|
||||
//end save
|
||||
debug.outln("saving config ... NYI", DEBUG_MED_INFO);
|
||||
}
|
||||
#endif
|
||||
|
||||
Serial.print("local ip: "); Serial.println(WiFi.localIP());
|
||||
Serial.print("gateway: "); Serial.println(WiFi.gatewayIP());
|
||||
Serial.print("subnet: "); Serial.println(WiFi.subnetMask());
|
||||
debug.out("local ip: ", DEBUG_MAX_INFO); debug.outln(WiFi.localIP(), DEBUG_MAX_INFO);
|
||||
debug.out("gateway: ", DEBUG_MAX_INFO); debug.outln(WiFi.gatewayIP(), DEBUG_MAX_INFO);
|
||||
debug.out("subnet: ", DEBUG_MAX_INFO); debug.outln(WiFi.subnetMask(), DEBUG_MAX_INFO);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void setup() {
|
||||
// if coming from deep sleep, we just go to sleep again
|
||||
debug.out(F("Starting *** "), DEBUG_MAX_INFO); debug.outln(appName, DEBUG_MAX_INFO);
|
||||
debug.out(F("Reset reason: "), DEBUG_MIN_INFO);
|
||||
debug.outln(ESP.getResetReason(), DEBUG_MIN_INFO);
|
||||
|
||||
// put your setup code here, to run once:
|
||||
Serial.begin(115200);
|
||||
Serial.print("Starting *** "); Serial.println(appName);
|
||||
Serial.println(ESP.getResetReason());
|
||||
filesystem.begin();
|
||||
config.begin("main.cfg", mainConfig, sizeof(mainConfig)/sizeof(mainConfig[0]));
|
||||
setupWifiConnection();
|
||||
debug.outln(F("Starting NTP Client"), DEBUG_MAX_INFO);
|
||||
timeClient.begin();
|
||||
timeClient.setTimeOffset(config.getInt("utcTimeOffsetMinutes") * 60);
|
||||
|
||||
//clean FS, for testing
|
||||
//SPIFFS.format();
|
||||
debug.outln(F("Have following configuration:"), DEBUG_MAX_INFO);
|
||||
debug.out(F(" App Mode: "), DEBUG_MAX_INFO); debug.outln(config.getString("appMode"), DEBUG_MAX_INFO);
|
||||
debug.out(F(" Clock color: "), DEBUG_MAX_INFO); debug.outln(config.getString("clockColor"), DEBUG_MAX_INFO);
|
||||
debug.out(F(" Brightness: "), DEBUG_MAX_INFO); debug.outln(config.getString("brightness"), DEBUG_MAX_INFO);
|
||||
debug.out(F(" Clock listen port: "), DEBUG_MAX_INFO); debug.outln(config.getString("listenPort"), DEBUG_MAX_INFO);
|
||||
debug.out(F(" Real time UTC offset: "), DEBUG_MAX_INFO); debug.outln(config.getString("utcTimeOffsetMinutes"), DEBUG_MAX_INFO);
|
||||
|
||||
//read configuration from FS json
|
||||
Serial.println("mounting FS...");
|
||||
|
||||
if (SPIFFS.begin()) {
|
||||
Serial.println("mounted file system");
|
||||
#if USE_CONFIG
|
||||
if (SPIFFS.exists("/config.json")) {
|
||||
//file exists, reading and loading
|
||||
Serial.println("reading config file");
|
||||
File configFile = SPIFFS.open("/config.json", "r");
|
||||
if (configFile) {
|
||||
Serial.println("opened config file");
|
||||
size_t size = configFile.size();
|
||||
// Allocate a buffer to store contents of the file.
|
||||
std::unique_ptr<char[]> buf(new char[size]);
|
||||
|
||||
configFile.readBytes(buf.get(), size);
|
||||
DynamicJsonDocument jsonBuffer(2048);
|
||||
JsonObject json = jsonBuffer.createObject();
|
||||
DeserializationError error = deserializeJson(jsonBuffer, json);
|
||||
serializeJson(json, Serial);
|
||||
if (!error) {
|
||||
Serial.println("\nparsed json");
|
||||
|
||||
//**strcpy(mqtt_server, json["mqtt_server"]);
|
||||
//**strcpy(mqtt_port, json["mqtt_port"]);
|
||||
//strcpy(blynk_token, json["blynk_token"]);
|
||||
|
||||
if (json["ip"]) {
|
||||
Serial.print("setting custom ip from config: ");
|
||||
//**strcpy(static_ip, json["ip"]);
|
||||
//**strcpy(static_gw, json["gateway"]);
|
||||
//**strcpy(static_sn, json["subnet"]);
|
||||
Serial.println(static_ip);
|
||||
/* Serial.println("converting ip");
|
||||
IPAddress ip = ipFromCharArray(static_ip);
|
||||
Serial.println(ip);*/
|
||||
} else {
|
||||
Serial.println("no custom ip in config");
|
||||
}
|
||||
} else {
|
||||
Serial.println("failed to load json config");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
Serial.println("failed to mount FS");
|
||||
}
|
||||
//end read
|
||||
Serial.print("static ip: "); Serial.println(static_ip);
|
||||
// setupWifiConnection();
|
||||
|
||||
/*
|
||||
radio.setClockChannel(clockChannel);
|
||||
radio.setClockName(clockName);
|
||||
radio.begin();
|
||||
debug.outln(F("Starting fastclock ..."), DEBUG_MAX_INFO);
|
||||
fastclock.begin();
|
||||
pinMode(POWER_OFF_PIN, INPUT);
|
||||
*/
|
||||
|
||||
debug.outln(F("Starting 7-segment clock display ..."), DEBUG_MAX_INFO);
|
||||
sevenSegmentClock.begin();
|
||||
sevenSegmentClock.setBrightness(config.getInt("brightness"));
|
||||
sevenSegmentClock.setColor(sevenSegmentClock.getColorHandleByName(config.getString("clockColor")));
|
||||
|
||||
// setting up web server for clock configuration; intentionally very late
|
||||
// as we need the web server during boot to be able to connect to/configure WiFi
|
||||
webUI = new WebUI(debug, config, timeClient, fastclock, sevenSegmentClock);
|
||||
webUI->begin();
|
||||
}
|
||||
|
||||
int hours = 0, minutes = 0;
|
||||
uint32_t nextUpdate_ms = 0;
|
||||
static int hours = 0, minutes = 0;
|
||||
static uint32_t nextUpdate_ms = 0;
|
||||
|
||||
void loop() {
|
||||
if (millis() > nextUpdate_ms) {
|
||||
nextUpdate_ms = millis() + 1000;
|
||||
minutes++;
|
||||
if (minutes > 99) { minutes = 0; }
|
||||
if (minutes % 5 == 0) hours++;
|
||||
if (hours > 99) hours = 0;
|
||||
sevenSegmentClock.displayTime(hours, minutes);
|
||||
if (hours % 4 == 0) sevenSegmentClock.setBlinkMode(SeperatorBlinking); else sevenSegmentClock.setBlinkMode(NoBlinking);
|
||||
}
|
||||
timeClient.update();
|
||||
fastclock.loop();
|
||||
|
||||
if (config.getString("appMode").equals(MODE_DEMO)) {
|
||||
if (millis() > nextUpdate_ms) {
|
||||
nextUpdate_ms = millis() + 1000;
|
||||
minutes++;
|
||||
if (minutes > 99) { minutes = 0; }
|
||||
if (minutes % 5 == 0) hours++;
|
||||
if (hours > 99) hours = 0;
|
||||
sevenSegmentClock.displayTime(hours, minutes);
|
||||
if (hours % 4 == 0) sevenSegmentClock.setBlinkMode(SevenSegmentClock::SeperatorBlinking); else sevenSegmentClock.setBlinkMode(SevenSegmentClock::NoBlinking);
|
||||
}
|
||||
} else if (config.getString("appMode").equals(MODE_REALCLOCK)) {
|
||||
sevenSegmentClock.setClockHalted(!fastclock.isActive());
|
||||
sevenSegmentClock.setBlinkMode(SevenSegmentClock::NoBlinking);
|
||||
sevenSegmentClock.displayTime(timeClient.getHours(), timeClient.getMinutes());
|
||||
} else if (config.getString("appMode").equals(MODE_FASTCLOCK)) {
|
||||
sevenSegmentClock.setClockHalted(!fastclock.isActive());
|
||||
if (fastclock.isActive()) {
|
||||
sevenSegmentClock.setBlinkMode(SevenSegmentClock::NoBlinking);
|
||||
} else {
|
||||
sevenSegmentClock.setBlinkMode(SevenSegmentClock::DecimalPointColoredBlinking);
|
||||
}
|
||||
sevenSegmentClock.displayTime(fastclock.getClockHours(), fastclock.getClockMinutes());
|
||||
} else { debug.outln(F("ERROR: Unknown appMode found."), DEBUG_ERROR); }
|
||||
|
||||
sevenSegmentClock.displayUpdate();
|
||||
webUI->loop();
|
||||
filesystem.loop();
|
||||
}
|
||||
|
Reference in New Issue
Block a user