Compare commits

...

19 Commits

Author SHA1 Message Date
57cfbe9a9c Added pictures and doc info. 2019-06-12 09:25:13 +02:00
d0b0041a40 Make use of SimpleFS to init filesystem. 2019-06-07 09:43:09 +02:00
f4fbd95183 Fixed writeAllConfigs.
Ignore changes if value does not change.
2019-06-07 09:22:12 +02:00
30a30b30de Feature: Added blinking when fastclock is halted. Added Magenta and Cyan colors. 2019-06-07 08:42:48 +02:00
d2ac7c4903 Fixed: Reduced big space between fastclock list and display color. 2019-06-06 23:23:26 +02:00
2bed562f44 Refactoring: Moved WebUI into a seperate module. 2019-06-06 23:13:22 +02:00
23df52cbb6 Fixed: Change config of fastclock name did apply to fastclock. 2019-06-06 22:15:29 +02:00
d3da31d6db Feature: Save configs permanently and automatically restore on boot. 2019-06-06 19:58:28 +02:00
59e84a6146 Fixed: Setting fastclock name in config. 2019-06-06 19:22:34 +02:00
0d1dff14f7 Refactoring: Moved color handling to clock display (from main).
Fixed: Apply config changes to config module.
2019-06-06 19:07:04 +02:00
c4b9b93d95 Cleaned up unused code. 2019-06-06 16:43:16 +02:00
93e82c535e Added existing fastclock implementation.
Added DjDebug, DjConfig.
2019-06-06 16:40:38 +02:00
362e0760db Added UTC time offset for real clock including configuration. Change color default to green. 2019-06-05 07:36:38 +02:00
cb0a0f89dc Fix typo 2019-06-04 21:44:31 +02:00
b773437d45 Changed default clock mode to Realclock 2019-06-04 21:43:48 +02:00
22df009807 Added color Yellow. 2019-06-04 21:43:09 +02:00
07984234ab Added NTP client to get real time.
Added support for 3 clock modes: demo / real / fast
2019-06-04 21:33:13 +02:00
4f28b11cf7 Configuration via Web-Interface introduced. Can change color and brightness. 2019-06-04 20:41:41 +02:00
c3b80e668b 1st working version -- start with blue to reduce the needed power when starting the display. 2019-06-03 16:54:56 +02:00
21 changed files with 1535 additions and 208 deletions

View File

@@ -6,11 +6,17 @@ But it might as well be used as a fast clock display for model railroads.
Prerequisites: Prerequisites:
* WS-2812 based adressable LED chain forming the 7-segment display - WS-2812 based adressable LED chain forming the 7-segment display
* number of LEDs per segment is configurable - 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):
![Clock Picture](doc/Clock1a.png)
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:
![Config Menu](doc/WebConfig.png)
## Links / References ## Links / References
- example of 3d printable segment frames - example of 3d printable segment frames

BIN
doc/Clock1a.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

BIN
doc/WebConfig.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -17,4 +17,5 @@ upload_port = /dev/cu.wchusbserial1420
[lib_deps] [lib_deps]
library = library =
WifiManager, WifiManager,
Adafruit NeoPixel Adafruit NeoPixel,
NTPClient

163
src/ClockClient.cpp Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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

View 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
View 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
View 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
View 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

View File

@@ -2,7 +2,21 @@
static const uint16_t PixelCount = 4*7*3+3; 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 // Seven Segment Layout: 3 LEDs per segment
// order of segments: // order of segments:
@@ -34,11 +48,11 @@ static const uint8_t digitOffset[] = { 0, LedsPerDigit, 2*LedsPerDigit+Seperator
#define Seg_f 0x20 #define Seg_f 0x20
#define Seg_g 0x40 #define Seg_g 0x40
#define decimalPointLed (2*LedsPerDigit) #define clockSeperatorLed1 (2*LedsPerDigit)
#define clockSeperatorLed1 (2*LedsPerDigit+1) #define clockSeperatorLed2 (2*LedsPerDigit+1)
#define clockSeperatorLed2 (2*LedsPerDigit+2) #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) #define lastCharacterMapped (sizeof(charMapping) + firstCharacterMapped)
static const unsigned char PROGMEM charMapping[] = { static const unsigned char PROGMEM charMapping[] = {
@@ -54,6 +68,7 @@ static const unsigned char PROGMEM charMapping[] = {
/* ) */ Seg_b + Seg_c + Seg_f + Seg_g, /* ) */ Seg_b + Seg_c + Seg_f + Seg_g,
/* * */ 0, /* * */ 0,
/* + */ 0, /* + */ 0,
/* , */ 0,
/* - */ Seg_d, /* - */ Seg_d,
/* . */ 0, /* . */ 0,
/* / */ Seg_e, /* / */ Seg_e,
@@ -63,7 +78,7 @@ static const unsigned char PROGMEM charMapping[] = {
/* 3 */ Seg_b + Seg_c + Seg_d + Seg_f + Seg_g, /* 3 */ Seg_b + Seg_c + Seg_d + Seg_f + Seg_g,
/* 4 */ Seg_a + Seg_c + Seg_d + Seg_g, /* 4 */ Seg_a + Seg_c + Seg_d + Seg_g,
/* 5 */ Seg_a + Seg_b + Seg_d + Seg_f + 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, /* 7 */ Seg_b + Seg_c + Seg_g,
/* 8 */ Seg_a + Seg_b + Seg_c + Seg_d + Seg_e + Seg_f + 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, /* 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 /* || */ Seg_a + Seg_c + Seg_e + Seg_g
}; };
uint8_t SevenSegmentClock::brightness;
void SevenSegmentClock::displaySegment(unsigned int ledAddress, uint32_t color) { void SevenSegmentClock::displaySegment(unsigned int ledAddress, uint32_t color) {
//Serial.print("displaySegment led="); Serial.print(ledAddress); Serial.print(" color=0x"); Serial.println(color, HEX); //Serial.print("displaySegment led="); Serial.print(ledAddress); Serial.print(" color=0x"); Serial.println(color, HEX);
for (int i=0; i<LedsPerSegment; i++) { for (int i=0; i<LedsPerSegment; i++) {
@@ -158,19 +175,28 @@ void SevenSegmentClock::displayDigit(unsigned int digitNum, char charToDisplay)
} }
void SevenSegmentClock::displaySeperator(char seperatorCharacter) { void SevenSegmentClock::displaySeperator(char seperatorCharacter) {
displaySeperator(seperatorCharacter, currentColor);
}
void SevenSegmentClock::displaySeperator(char seperatorCharacter, uint32_t color) {
//Serial.print("displaySeperator: seperator="); Serial.println(seperatorCharacter); //Serial.print("displaySeperator: seperator="); Serial.println(seperatorCharacter);
switch (seperatorCharacter) { switch (seperatorCharacter) {
case '.': case '.':
case ',': case ',':
strip->setPixelColor(decimalPointLed, currentColor); strip->setPixelColor(decimalPointLed, color);
strip->setPixelColor(clockSeperatorLed1, black); strip->setPixelColor(clockSeperatorLed1, black);
strip->setPixelColor(clockSeperatorLed2, black); strip->setPixelColor(clockSeperatorLed2, black);
break; break;
case ':': case ':':
strip->setPixelColor(decimalPointLed, black); strip->setPixelColor(decimalPointLed, black);
strip->setPixelColor(clockSeperatorLed1, currentColor); strip->setPixelColor(clockSeperatorLed1, color);
strip->setPixelColor(clockSeperatorLed2, currentColor); strip->setPixelColor(clockSeperatorLed2, color);
break; break;
case '|':
strip->setPixelColor(decimalPointLed, color);
strip->setPixelColor(clockSeperatorLed1, color);
strip->setPixelColor(clockSeperatorLed2, color);
break;
default: default:
Serial.print("SevenSegmentClock::displaySeperator: Unknown character to be displayed: "); Serial.print("SevenSegmentClock::displaySeperator: Unknown character to be displayed: ");
Serial.println(seperatorCharacter); 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) { void SevenSegmentClock::displayTime(int hour, int minute) {
clockHour = hour; if (clockHour != hour || clockMinute != minute) {
clockMinute = minute; clockHour = hour;
Serial.print("SevenSegmentClock: new time "); clockMinute = minute;
Serial.print(clockHour); Serial.print(":"); Serial.println(clockMinute); Serial.print("SevenSegmentClock: new time ");
Serial.print(clockHour); Serial.print(":"); Serial.println(clockMinute);
}
displayUpdate(); displayUpdate();
}; };
SevenSegmentClock::BlinkMode SevenSegmentClock::blinkMode;
void SevenSegmentClock::displayUpdate(void) { void SevenSegmentClock::displayUpdate(void) {
char displayText[4]; char displayText[4];
static int lastHour=0, lastMinute=0; 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) { if (clockHour != lastHour || clockMinute != lastMinute || millis()-lastUpdate_ms > TIME_BETWEEN_DISPLAY_UPDATES_ms) {
lastHour = clockHour; lastHour = clockHour;
lastMinute = clockMinute; lastMinute = clockMinute;
displayText[0] = (hour > 9) ? '0' + (hour/10) : ' '; displayText[0] = (clockHour > 9) ? '0' + (clockHour/10) : ' ';
displayText[1] = '0' + hour % 10; displayText[1] = '0' + clockHour % 10;
displayText[2] = '0' + minute / 10; displayText[2] = '0' + clockMinute / 10;
displayText[3] = '0' + minute % 10; displayText[3] = '0' + clockMinute % 10;
switch (blinkMode) { switch (blinkMode) {
case NoBlinking: case NoBlinking:
displayDigit(0, displayText[0]); displayDigit(0, displayText[0]);
displayDigit(1, displayText[1]); displayDigit(1, displayText[1]);
displayDigit(2, displayText[2]); displayDigit(2, displayText[2]);
displayDigit(3, displayText[3]); displayDigit(3, displayText[3]);
displaySeperator(':'); //displaySeperator(':');
displaySeperator(currentColor);
displayDecimalPoint(black);
break; break;
case ClockBlinking: case ClockBlinking:
if (currentlyBlinkOn) { if (currentlyBlinkOn) {
@@ -220,17 +260,15 @@ void SevenSegmentClock::displayUpdate(void) {
displayDigit(1, displayText[1]); displayDigit(1, displayText[1]);
displayDigit(2, displayText[2]); displayDigit(2, displayText[2]);
displayDigit(3, displayText[3]); displayDigit(3, displayText[3]);
displaySeperator(':'); //displaySeperator(':');
displaySeperator(currentColor);
} else { } else {
displayDigit(0, ' '); displayDigit(0, ' ');
displayDigit(1, ' '); displayDigit(1, ' ');
displayDigit(2, ' '); displayDigit(2, ' ');
displayDigit(3, ' '); displayDigit(3, ' ');
displaySeperator(' '); //displaySeperator(' ');
} displaySeperator(black);
if (millis() > nextBlinkSwitch_ms) {
currentlyBlinkOn = !currentlyBlinkOn;
nextBlinkSwitch_ms = millis() + (currentlyBlinkOn ? BLINK_ON_TIME_ms : BLINK_OFF_TIME_ms);
} }
break; break;
case SeperatorBlinking: case SeperatorBlinking:
@@ -239,13 +277,13 @@ void SevenSegmentClock::displayUpdate(void) {
displayDigit(2, displayText[2]); displayDigit(2, displayText[2]);
displayDigit(3, displayText[3]); displayDigit(3, displayText[3]);
if (currentlyBlinkOn) { if (currentlyBlinkOn) {
displaySeperator(':'); //displaySeperator('|');
displaySeperator(currentColor);
displayDecimalPoint(currentColor);
} else { } else {
displaySeperator(' '); //displaySeperator(' ');
} displaySeperator(black);
if (millis() > nextBlinkSwitch_ms) { displayDecimalPoint(black);
currentlyBlinkOn = !currentlyBlinkOn;
nextBlinkSwitch_ms = millis() + (currentlyBlinkOn ? BLINK_ON_TIME_ms : BLINK_OFF_TIME_ms);
} }
break; break;
case DecimalPointBlinking: case DecimalPointBlinking:
@@ -254,47 +292,113 @@ void SevenSegmentClock::displayUpdate(void) {
displayDigit(2, displayText[2]); displayDigit(2, displayText[2]);
displayDigit(3, displayText[3]); displayDigit(3, displayText[3]);
if (currentlyBlinkOn) { if (currentlyBlinkOn) {
displaySeperator('.'); //displaySeperator('.');
displayDecimalPoint(currentColor);
} else { } else {
displaySeperator(' '); //displaySeperator(' ');
displayDecimalPoint(black);
} }
if (millis() > nextBlinkSwitch_ms) { break;
currentlyBlinkOn = !currentlyBlinkOn; case DecimalPointColoredBlinking:
nextBlinkSwitch_ms = millis() + (currentlyBlinkOn ? BLINK_ON_TIME_ms : BLINK_OFF_TIME_ms); 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; break;
} }
if (millis() > nextBlinkSwitch_ms) {
currentlyBlinkOn = !currentlyBlinkOn;
nextBlinkSwitch_ms = millis() + (currentlyBlinkOn ? BLINK_ON_TIME_ms : BLINK_OFF_TIME_ms);
}
strip->show(); strip->show();
Serial.print("Shown: "); Serial.print(displayText[0]); Serial.print(displayText[1]); //Serial.print("Shown: "); Serial.print(displayText[0]); Serial.print(displayText[1]);
Serial.print(':'); Serial.print(displayText[2]); Serial.println(displayText[3]); //Serial.print(':'); Serial.print(displayText[2]); Serial.println(displayText[3]);
lastUpdate_ms = millis(); lastUpdate_ms = millis();
} }
} }
uint32_t SevenSegmentClock::red, SevenSegmentClock::green, SevenSegmentClock::blue, SevenSegmentClock::white, SevenSegmentClock::black;
uint8_t SevenSegmentClock::LedDataPin; uint8_t SevenSegmentClock::LedDataPin;
Adafruit_NeoPixel *SevenSegmentClock::strip; 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) { void SevenSegmentClock::begin(void) {
Serial.println("Init Neopixels ..."); Serial.println("Init Neopixels ...");
Serial.print("LED pin="); Serial.println(LedDataPin); Serial.print("LED pin="); Serial.println(LedDataPin);
Serial.print("Pixels="); Serial.println(PixelCount); Serial.print("Pixels="); Serial.println(PixelCount);
SevenSegmentClock::strip = new Adafruit_NeoPixel(PixelCount, LedDataPin, NEO_GRB + NEO_KHZ800); SevenSegmentClock::strip = new Adafruit_NeoPixel(PixelCount, LedDataPin, NEO_GRB + NEO_KHZ800);
strip->begin(); strip->begin();
SevenSegmentClock::red = strip->Color(colorSaturation, 0, 0); setClockHalted(true);
SevenSegmentClock::green = strip->Color(0, colorSaturation, 0); setBrightness(20);
SevenSegmentClock::blue = strip->Color(0, 0, colorSaturation); setColor(Green);
SevenSegmentClock::white = strip->Color(colorSaturation, colorSaturation, colorSaturation); setBlinkColor(Red);
SevenSegmentClock::black = strip->Color(0, 0, 0); black = strip->Color(0, 0, 0);
SevenSegmentClock::currentColor = SevenSegmentClock::white; strip->clear();
// 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;
}
strip->show(); strip->show();
delay(2000);
} }

View File

@@ -2,37 +2,65 @@
#define sevenSegmentClock_h_included #define sevenSegmentClock_h_included
#include <Adafruit_NeoPixel.h> #include <Adafruit_NeoPixel.h>
#include "DjDebug.h"
#include "DjConfig.h"
// avoid flickering of the display: // avoid flickering of the display:
#define TIME_BETWEEN_DISPLAY_UPDATES_ms 300 #define TIME_BETWEEN_DISPLAY_UPDATES_ms 100
#define BLINK_OFF_TIME_ms 600 #define BLINK_OFF_TIME_ms 400
#define BLINK_ON_TIME_ms 400 #define BLINK_ON_TIME_ms 400
#define defaultLedDataPin 2 #define defaultLedDataPin 2
class SevenSegmentClock { class SevenSegmentClock {
public: public:
SevenSegmentClock() { LedDataPin=defaultLedDataPin; init(); }; SevenSegmentClock(Debug& _debug, Config& _config):debug(_debug), config(_config) { LedDataPin=defaultLedDataPin; };
SevenSegmentClock(uint8_t dataPin) { LedDataPin=dataPin; init(); }; SevenSegmentClock(Debug& _debug, Config& _config, uint8_t dataPin):debug(_debug), config(_config) { LedDataPin=dataPin; };
void begin(void); void begin(void);
void displayTime(int hour, int minute); void displayTime(int hour, int minute);
void displayUpdate(void); void displayUpdate(void);
//void setClockSpeed(int _msPerModelSecond) { msPerModelSecond = _msPerModelSecond; setClockSpeed("x"); }; //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 setBlinkMode(BlinkMode _blinkMode) { blinkMode = _blinkMode; };
void setClockHalted(bool halted) { clockHalted = halted; }; void setClockHalted(bool halted) { clockHalted = halted; };
static uint32_t red, green, blue, white, black; enum Color { Black, Red, Green, Blue, White, Yellow, Magenta, Cyan };
enum ClockDisplayStatus { Off, Booting, Halted, StandardClock, FastClock }; void setColor(Color color);
void setBlinkColor(Color color);
Color getColor(void) { return currentColorHandle; };
void displayDigit(unsigned int digitNum, char c); void displayDigit(unsigned int digitNum, char c);
void displaySeperator(char seperatorCharacter); 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: private:
void init(void) { displayStatus = Off; clockHour=12; clockMinute=34; setClockHalted(true); }; Debug& debug;
Config& config;
static uint8_t LedDataPin; static uint8_t LedDataPin;
static Adafruit_NeoPixel *strip; static Adafruit_NeoPixel *strip;
static BlinkMode blinkMode; static BlinkMode blinkMode;
ClockDisplayStatus displayStatus; static uint8_t brightness;
int clockHour; int clockHour;
int clockMinute; int clockMinute;
bool clockHalted; bool clockHalted;
Color currentColorHandle;
Color blinkColorHandle;
uint32_t currentColor; uint32_t currentColor;
uint32_t blinkColor;
uint32_t black;
void displaySegment(unsigned int ledAddress, uint32_t color); 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 #endif

202
src/WebUI.cpp Normal file
View 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>&nbsp;<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
View 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
View 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

View File

@@ -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 <FS.h> //this needs to be first, or it all crashes and burns...
#include <ESP8266WiFi.h> //https://github.com/esp8266/Arduino #include <ESP8266WiFi.h>
#include <NTPClient.h> //https://github.com/esp8266/Arduino
//needed for library #include <WiFiUdp.h>
#include <DNSServer.h> #include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h> #include <WiFiManager.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <SPI.h> #include <SPI.h>
#include "DjDebug.h"
#include "DjConfig.h"
#include "SevenSegmentClock.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"; 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 Debug debug;
#define DEFAULT_CLOCK_CHANNEL 1 Config config(debug);
ClockClient fastclock(debug, config);
SevenSegmentClock sevenSegmentClock; SevenSegmentClock sevenSegmentClock(debug, config);
ClockClient fastclockClient(debug, config);
char static_ip[16] = "10.0.1.56"; //ESP8266WebServer *server;
char static_gw[16] = "10.0.1.1"; WebUI *webUI;
char static_sn[16] = "255.255.255.0"; SimpleFS filesystem(debug);
char clockName[MAX_CLOCK_NAME_LEN+1] = "fastclk";
uint8_t clockChannel = DEFAULT_CLOCK_CHANNEL;
//flag for saving data //flag for saving data
bool shouldSaveConfig = false; bool shouldSaveConfig = false;
//callback notifying us of the need to save config //callback notifying us of the need to save config
void saveConfigCallback () { void saveConfigCallback () {
Serial.println("Should save config"); debug.outln("Should save config", DEBUG_MAX_INFO);
shouldSaveConfig = true; shouldSaveConfig = true;
} }
@@ -42,30 +55,16 @@ void setupWifiConnection() {
wifiManager.setSaveConfigCallback(saveConfigCallback); wifiManager.setSaveConfigCallback(saveConfigCallback);
wifiManager.setConfigPortalTimeout(300); 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 //set minimu quality of signal so it ignores AP's under that quality
//defaults to 8% //defaults to 8%
wifiManager.setMinimumSignalQuality(); wifiManager.setMinimumSignalQuality(15);
Serial.println("Starting autoConnect ..."); debug.outln("Starting autoConnect ...", DEBUG_MAX_INFO);
if (!wifiManager.autoConnect("FastclockClient7Seg", "password")) { //if (!wifiManager.autoConnect("FastclockClient7Seg", "password")) {
Serial.println("failed to connect and hit timeout"); //if (!wifiManager.autoConnect("fc7seg", "password")) {
if (!wifiManager.autoConnect("fc7seg")) {
debug.outln("failed to connect and hit timeout", DEBUG_WARNING);
delay(3000); delay(3000);
//reset and try again, or maybe put it to deep sleep //reset and try again, or maybe put it to deep sleep
ESP.reset(); ESP.reset();
@@ -73,125 +72,85 @@ void setupWifiConnection() {
} }
//if you get here you have connected to the WiFi //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 //save the custom parameters to FS
#if USE_CONFIG
if (shouldSaveConfig) { if (shouldSaveConfig) {
Serial.println("saving config"); debug.outln("saving config ... NYI", DEBUG_MED_INFO);
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
} }
#endif
Serial.print("local ip: "); Serial.println(WiFi.localIP()); debug.out("local ip: ", DEBUG_MAX_INFO); debug.outln(WiFi.localIP(), DEBUG_MAX_INFO);
Serial.print("gateway: "); Serial.println(WiFi.gatewayIP()); debug.out("gateway: ", DEBUG_MAX_INFO); debug.outln(WiFi.gatewayIP(), DEBUG_MAX_INFO);
Serial.print("subnet: "); Serial.println(WiFi.subnetMask()); debug.out("subnet: ", DEBUG_MAX_INFO); debug.outln(WiFi.subnetMask(), DEBUG_MAX_INFO);
} }
void setup() { 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: filesystem.begin();
Serial.begin(115200); config.begin("main.cfg", mainConfig, sizeof(mainConfig)/sizeof(mainConfig[0]));
Serial.print("Starting *** "); Serial.println(appName); setupWifiConnection();
Serial.println(ESP.getResetReason()); debug.outln(F("Starting NTP Client"), DEBUG_MAX_INFO);
timeClient.begin();
timeClient.setTimeOffset(config.getInt("utcTimeOffsetMinutes") * 60);
//clean FS, for testing debug.outln(F("Have following configuration:"), DEBUG_MAX_INFO);
//SPIFFS.format(); 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 debug.outln(F("Starting fastclock ..."), DEBUG_MAX_INFO);
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();
fastclock.begin(); fastclock.begin();
pinMode(POWER_OFF_PIN, INPUT);
*/ debug.outln(F("Starting 7-segment clock display ..."), DEBUG_MAX_INFO);
sevenSegmentClock.begin(); 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; static int hours = 0, minutes = 0;
uint32_t nextUpdate_ms = 0; static uint32_t nextUpdate_ms = 0;
void loop() { void loop() {
if (millis() > nextUpdate_ms) { timeClient.update();
nextUpdate_ms = millis() + 1000; fastclock.loop();
minutes++;
if (minutes > 99) { minutes = 0; } if (config.getString("appMode").equals(MODE_DEMO)) {
if (minutes % 5 == 0) hours++; if (millis() > nextUpdate_ms) {
if (hours > 99) hours = 0; nextUpdate_ms = millis() + 1000;
sevenSegmentClock.displayTime(hours, minutes); minutes++;
if (hours % 4 == 0) sevenSegmentClock.setBlinkMode(SeperatorBlinking); else sevenSegmentClock.setBlinkMode(NoBlinking); 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(); sevenSegmentClock.displayUpdate();
webUI->loop();
filesystem.loop();
} }