From 93e82c535ee348a02d47b2809e24b0ed32987094 Mon Sep 17 00:00:00 2001 From: Dirk Jahnke Date: Thu, 6 Jun 2019 16:40:38 +0200 Subject: [PATCH] Added existing fastclock implementation. Added DjDebug, DjConfig. --- src/ClockClient.cpp | 163 ++++++++++++++++++++++ src/ClockClient.h | 71 ++++++++++ src/ConfigTemp.h | 40 ++++++ src/DjConfig.cpp | 250 +++++++++++++++++++++++++++++++++ src/DjConfig.h | 35 +++++ src/DjDebug.cpp | 176 ++++++++++++++++++++++++ src/DjDebug.h | 52 +++++++ src/DjFastclockScanner.cpp | 31 +++++ src/DjFastclockScanner.h | 25 ++++ src/DjSimpleFS.cpp | 79 +++++++++++ src/DjSimpleFS.h | 31 +++++ src/SevenSegmentClock.h | 8 +- src/main.cpp | 273 +++++++++++++++++-------------------- 13 files changed, 1087 insertions(+), 147 deletions(-) create mode 100644 src/ClockClient.cpp create mode 100644 src/ClockClient.h create mode 100644 src/ConfigTemp.h create mode 100644 src/DjConfig.cpp create mode 100644 src/DjConfig.h create mode 100644 src/DjDebug.cpp create mode 100644 src/DjDebug.h create mode 100644 src/DjFastclockScanner.cpp create mode 100644 src/DjFastclockScanner.h create mode 100644 src/DjSimpleFS.cpp create mode 100644 src/DjSimpleFS.h diff --git a/src/ClockClient.cpp b/src/ClockClient.cpp new file mode 100644 index 0000000..585cf6b --- /dev/null +++ b/src/ClockClient.cpp @@ -0,0 +1,163 @@ +// +// FILE: ClockClient.cpp +// PURPOSE: UDP broadcast listener for fastclock (FREMO clock) +// +// + +#include "ClockClient.h" +#include +#include + +#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 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); + } +} diff --git a/src/ClockClient.h b/src/ClockClient.h new file mode 100644 index 0000000..4c79a01 --- /dev/null +++ b/src/ClockClient.h @@ -0,0 +1,71 @@ +// +// FILE: ClockClient.h +// VERSION: 0.1 +// PURPOSE: FREMO Clock Client +// +// + +#ifndef _clockClientLoaded +#define _clockClientLoaded + +#include +#include "DjDebug.h" +#include "DjFastclockScanner.h" +#include "DjConfig.h" +//#include +#include + +#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 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); + static int getNumberOfKnownClocks(); + static String *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 diff --git a/src/ConfigTemp.h b/src/ConfigTemp.h new file mode 100644 index 0000000..fc49fc7 --- /dev/null +++ b/src/ConfigTemp.h @@ -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 diff --git a/src/DjConfig.cpp b/src/DjConfig.cpp new file mode 100644 index 0000000..f662cf7 --- /dev/null +++ b/src/DjConfig.cpp @@ -0,0 +1,250 @@ +// +// FILE: Config.h +// VERSION: 0.1 +// PURPOSE: Configuration of controller, wifi and application basics +// +// + +#include "DjConfig.h" +#include +#include + +#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 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::writeConfigFile(String filename) { + logHeap(); + if (!filename.startsWith("/")) { + filename = "/" + filename; + } + File configFile = SPIFFS.open(filename, "w"); + + if (configFile) { + StaticJsonDocument<2000> json; + for (int i=0; igetString(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"); +} diff --git a/src/DjConfig.h b/src/DjConfig.h new file mode 100644 index 0000000..14bf939 --- /dev/null +++ b/src/DjConfig.h @@ -0,0 +1,35 @@ +// +// FILE: Config.h +// VERSION: 0.1 +// PURPOSE: Configuration of controller, wifi and application basics +// +// + +#ifndef lightningConfigLoaded +#define lightningConfigLoaded true + +#include +#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); + +private: + String _filename = ""; + Debug& debug; + void writeConfigFile(String filename); +}; + +#endif diff --git a/src/DjDebug.cpp b/src/DjDebug.cpp new file mode 100644 index 0000000..617c291 --- /dev/null +++ b/src/DjDebug.cpp @@ -0,0 +1,176 @@ +#include +#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); + } +} diff --git a/src/DjDebug.h b/src/DjDebug.h new file mode 100644 index 0000000..617ffca --- /dev/null +++ b/src/DjDebug.h @@ -0,0 +1,52 @@ + +#ifndef debug_h +#define debug_h + +#include +#include + +#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 + diff --git a/src/DjFastclockScanner.cpp b/src/DjFastclockScanner.cpp new file mode 100644 index 0000000..ac5a360 --- /dev/null +++ b/src/DjFastclockScanner.cpp @@ -0,0 +1,31 @@ +// +// FILE: DjFastclockScanner.h +// VERSION: 1.0 +// PURPOSE: Scans the broadcasts for clocks and returns the clocknames found +// +// + +#include + + +String FastclockScanner::knownClocks[MAX_CLOCKS]; +int FastclockScanner::numberOfKnownClocks = 0; + +void FastclockScanner::addClock(String clockName) { + for (int i=0; i + +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; +} + diff --git a/src/DjSimpleFS.h b/src/DjSimpleFS.h new file mode 100644 index 0000000..79ad718 --- /dev/null +++ b/src/DjSimpleFS.h @@ -0,0 +1,31 @@ +// +// FILE: DjSimpleFS.h +// VERSION: 1.0 +// PURPOSE: File system +// +// + +#ifndef _djSimpleFSLoaded +#define _djSimpleFSLoaded true + +#include +#include +#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 diff --git a/src/SevenSegmentClock.h b/src/SevenSegmentClock.h index fd29dd1..f33abef 100644 --- a/src/SevenSegmentClock.h +++ b/src/SevenSegmentClock.h @@ -2,6 +2,8 @@ #define sevenSegmentClock_h_included #include +#include "DjDebug.h" +#include "DjConfig.h" // avoid flickering of the display: #define TIME_BETWEEN_DISPLAY_UPDATES_ms 100 @@ -10,8 +12,8 @@ #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; init(); }; + SevenSegmentClock(Debug& _debug, Config& _config, uint8_t dataPin):debug(_debug), config(_config) { LedDataPin=dataPin; init(); }; void begin(void); void displayTime(int hour, int minute); void displayUpdate(void); @@ -28,6 +30,8 @@ public: void setBrightness(uint8_t b) { brightness=b; initColors(b); }; uint8_t getBrightness(void) { return brightness; }; private: + Debug& debug; + Config& config; void init(void) { displayStatus = Off; clockHour=12; clockMinute=34; setClockHalted(true); currentColorHandle = Green; currentColor = green; }; static uint8_t LedDataPin; static Adafruit_NeoPixel *strip; diff --git a/src/main.cpp b/src/main.cpp index b699ad1..ccea353 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,7 +11,11 @@ #include #include +#include "DjDebug.h" +#include "DjConfig.h" #include "SevenSegmentClock.h" +#include "DjDebug.h" +#include "ClockClient.h" // NTP WiFiUDP ntpUDP; @@ -20,29 +24,25 @@ WiFiUDP ntpUDP; // update interval (in milliseconds, can be changed using setUpdateInterval() ). NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 7200, 60000); -#define MODE_DEMO 1 -#define MODE_REALCLOCK 2 -#define MODE_FASTCLOCK 3 -static int appMode = MODE_REALCLOCK; - - +#define MODE_DEMO "Demo" +#define MODE_REALCLOCK "Realclock" +#define MODE_FASTCLOCK "Fastclock" 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 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" - -SevenSegmentClock sevenSegmentClock; +Debug debug; +Config config(debug); +ClockClient fastclock(debug, config); +SevenSegmentClock sevenSegmentClock(debug, config); ESP8266WebServer *server; - -char static_ip[16] = "10.0.1.56"; -char static_gw[16] = "10.0.1.1"; -char static_sn[16] = "255.255.255.0"; +ClockClient fastclockClient(debug, config); static struct ColorSelection { uint8_t id; @@ -57,6 +57,8 @@ static struct ColorSelection { { 6, SevenSegmentClock::Yellow, "yellow" } }; +#if 0 + static const String getColorName(uint8_t color) { for (unsigned int i=0; i{v}"; -const char _STYLE[] PROGMEM = ""; -const char _SCRIPT[] PROGMEM = ""; -const char _HEAD_END[] PROGMEM = "
"; -const char _PORTAL_OPTIONS[] PROGMEM = "



"; -const char _ITEM[] PROGMEM = "
{v} {r}%
"; -const char _FORM_START[] PROGMEM = "
"; -const char _FORM_CLOCKNAME[] PROGMEM = "
"; +const char _HEAD[] PROGMEM = "{v}"; +const char _STYLE[] PROGMEM = ""; +const char _SCRIPT[] PROGMEM = ""; +const char _HEAD_END[] PROGMEM = "
"; +const char _PORTAL_OPTIONS[] PROGMEM = "


"; +const char _ITEM[] PROGMEM = "
{v} {r}%
"; +const char _FORM_START[] PROGMEM = "
"; +const char _FORM_CLOCKNAME[] PROGMEM = "
"; const char _FORM_CLOCKMODE_HEADLINE[] PROGMEM = "
Clock mode:
"; const char _FORM_CLOCKMODE_DEMO[] PROGMEM = "
"; const char _FORM_CLOCKMODE_REAL[] PROGMEM = "
"; const char _FORM_CLOCKMODE_FAST[] PROGMEM = "
"; -const char _FORM_UTC_OFFSET[] PROGMEM = "
"; -const char _FORM_PARAM[] PROGMEM = "
"; +const char _FORM_UTC_OFFSET[] PROGMEM = "
"; +const char _FORM_PARAM[] PROGMEM = "
"; const char _FORM_COLOR_HEADLINE[] PROGMEM = "
Display color:
"; -const char _FORM_COLOR_BLUE[] PROGMEM = "
"; -const char _FORM_COLOR_RED[] PROGMEM = "
"; -const char _FORM_COLOR_GREEN[] PROGMEM = "
"; -const char _FORM_COLOR_WHITE[] PROGMEM = "
"; +const char _FORM_COLOR_BLUE[] PROGMEM = "
"; +const char _FORM_COLOR_RED[] PROGMEM = "
"; +const char _FORM_COLOR_GREEN[] PROGMEM = "
"; +const char _FORM_COLOR_WHITE[] PROGMEM = "
"; const char _FORM_COLOR_YELLOW[] PROGMEM = "
"; -const char _FORM_BRIGHTNESS[] PROGMEM = "

"; -const char _FORM_END[] PROGMEM = "
"; -const char _SCAN_LINK[] PROGMEM = "
"; -const char _SAVED[] PROGMEM = "
Credentials Saved
Trying to connect ESP to network.
If it fails reconnect to AP to try again
"; -const char _END[] PROGMEM = "
"; +const char _FORM_BRIGHTNESS[] PROGMEM = "

"; +const char _FORM_FASTCLOCK_INFO[] PROGMEM = "

Number of fastclocks found: {nfc}
"; +const char _FORM_END[] PROGMEM = "
"; +const char _CONFIG_LINK[] PROGMEM = "
"; +const char _SAVED[] PROGMEM = "
Credentials Saved
Trying to connect ESP to network.
If it fails reconnect to AP to try again
"; +const char _END[] PROGMEM = "
"; void appConfig() { String page = FPSTR(_HEAD); @@ -228,37 +205,43 @@ void appConfig() { page += FPSTR(_FORM_START); page += FPSTR(_FORM_CLOCKMODE_HEADLINE); input = FPSTR(_FORM_CLOCKMODE_DEMO); - input.replace("{check}", (appMode == MODE_DEMO) ? "checked" : ""); + input.replace("{check}", (config.getString("appMode").equals("Demo")) ? "checked" : ""); page += input; input = FPSTR(_FORM_CLOCKMODE_REAL); - input.replace("{check}", (appMode == MODE_REALCLOCK) ? "checked" : ""); + input.replace("{check}", (config.getString("appMode").equals("Realclock")) ? "checked" : ""); page += input; input = FPSTR(_FORM_CLOCKMODE_FAST); - input.replace("{check}", (appMode == MODE_FASTCLOCK) ? "checked" : ""); + input.replace("{check}", (config.getString("appMode").equals("Fastclock")) ? "checked" : ""); page += input; page += FPSTR(_FORM_UTC_OFFSET); page += FPSTR(_FORM_CLOCKNAME); page += FPSTR(_FORM_COLOR_HEADLINE); input = FPSTR(_FORM_COLOR_BLUE); - input.replace("{check}", (clockColor == SevenSegmentClock::Blue) ? "checked" : ""); + input.replace("{check}", (config.getString("clockColor").equals("Blue")) ? "checked" : ""); page += input; input = FPSTR(_FORM_COLOR_RED); - input.replace("{check}", (clockColor == SevenSegmentClock::Red) ? "checked" : ""); + input.replace("{check}", (config.getString("clockColor").equals("Red")) ? "checked" : ""); page += input; input = FPSTR(_FORM_COLOR_GREEN); - input.replace("{check}", (clockColor == SevenSegmentClock::Green) ? "checked" : ""); + input.replace("{check}", (config.getString("clockColor").equals("Green")) ? "checked" : ""); page += input; input = FPSTR(_FORM_COLOR_YELLOW); - input.replace("{check}", (clockColor == SevenSegmentClock::Yellow) ? "checked" : ""); + input.replace("{check}", (config.getString("clockColor").equals("Yellow")) ? "checked" : ""); page += input; input = FPSTR(_FORM_COLOR_WHITE); - input.replace("{check}", (clockColor == SevenSegmentClock::White) ? "checked" : ""); + input.replace("{check}", (config.getString("clockColor").equals("White")) ? "checked" : ""); page += input; input = FPSTR(_FORM_BRIGHTNESS); value = String(sevenSegmentClock.getBrightness()); input.replace("{bright}", value); page += input; + input = FPSTR(_FORM_FASTCLOCK_INFO); + //value = String(ClockClient::getNumberOfKnownClocks()); + value = String("unknown"); + input.replace("{nfc}", value); + page += input; + page += FPSTR(_FORM_END); page += FPSTR(_END); @@ -266,6 +249,10 @@ void appConfig() { server->send(200, "text/html", page); } +void setRealClockTimeOffset(int offsetInMinutes) { + timeClient.setTimeOffset(offsetInMinutes * 60); +} + void appConfigSave() { String page = FPSTR(_HEAD); @@ -277,11 +264,11 @@ void appConfigSave() { page += appName; page += String(F("")); - Serial.print("appConfigSave "); Serial.print(server->args()); Serial.println(" arguments"); + debug.out(server->args(), DEBUG_MED_INFO); debug.outln(" arguments", DEBUG_MED_INFO); for (int i=0; iargs(); ++i) { - Serial.print(server->argName(i)); - Serial.print(": "); - Serial.println(server->arg(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("b")) { sevenSegmentClock.setBrightness(server->arg("b").toInt()); @@ -298,52 +285,55 @@ void appConfigSave() { page += F("."); } if (server->hasArg("m")) { - Serial.print("setting clock mode to "); Serial.println(server->arg("m")); + debug.out("setting clock mode to ", DEBUG_MAX_INFO); debug.outln(server->arg("m"), DEBUG_MAX_INFO); page += F("
Set clock mode to "); page += server->arg("m"); page += F(".
"); - if (server->arg("m").equals("real")) appMode = MODE_REALCLOCK; - else if (server->arg("m").equals("fast")) appMode = MODE_FASTCLOCK; - else if (server->arg("m").equals("demo")) appMode = MODE_DEMO; + 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 { - Serial.println("ERROR: Unknown application mode, going into demo mode"); - appMode = MODE_DEMO; + debug.outln("ERROR: Unknown application mode, going into demo mode", DEBUG_ERROR); + config.setString("appMode", MODE_DEMO); page += F("
ERROR: Unknown clockmode, using default: demo.
"); } } if (server->hasArg("utc")) { page += F("
Set real clock offset to "); + int timeOffset; if (server->arg("utc").equals("")) { page += "120"; - utcTimeOffsetMinutes = 120; + timeOffset = 120; } else { page += server->arg("utc"); - utcTimeOffsetMinutes = server->arg("utc").toInt(); + timeOffset = server->arg("utc").toInt(); } - timeClient.setTimeOffset(utcTimeOffsetMinutes); + config.setInt("utcTimeOffsetMinutes", timeOffset); + setRealClockTimeOffset(timeOffset); page += F(" minutes.
"); } page += String(F("
Configuration updated.
")); + page += FPSTR(_CONFIG_LINK); page += FPSTR(_END); server->sendHeader("Content-Length", String(page.length())); server->send(200, "text/html", page); } void setup() { - Serial.begin(115200); - Serial.println("---"); - Serial.print("Starting *** "); Serial.println(appName); - Serial.print("Reset reason: "); - Serial.println(ESP.getResetReason()); + 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); //clean FS, for testing //SPIFFS.format(); //read configuration from FS json - Serial.println("mounting FS..."); + debug.outln(F("mounting FS..."), DEBUG_MAX_INFO); if (SPIFFS.begin()) { - Serial.println("mounted file system"); + debug.outln(F("mounted file system"), DEBUG_MAX_INFO); + config.begin("main.cfg", mainConfig, sizeof(mainConfig)/sizeof(mainConfig[0])); + #if 0 if (SPIFFS.exists("/config.json")) { //file exists, reading and loading Serial.println("reading config file"); @@ -382,20 +372,6 @@ void setup() { } else { Serial.println("no clock color in config"); } -#if 0 - 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"); - } -#endif } else { Serial.println("failed to load json config, using defaults"); strncpy(clockName, DEFAULT_CLOCK_NAME, MAX_CLOCK_NAME_LEN); @@ -407,11 +383,12 @@ void setup() { } else { Serial.println("no config file found"); } +#endif } else { - Serial.println("failed to mount FS"); + debug.outln(F("failed to mount FS"), DEBUG_ERROR); + config.begin("main.cfg", mainConfig, sizeof(mainConfig)/sizeof(mainConfig[0])); } //end read - Serial.print("static ip: "); Serial.println(static_ip); // setupWifiConnection(); /* @@ -422,15 +399,23 @@ void setup() { pinMode(POWER_OFF_PIN, INPUT); */ setupWifiConnection(); - Serial.println("Starting NTP Client"); + debug.outln(F("Starting NTP Client"), DEBUG_MAX_INFO); timeClient.begin(); - Serial.println("Have following configuration:"); - Serial.print(" Clock name: "); Serial.println(clockName); - Serial.print(" Clock channel: "); Serial.println(clockChannelString); - Serial.print(" Clock color: "); Serial.println(getColorName(clockColor)); + debug.outln(F("Have following configuration:"), DEBUG_MAX_INFO); + //Serial.print(" Clock name: "); Serial.println(config.getString("clock_name")); + 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); - Serial.println("Starting 7-segment clock display ..."); + setRealClockTimeOffset(config.getInt("utcTimeOffsetMinutes")); + + debug.outln(F("Starting fastclock ..."), DEBUG_MAX_INFO); + fastclock.begin(); + + debug.outln(F("Starting 7-segment clock display ..."), DEBUG_MAX_INFO); sevenSegmentClock.begin(); // setting up web server for clock configuration @@ -445,27 +430,25 @@ uint32_t nextUpdate_ms = 0; void loop() { timeClient.update(); + fastclock.loop(); //Serial.println(timeClient.getFormattedTime()); - switch (appMode) { - case 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); - } - break; - case MODE_REALCLOCK: - sevenSegmentClock.displayTime(timeClient.getHours(), timeClient.getMinutes()); - break; - case MODE_FASTCLOCK: - break; - } + 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.displayTime(timeClient.getHours(), timeClient.getMinutes()); + } else if (config.getString("appMode").equals(MODE_FASTCLOCK)) { + sevenSegmentClock.displayTime(fastclock.getClockHours(), fastclock.getClockMinutes()); + } else { debug.outln("ERROR: Unknown appMode found.", DEBUG_ERROR); } sevenSegmentClock.displayUpdate(); server->handleClient();