Added existing fastclock implementation.
Added DjDebug, DjConfig.
This commit is contained in:
		
							
								
								
									
										163
									
								
								src/ClockClient.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								src/ClockClient.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,163 @@
 | 
			
		||||
//
 | 
			
		||||
//    FILE: ClockClient.cpp
 | 
			
		||||
// PURPOSE: UDP broadcast listener for fastclock (FREMO clock)
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "ClockClient.h"
 | 
			
		||||
#include <ESP8266WiFi.h>
 | 
			
		||||
#include <WiFiUdp.h>
 | 
			
		||||
 | 
			
		||||
#ifdef ESP8266
 | 
			
		||||
extern "C" {
 | 
			
		||||
#include "user_interface.h"
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
//const char * const PROGMEM clockConfig[] = {"ipMulticast:string:239.50.50.20", "ipInterface:string:192.168.0.100", "listenPort:int:2000", "interpolate:boolean:true"};
 | 
			
		||||
//const char * const PROGMEM clockConfig[] = {"ipMulticast:string:239.50.50.20", "ipInterface:string:127.0.0.1", "listenPort:int:2000", "listenToClock:string:MRclock#2"};
 | 
			
		||||
const char * const PROGMEM clockConfig[] = {
 | 
			
		||||
  "ipMulticast:string:239.50.50.20",
 | 
			
		||||
  "ipInterface:string:127.0.0.1",
 | 
			
		||||
  "listenPort:int:2000",
 | 
			
		||||
  "listenToClock:string:DefaultClock"
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static WiFiUDP udp;
 | 
			
		||||
 | 
			
		||||
#define CLOCK_PACKET_SIZE 1024
 | 
			
		||||
static byte packetBuffer[CLOCK_PACKET_SIZE+1]; //buffer to hold incoming and outgoing packets
 | 
			
		||||
 | 
			
		||||
const char * const ClockClient::getLastMessage() { return (const char *) packetBuffer; }
 | 
			
		||||
 | 
			
		||||
String ClockClient::name{""};
 | 
			
		||||
String ClockClient::text{""};
 | 
			
		||||
String ClockClient::clocktype{""};
 | 
			
		||||
boolean ClockClient::active{false};
 | 
			
		||||
float ClockClient::speed{1.0};
 | 
			
		||||
String ClockClient::clock{""};
 | 
			
		||||
String ClockClient::weekday{""};
 | 
			
		||||
int ClockClient::clockHours{0};
 | 
			
		||||
int ClockClient::clockMinutes{0};
 | 
			
		||||
int ClockClient::clockSeconds{0};
 | 
			
		||||
int ClockClient::numClockChangeCallbacks{0};
 | 
			
		||||
ClockChangeCallback ClockClient::clockChangeCallback[];
 | 
			
		||||
 | 
			
		||||
void ClockClient::addClockChangeCallback(ClockChangeCallback _clockChangeCallback) {
 | 
			
		||||
  if (numClockChangeCallbacks >= MAX_CLOCK_CHANGE_CALLBACKS) {
 | 
			
		||||
    Debug::outln(F("ERROR: Too many clock change callbacks registered!"));
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  clockChangeCallback[numClockChangeCallbacks++] = _clockChangeCallback;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static IPAddress interfaceAddr;
 | 
			
		||||
static IPAddress multicast;
 | 
			
		||||
static uint16_t port = 2000;
 | 
			
		||||
 | 
			
		||||
IPAddress ClockClient::getMulticastIP() { return multicast; }
 | 
			
		||||
 | 
			
		||||
int ClockClient::getListenPort() { return port; }
 | 
			
		||||
 | 
			
		||||
void ClockClient::begin() {
 | 
			
		||||
  debug.outln("Beginning fastclock client", DEBUG_MAX_INFO);
 | 
			
		||||
  config.loadFile("clockclient.cfg", clockConfig, sizeof(clockConfig)/sizeof(clockConfig[0]));
 | 
			
		||||
  name = config.getString("listenToClock");
 | 
			
		||||
  // WiFi.mode(WIFI_STA);
 | 
			
		||||
  multicast.fromString(config.getString("ipMulticast"));
 | 
			
		||||
  port = config.getInt("listenPort");
 | 
			
		||||
  logHeap();
 | 
			
		||||
 | 
			
		||||
  delay(100);
 | 
			
		||||
  if (!udp.beginMulticast(WiFi.localIP(), multicast, port)) {
 | 
			
		||||
    debug.outln(F("ERROR: failed to begin UDP"));
 | 
			
		||||
  } else {
 | 
			
		||||
    debug.outln(F("Successfully started multicast receiver"));
 | 
			
		||||
  }
 | 
			
		||||
  debug.out(F("interfaceAddr=")); debug.out(WiFi.localIP().toString());
 | 
			
		||||
  debug.out(F(", multicast IP=")); debug.out(config.getString("ipMulticast"));
 | 
			
		||||
  debug.out(F(", Port=")); debug.outln(port);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ClockClient::interpretClockMessage(const char *_msg) {
 | 
			
		||||
  String msg = String(_msg);
 | 
			
		||||
  if (!msg.startsWith("fastclock\r\n")) {
 | 
			
		||||
      debug.out(F("ERROR: This is not a fastclock message! Got message=")); debug.outln(msg.substring(0,30));
 | 
			
		||||
  }
 | 
			
		||||
  msg = msg.substring(11);
 | 
			
		||||
  if (!msg.startsWith("version=2\r\n")) {
 | 
			
		||||
    debug.out(F("WARNING: Version of fastclock not supported! Got ")); debug.outln(msg.substring(0,10));
 | 
			
		||||
  }
 | 
			
		||||
  msg = msg.substring(msg.indexOf('\n')+1);
 | 
			
		||||
 | 
			
		||||
  String checkName{""};
 | 
			
		||||
  if (msg.startsWith("name=")) {
 | 
			
		||||
    checkName = msg.substring(msg.indexOf('=')+1, msg.indexOf('\r'));
 | 
			
		||||
    fastclockScanner.addClock(checkName);
 | 
			
		||||
    msg = msg.substring(msg.indexOf('\n')+1);
 | 
			
		||||
  } else { debug.outln(F("ERROR: Clock Message Format invalid! Expected name field.")); return; }
 | 
			
		||||
  if (!checkName.equals(name)) {
 | 
			
		||||
    // this is another clock, we are not following this one
 | 
			
		||||
    debug.out(F("Ignoring clock with name="), DEBUG_MAX_INFO); debug.out(checkName.c_str(), DEBUG_MAX_INFO); debug.out(F("; looking for "), DEBUG_MAX_INFO); debug.outln(name.c_str(), DEBUG_MAX_INFO);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (msg.startsWith("ip-address=")) {
 | 
			
		||||
    msg = msg.substring(msg.indexOf('\n')+1);
 | 
			
		||||
  } else { debug.outln(F("ERROR: Clock Message Format invalid! Expected ip-address field.")); return; }
 | 
			
		||||
  if (msg.startsWith("ip-port=")) {
 | 
			
		||||
    msg = msg.substring(msg.indexOf('\n')+1);
 | 
			
		||||
  } else { debug.outln(F("ERROR: Clock Message Format invalid! Expected ip-port field.")); return; }
 | 
			
		||||
  if (msg.startsWith("text=")) {
 | 
			
		||||
    text = msg.substring(msg.indexOf('=')+1, msg.indexOf('\r'));
 | 
			
		||||
    msg = msg.substring(msg.indexOf('\n')+1);
 | 
			
		||||
  } else { debug.outln(F("ERROR: Clock Message Format invalid! Expected text field.")); return; }
 | 
			
		||||
  if (msg.startsWith("clocktype=")) {
 | 
			
		||||
    clocktype = msg.substring(msg.indexOf('=')+1, msg.indexOf('\r'));
 | 
			
		||||
    msg = msg.substring(msg.indexOf('\n')+1);
 | 
			
		||||
  } else { debug.outln(F("ERROR: Clock Message Format invalid! Expected clocktype field.")); return; }
 | 
			
		||||
  if (msg.startsWith("active=")) {
 | 
			
		||||
    if (msg.startsWith("active=yes\r\n")) {
 | 
			
		||||
      active = true;
 | 
			
		||||
    } else {
 | 
			
		||||
      active = false;
 | 
			
		||||
    }
 | 
			
		||||
    msg = msg.substring(msg.indexOf('\n')+1);
 | 
			
		||||
  } else { debug.outln(F("ERROR: Clock Message Format invalid! Expected active field.")); return; }
 | 
			
		||||
  if (msg.startsWith("speed=")) {
 | 
			
		||||
    speed = msg.substring(msg.indexOf('=')+1, msg.indexOf('\r')).toFloat();
 | 
			
		||||
    msg = msg.substring(msg.indexOf('\n')+1);
 | 
			
		||||
  } else { debug.outln(F("ERROR: Clock Message Format invalid! Expected speed field.")); return; }
 | 
			
		||||
  if (msg.startsWith("clock=")) {
 | 
			
		||||
    clock = msg.substring(msg.indexOf('=')+1, msg.indexOf('\r'));
 | 
			
		||||
    int firstColonPos = clock.indexOf(':');
 | 
			
		||||
    int secondColonPos = clock.lastIndexOf(':');
 | 
			
		||||
    clockHours = clock.substring(0,firstColonPos).toInt();
 | 
			
		||||
    clockMinutes = clock.substring(firstColonPos+1, secondColonPos).toInt();
 | 
			
		||||
    clockSeconds = clock.substring(secondColonPos+1).toInt();
 | 
			
		||||
    msg = msg.substring(msg.indexOf('\n')+1);
 | 
			
		||||
  } else { debug.outln(F("ERROR: Clock Message Format invalid! Expected clock field.")); return; }
 | 
			
		||||
  if (msg.startsWith("weekday=")) {
 | 
			
		||||
    weekday = msg.substring(msg.indexOf('=')+1, msg.indexOf('\r'));
 | 
			
		||||
    msg = msg.substring(msg.indexOf('\n')+1);
 | 
			
		||||
  } else { debug.outln(F("ERROR: Clock Message Format invalid! Expected weekday field.")); return; }
 | 
			
		||||
 | 
			
		||||
  for (int i=0; i<numClockChangeCallbacks; ++i) {
 | 
			
		||||
    clockChangeCallback[i](clockHours, clockMinutes, clockSeconds);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Debug::out(F("Clock Name=")); Debug::out(ClockClient::name.c_str()); Debug::out(F(", ")); Debug::outln(ClockClient::clock.c_str());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ClockClient::loop() {
 | 
			
		||||
  int length = 0;
 | 
			
		||||
 | 
			
		||||
  length = udp.parsePacket();
 | 
			
		||||
  if (length > 0) {
 | 
			
		||||
    // debug.out(F("ClockClient received: ")); debug.out(length); debug.outln(F(" bytes."));
 | 
			
		||||
    udp.read(packetBuffer, CLOCK_PACKET_SIZE);
 | 
			
		||||
    packetBuffer[length] = '\0';
 | 
			
		||||
    interpretClockMessage((char *) packetBuffer);
 | 
			
		||||
    // debug.out(F("> ")); debug.outln( (char *) packetBuffer);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										71
									
								
								src/ClockClient.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/ClockClient.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
			
		||||
//
 | 
			
		||||
//    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 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
 | 
			
		||||
							
								
								
									
										40
									
								
								src/ConfigTemp.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/ConfigTemp.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
#ifndef _config_h_included
 | 
			
		||||
#define _config_h_included
 | 
			
		||||
 | 
			
		||||
#define MAX_CLOCK_NAME_LEN 16
 | 
			
		||||
#define MAX_CLOCK_CHANNEL_STRING_LEN 3
 | 
			
		||||
#define MAX_CLOCK_COLOR_LEN 16
 | 
			
		||||
#define DEFAULT_CLOCK_NAME "fastclk"
 | 
			
		||||
#define DEFAULT_CLOCK_CHANNEL_STRING "1"
 | 
			
		||||
#define DEFAULT_CLOCK_CHANNEL 1
 | 
			
		||||
#define DEFAULT_CLOCK_COLOR "green"
 | 
			
		||||
#define DEFAULT_BRIGHTNESS 31
 | 
			
		||||
//#define DEFAULT_COLOR SevenSegmentClock::Green
 | 
			
		||||
#define DEFAULT_COLOR "Green"
 | 
			
		||||
 | 
			
		||||
class Config {
 | 
			
		||||
public:
 | 
			
		||||
  Config() {
 | 
			
		||||
    strncpy(clockName, DEFAULT_CLOCK_NAME, MAX_CLOCK_NAME_LEN);
 | 
			
		||||
    strncpy(clockChannelString, DEFAULT_CLOCK_CHANNEL_STRING, MAX_CLOCK_CHANNEL_STRING_LEN);
 | 
			
		||||
    clockChannel = DEFAULT_CLOCK_CHANNEL;
 | 
			
		||||
    appMode = MODE_REALCLOCK;
 | 
			
		||||
    utcTimeOffsetMinutes = 120;
 | 
			
		||||
    brightness = DEFAULT_BRIGHTNESS;
 | 
			
		||||
    clockColorName = DEFAULT_COLOR;
 | 
			
		||||
  };
 | 
			
		||||
  enum AppMode { MODE_DEMO, MODE_REALCLOCK, MODE_FASTCLOCK };
 | 
			
		||||
private:
 | 
			
		||||
  char clockName[MAX_CLOCK_NAME_LEN+1];
 | 
			
		||||
  char clockChannelString[MAX_CLOCK_CHANNEL_STRING_LEN+1];
 | 
			
		||||
  uint8_t clockChannel;
 | 
			
		||||
  AppMode appMode;
 | 
			
		||||
  int utcTimeOffsetMinutes;
 | 
			
		||||
  uint8_t brightness;
 | 
			
		||||
  String clockColorName;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//SevenSegmentClock::Color clockColor = DEFAULT_COLOR;
 | 
			
		||||
//uint8_t brightness = DEFAULT_BRIGHTNESS;
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										250
									
								
								src/DjConfig.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										250
									
								
								src/DjConfig.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,250 @@
 | 
			
		||||
//
 | 
			
		||||
//    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::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);
 | 
			
		||||
    configItems[index].value = String(value);
 | 
			
		||||
    configItems[index].changed = true;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
int Config::getInt(String key)
 | 
			
		||||
{
 | 
			
		||||
  return this->getString(key).toInt();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Config::setInt(String parameter, int value) {
 | 
			
		||||
  this->setString(parameter, String(value));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
boolean Config::getBoolean(String key)
 | 
			
		||||
{
 | 
			
		||||
  String sval = getString(key);
 | 
			
		||||
  boolean bval = false;
 | 
			
		||||
 | 
			
		||||
  if (sval.length() > 0) {
 | 
			
		||||
    if (sval.equals("true") || sval.equals("TRUE")) {
 | 
			
		||||
      bval = true;
 | 
			
		||||
    } else if (sval.equals("false") || sval.equals("FALSE")) {
 | 
			
		||||
      bval = false;
 | 
			
		||||
    } else {
 | 
			
		||||
      int ival = sval.toInt();
 | 
			
		||||
      if (ival) {
 | 
			
		||||
        bval = true;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return bval;
 | 
			
		||||
  } else {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Config::setBoolean(String parameter, boolean value) {
 | 
			
		||||
  this->setString(parameter, value ? "true" : "false");
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								src/DjConfig.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/DjConfig.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
//
 | 
			
		||||
//    FILE: Config.h
 | 
			
		||||
// VERSION: 0.1
 | 
			
		||||
// PURPOSE: Configuration of controller, wifi and application basics
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef lightningConfigLoaded
 | 
			
		||||
#define lightningConfigLoaded true
 | 
			
		||||
 | 
			
		||||
#include <Arduino.h>
 | 
			
		||||
#include "DjDebug.h"
 | 
			
		||||
 | 
			
		||||
#define MAX_NUMBER_OF_CONFIG_ITEMS 50
 | 
			
		||||
 | 
			
		||||
class Config {
 | 
			
		||||
public:
 | 
			
		||||
  Config(Debug& _debug):debug(_debug) { debug.outln("Config constructor", DEBUG_MAX_INFO); logHeap(); };
 | 
			
		||||
  void begin(const char * filename, const char * const *configItemDescriptions, unsigned int numberOfConfigItems);
 | 
			
		||||
  void loadFile(const char *filename, const char * const configDescriptions[], int numConfigs);
 | 
			
		||||
  int getInt(String parameter);
 | 
			
		||||
  String getString(String parameter);
 | 
			
		||||
  const char * getCString(String parameter);
 | 
			
		||||
  boolean getBoolean(String parameter);
 | 
			
		||||
  void setString(String parameter, String value);
 | 
			
		||||
  void setInt(String parameter, int value);
 | 
			
		||||
  void setBoolean(String parameter, boolean value);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
  String _filename = "";
 | 
			
		||||
  Debug& debug;
 | 
			
		||||
  void writeConfigFile(String filename);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										176
									
								
								src/DjDebug.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								src/DjDebug.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,176 @@
 | 
			
		||||
#include <Arduino.h>
 | 
			
		||||
#include "DjDebug.h"
 | 
			
		||||
 | 
			
		||||
/*****************************************************************/
 | 
			
		||||
/* Debug output                                                  */
 | 
			
		||||
/*****************************************************************/
 | 
			
		||||
 | 
			
		||||
int Debug::outputUptoLevel = DEBUG_MAX_INFO;
 | 
			
		||||
int Debug::defaultDebugLevel = DEBUG_MIN_INFO;
 | 
			
		||||
 | 
			
		||||
Debug::Debug() {
 | 
			
		||||
  Debug(115200, "Serial");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Debug::Debug(int baudRate) {
 | 
			
		||||
  Debug(baudRate, "Serial");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Debug::Debug(const char * outputChannel) {
 | 
			
		||||
  Debug(115200, outputChannel);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Debug::Debug(int baudRate, const char * outputChannel)
 | 
			
		||||
{
 | 
			
		||||
  Serial.begin(baudRate);
 | 
			
		||||
  Serial.println(F("\n"));
 | 
			
		||||
  Serial.println(F("=== Debug output starts"));
 | 
			
		||||
  Serial.print(F("Output to: "));
 | 
			
		||||
  Serial.print(outputChannel);
 | 
			
		||||
  Serial.print(", speed ");
 | 
			
		||||
  Serial.println(baudRate);
 | 
			
		||||
  logHeap();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Debug::setOutputUptoLevel(int level)
 | 
			
		||||
{
 | 
			
		||||
  outputUptoLevel = level;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Debug::setDefaultDebugLevel(int level)
 | 
			
		||||
{
 | 
			
		||||
  defaultDebugLevel = level;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Debug::heapLogger(const char *fileName, const int line, const char *functionName)
 | 
			
		||||
{
 | 
			
		||||
  out(F("> Heap: "));
 | 
			
		||||
  out(ESP.getFreeHeap());
 | 
			
		||||
  out(F(" - "));
 | 
			
		||||
  // search filename beginning without path, look for slash character
 | 
			
		||||
  const char *slash = fileName + strlen(fileName);
 | 
			
		||||
  while (*slash != '/' && slash > fileName)
 | 
			
		||||
    --slash;
 | 
			
		||||
  out(slash);
 | 
			
		||||
  out(F(": "));
 | 
			
		||||
  outln(line);
 | 
			
		||||
  #if 0
 | 
			
		||||
  if (functionName) {
 | 
			
		||||
    out(F(" in "));
 | 
			
		||||
    outln(functionName);
 | 
			
		||||
  }
 | 
			
		||||
  #endif
 | 
			
		||||
  delay(10);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Debug::out(const String& text, const int level)
 | 
			
		||||
{
 | 
			
		||||
  if (level <= outputUptoLevel) {
 | 
			
		||||
    Serial.print(text);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Debug::out(int number)
 | 
			
		||||
{
 | 
			
		||||
  if (defaultDebugLevel <= outputUptoLevel) {
 | 
			
		||||
    Serial.print(number);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Debug::outln(int number)
 | 
			
		||||
{
 | 
			
		||||
  if (defaultDebugLevel <= outputUptoLevel) {
 | 
			
		||||
    Serial.println(number);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Debug::out(int number, int level)
 | 
			
		||||
{
 | 
			
		||||
  if (level <= outputUptoLevel) {
 | 
			
		||||
    Serial.print(number);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Debug::outln(int number, int level)
 | 
			
		||||
{
 | 
			
		||||
  if (level <= outputUptoLevel) {
 | 
			
		||||
    Serial.println(number);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Debug::out(const char * text)
 | 
			
		||||
{
 | 
			
		||||
  if (defaultDebugLevel <= outputUptoLevel) {
 | 
			
		||||
    Serial.print(text);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Debug::out_p(PGM_P text)
 | 
			
		||||
{
 | 
			
		||||
  if (defaultDebugLevel <= outputUptoLevel) {
 | 
			
		||||
    Serial.print(text);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Debug::out(const char * text, const int level)
 | 
			
		||||
{
 | 
			
		||||
  if (level <= outputUptoLevel) {
 | 
			
		||||
    Serial.print(text);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Debug::out_p(PGM_P text, const int level)
 | 
			
		||||
{
 | 
			
		||||
  if (level <= outputUptoLevel) {
 | 
			
		||||
    Serial.print(text);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Debug::out(const String& text)
 | 
			
		||||
{
 | 
			
		||||
  if (defaultDebugLevel <= outputUptoLevel) {
 | 
			
		||||
    Serial.print(text);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Debug::outln(const String& text, const int level)
 | 
			
		||||
{
 | 
			
		||||
  if (level <= outputUptoLevel) {
 | 
			
		||||
    Serial.println(text);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Debug::outln(const String& text)
 | 
			
		||||
{
 | 
			
		||||
  if (defaultDebugLevel <= outputUptoLevel) {
 | 
			
		||||
    Serial.println(text);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Debug::outln(const char * text, const int level)
 | 
			
		||||
{
 | 
			
		||||
  if (level <= outputUptoLevel) {
 | 
			
		||||
    Serial.println(text);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Debug::outln_p(PGM_P text, const int level)
 | 
			
		||||
{
 | 
			
		||||
  if (level <= outputUptoLevel) {
 | 
			
		||||
    Serial.println(text);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Debug::outln(const char * text)
 | 
			
		||||
{
 | 
			
		||||
  if (defaultDebugLevel <= outputUptoLevel) {
 | 
			
		||||
    Serial.println(text);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Debug::outln_p(PGM_P text)
 | 
			
		||||
{
 | 
			
		||||
  if (defaultDebugLevel <= outputUptoLevel) {
 | 
			
		||||
    Serial.println(text);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										52
									
								
								src/DjDebug.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/DjDebug.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
 | 
			
		||||
#ifndef debug_h
 | 
			
		||||
#define debug_h
 | 
			
		||||
 | 
			
		||||
#include <Arduino.h>
 | 
			
		||||
#include <pgmspace.h>
 | 
			
		||||
 | 
			
		||||
#define logHeap() Debug::heapLogger(__FILE__,__LINE__,__func__)
 | 
			
		||||
 | 
			
		||||
class Debug {
 | 
			
		||||
  public:
 | 
			
		||||
    Debug();
 | 
			
		||||
    Debug(int baudRate);
 | 
			
		||||
  	Debug(const char * outputChannel);
 | 
			
		||||
    Debug(int baudRate, const char * outputChannel);
 | 
			
		||||
    static void heapLogger(const char *fileName, const int line, const char *functionName);
 | 
			
		||||
    static void out(int number);
 | 
			
		||||
    static void outln(int number);
 | 
			
		||||
    static void out(int number, int level);
 | 
			
		||||
    static void outln(int number, int level);
 | 
			
		||||
  	static void out(const String& text, const int level);
 | 
			
		||||
  	static void out(const String& text);
 | 
			
		||||
  	static void outln(const String& text, const int level);
 | 
			
		||||
  	static void outln(const String& text);
 | 
			
		||||
    static void out(const char * text, const int level);
 | 
			
		||||
    static void out(const char * text);
 | 
			
		||||
    static void outln(const char * text, const int level);
 | 
			
		||||
    static void outln(const char * text);
 | 
			
		||||
    static void out_p(PGM_P text, const int level);
 | 
			
		||||
    static void out_p(PGM_P text);
 | 
			
		||||
    static void outln_p(PGM_P text, const int level);
 | 
			
		||||
    static void outln_p(PGM_P text);
 | 
			
		||||
  	static void setOutputUptoLevel(int level);
 | 
			
		||||
  	static void setDefaultDebugLevel(int level);
 | 
			
		||||
  
 | 
			
		||||
  private:
 | 
			
		||||
  	static int outputUptoLevel;
 | 
			
		||||
  	static int defaultDebugLevel;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Wieviele Informationen sollen über die serielle Schnittstelle ausgegeben werden?
 | 
			
		||||
#define DEBUG 3
 | 
			
		||||
 | 
			
		||||
// Definition der Debuglevel
 | 
			
		||||
#define DEBUG_ERROR 1
 | 
			
		||||
#define DEBUG_WARNING 2
 | 
			
		||||
#define DEBUG_MIN_INFO 3
 | 
			
		||||
#define DEBUG_MED_INFO 4
 | 
			
		||||
#define DEBUG_MAX_INFO 5
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										31
									
								
								src/DjFastclockScanner.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/DjFastclockScanner.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
//
 | 
			
		||||
//    FILE: DjFastclockScanner.h
 | 
			
		||||
// VERSION: 1.0
 | 
			
		||||
// PURPOSE: Scans the broadcasts for clocks and returns the clocknames found
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include <DjFastclockScanner.h>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
String FastclockScanner::knownClocks[MAX_CLOCKS];
 | 
			
		||||
int FastclockScanner::numberOfKnownClocks = 0;
 | 
			
		||||
 | 
			
		||||
void FastclockScanner::addClock(String clockName) {
 | 
			
		||||
  for (int i=0; i<numberOfKnownClocks; ++i) {
 | 
			
		||||
    if (clockName.equals(knownClocks[i])) return;
 | 
			
		||||
  }
 | 
			
		||||
  if (numberOfKnownClocks < MAX_CLOCKS) {
 | 
			
		||||
    knownClocks[numberOfKnownClocks] = String(clockName);
 | 
			
		||||
    ++numberOfKnownClocks;
 | 
			
		||||
    debug.out(F("Added new clock with name=")); debug.outln(clockName);
 | 
			
		||||
  }
 | 
			
		||||
  logHeap();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FastclockScanner::addClock(const char * clockName) {
 | 
			
		||||
  addClock(String(clockName));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										25
									
								
								src/DjFastclockScanner.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/DjFastclockScanner.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
/* DjFastclockScanner -- collect fastclock server names
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef _DjFastclockScannerIncluded
 | 
			
		||||
#define _DjFastclockScannerIncluded true
 | 
			
		||||
 | 
			
		||||
#include <Arduino.h>
 | 
			
		||||
#include "DjDebug.h"
 | 
			
		||||
 | 
			
		||||
#define MAX_CLOCKS 10
 | 
			
		||||
 | 
			
		||||
class FastclockScanner {
 | 
			
		||||
	public:
 | 
			
		||||
		FastclockScanner(Debug& _debug):debug(_debug) {};
 | 
			
		||||
		void addClock(String clockName);
 | 
			
		||||
		void addClock(const char * clockName);
 | 
			
		||||
		String *getKnownClocks() { return knownClocks; }
 | 
			
		||||
		int getNumberOfKnownClocks() { return numberOfKnownClocks; }
 | 
			
		||||
	private:
 | 
			
		||||
		Debug& debug;
 | 
			
		||||
		static String knownClocks[];
 | 
			
		||||
		static int numberOfKnownClocks;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										79
									
								
								src/DjSimpleFS.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/DjSimpleFS.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
			
		||||
//
 | 
			
		||||
//    FILE: DjSimpleFS.cpp
 | 
			
		||||
// VERSION: 1.0
 | 
			
		||||
// PURPOSE: File system
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include <DjSimpleFS.h>
 | 
			
		||||
 | 
			
		||||
boolean SimpleFS::_initialized = false;
 | 
			
		||||
FSInfo SimpleFS::fsInfo;
 | 
			
		||||
 | 
			
		||||
//format bytes
 | 
			
		||||
String SimpleFS::formatBytes(size_t bytes) {
 | 
			
		||||
  if (bytes < 1024){
 | 
			
		||||
    return String(bytes)+"B";
 | 
			
		||||
  } else if(bytes < (1024 * 1024)){
 | 
			
		||||
    return String(bytes/1024.0)+"KB";
 | 
			
		||||
  } else if(bytes < (1024 * 1024 * 1024)){
 | 
			
		||||
    return String(bytes/1024.0/1024.0)+"MB";
 | 
			
		||||
  } else {
 | 
			
		||||
    return String(bytes/1024.0/1024.0/1024.0)+"GB";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void SimpleFS::print_filesystem_info()
 | 
			
		||||
{
 | 
			
		||||
  debug.outln(F("---------------"));
 | 
			
		||||
  debug.outln(F("Filesystem Info"));
 | 
			
		||||
  debug.outln(F("---------------"));
 | 
			
		||||
  debug.out(F("Filesystem capacity: ")); debug.outln(formatBytes(fsInfo.totalBytes));
 | 
			
		||||
  debug.out(F("Used capacity: ")); debug.outln(formatBytes(fsInfo.usedBytes));
 | 
			
		||||
  debug.out(F("maxOpenFiles: ")); debug.outln(fsInfo.maxOpenFiles);
 | 
			
		||||
  debug.outln(F("File system directory:"));
 | 
			
		||||
  debug.outln(F("----------------------"));
 | 
			
		||||
  Dir dir = SPIFFS.openDir("/");
 | 
			
		||||
  while (dir.next()) {
 | 
			
		||||
    String fileName = dir.fileName();
 | 
			
		||||
    size_t fileSize = dir.fileSize();
 | 
			
		||||
    debug.out(F("FS File: ")); debug.out(fileName.c_str());
 | 
			
		||||
    debug.out(F(", size: ")); debug.outln(formatBytes(fileSize).c_str());
 | 
			
		||||
  }
 | 
			
		||||
  debug.outln(F("__________________"));
 | 
			
		||||
  delay(10);
 | 
			
		||||
  logHeap();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SimpleFS::begin() {
 | 
			
		||||
  if (_initialized) return;
 | 
			
		||||
 | 
			
		||||
  if (SPIFFS.begin() == false) {
 | 
			
		||||
    debug.outln(F("Formatting file system ..."));
 | 
			
		||||
    SPIFFS.format();
 | 
			
		||||
  }
 | 
			
		||||
  SPIFFS.info(fsInfo);
 | 
			
		||||
  print_filesystem_info();
 | 
			
		||||
  _initialized = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static size_t minFreeSpace = 1024 * 1024 * 1024;
 | 
			
		||||
 | 
			
		||||
void SimpleFS::loop() {
 | 
			
		||||
  SPIFFS.info(fsInfo);
 | 
			
		||||
  size_t freeSpace = fsInfo.totalBytes - fsInfo.usedBytes;
 | 
			
		||||
 | 
			
		||||
  if (freeSpace < minFreeSpace) {
 | 
			
		||||
    minFreeSpace = freeSpace;
 | 
			
		||||
    debug.out(F("New minFreeSpace=")); debug.outln(formatBytes(minFreeSpace));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t SimpleFS::getMaxPathLength() {
 | 
			
		||||
  if (!_initialized) {
 | 
			
		||||
    return -1; // ERROR case
 | 
			
		||||
  }
 | 
			
		||||
  return fsInfo.maxPathLength;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										31
									
								
								src/DjSimpleFS.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/DjSimpleFS.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
//
 | 
			
		||||
//    FILE: DjSimpleFS.h
 | 
			
		||||
// VERSION: 1.0
 | 
			
		||||
// PURPOSE: File system
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef _djSimpleFSLoaded
 | 
			
		||||
#define _djSimpleFSLoaded true
 | 
			
		||||
 | 
			
		||||
#include <Arduino.h>
 | 
			
		||||
#include <FS.h>
 | 
			
		||||
#include "DjDebug.h"
 | 
			
		||||
 | 
			
		||||
class SimpleFS {
 | 
			
		||||
  public:
 | 
			
		||||
    SimpleFS(Debug& _debug):debug(_debug) { logHeap(); };
 | 
			
		||||
    void begin();
 | 
			
		||||
    void loop();
 | 
			
		||||
    boolean initialized() { return _initialized; };
 | 
			
		||||
    static size_t getMaxPathLength();
 | 
			
		||||
    void print_filesystem_info();
 | 
			
		||||
 | 
			
		||||
  private:
 | 
			
		||||
    Debug& debug;
 | 
			
		||||
    String formatBytes(size_t bytes);
 | 
			
		||||
    static boolean _initialized;
 | 
			
		||||
    static FSInfo fsInfo;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
@@ -2,6 +2,8 @@
 | 
			
		||||
#define sevenSegmentClock_h_included
 | 
			
		||||
 | 
			
		||||
#include <Adafruit_NeoPixel.h>
 | 
			
		||||
#include "DjDebug.h"
 | 
			
		||||
#include "DjConfig.h"
 | 
			
		||||
 | 
			
		||||
// avoid flickering of the display:
 | 
			
		||||
#define TIME_BETWEEN_DISPLAY_UPDATES_ms 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;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										273
									
								
								src/main.cpp
									
									
									
									
									
								
							
							
						
						
									
										273
									
								
								src/main.cpp
									
									
									
									
									
								
							@@ -11,7 +11,11 @@
 | 
			
		||||
 | 
			
		||||
#include <ArduinoJson.h>
 | 
			
		||||
#include <SPI.h>
 | 
			
		||||
#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<sizeof(colorSelection); ++i) {
 | 
			
		||||
    if (color == colorSelection[i].id) {
 | 
			
		||||
@@ -83,6 +85,7 @@ static const SevenSegmentClock::Color getColorHandle(uint8_t id) {
 | 
			
		||||
  }
 | 
			
		||||
  return SevenSegmentClock::Green; // default
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
static const SevenSegmentClock::Color getColorHandleByName(String name) {
 | 
			
		||||
  for (unsigned int i=0; i<sizeof(colorSelection); ++i) {
 | 
			
		||||
@@ -93,22 +96,13 @@ static const SevenSegmentClock::Color getColorHandleByName(String name) {
 | 
			
		||||
  return SevenSegmentClock::Green; // default
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define DEFAULT_COLOR SevenSegmentClock::Green
 | 
			
		||||
#define DEFAULT_BRIGHTNESS 31
 | 
			
		||||
 | 
			
		||||
char clockName[MAX_CLOCK_NAME_LEN+1] = DEFAULT_CLOCK_NAME;
 | 
			
		||||
char clockChannelString[MAX_CLOCK_CHANNEL_STRING_LEN+1] = DEFAULT_CLOCK_CHANNEL_STRING;
 | 
			
		||||
uint8_t clockChannel = DEFAULT_CLOCK_CHANNEL;
 | 
			
		||||
SevenSegmentClock::Color clockColor = DEFAULT_COLOR;
 | 
			
		||||
//uint8_t brightness = DEFAULT_BRIGHTNESS;
 | 
			
		||||
int utcTimeOffsetMinutes = 120; // default UTC+2h
 | 
			
		||||
 | 
			
		||||
//flag for saving data
 | 
			
		||||
bool shouldSaveConfig = false;
 | 
			
		||||
 | 
			
		||||
//callback notifying us of the need to save config
 | 
			
		||||
void saveConfigCallback () {
 | 
			
		||||
  Serial.println("Should save config");
 | 
			
		||||
  debug.outln("Should save config", DEBUG_MAX_INFO);
 | 
			
		||||
  shouldSaveConfig = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -119,31 +113,15 @@ void setupWifiConnection() {
 | 
			
		||||
  wifiManager.setSaveConfigCallback(saveConfigCallback);
 | 
			
		||||
  wifiManager.setConfigPortalTimeout(300);
 | 
			
		||||
 | 
			
		||||
  //set static ip
 | 
			
		||||
  IPAddress _ip,_gw,_sn;
 | 
			
		||||
  _ip.fromString(static_ip);
 | 
			
		||||
  _gw.fromString(static_gw);
 | 
			
		||||
  _sn.fromString(static_sn);
 | 
			
		||||
 | 
			
		||||
  //wifiManager.setSTAStaticIPConfig(_ip, _gw, _sn);
 | 
			
		||||
 | 
			
		||||
  //add all your parameters here
 | 
			
		||||
  //**wifiManager.addParameter(&custom_mqtt_server);
 | 
			
		||||
  //**wifiManager.addParameter(&custom_mqtt_port);
 | 
			
		||||
  //wifiManager.addParameter(&custom_blynk_token);
 | 
			
		||||
 | 
			
		||||
  //reset settings - for testing
 | 
			
		||||
  //wifiManager.resetSettings();
 | 
			
		||||
 | 
			
		||||
  //set minimu quality of signal so it ignores AP's under that quality
 | 
			
		||||
  //defaults to 8%
 | 
			
		||||
  wifiManager.setMinimumSignalQuality(15);
 | 
			
		||||
 | 
			
		||||
  Serial.println("Starting autoConnect ...");
 | 
			
		||||
  debug.outln("Starting autoConnect ...", DEBUG_MAX_INFO);
 | 
			
		||||
  //if (!wifiManager.autoConnect("FastclockClient7Seg", "password")) {
 | 
			
		||||
  //if (!wifiManager.autoConnect("fc7seg", "password")) {
 | 
			
		||||
  if (!wifiManager.autoConnect("fc7seg")) {
 | 
			
		||||
    Serial.println("failed to connect and hit timeout");
 | 
			
		||||
    debug.outln("failed to connect and hit timeout", DEBUG_WARNING);
 | 
			
		||||
    delay(3000);
 | 
			
		||||
    //reset and try again, or maybe put it to deep sleep
 | 
			
		||||
    ESP.reset();
 | 
			
		||||
@@ -151,23 +129,20 @@ void setupWifiConnection() {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  //if you get here you have connected to the WiFi
 | 
			
		||||
  Serial.println("connected...yeey :)");
 | 
			
		||||
  debug.outln("connected...yeey :)", DEBUG_MAX_INFO);
 | 
			
		||||
 | 
			
		||||
  //save the custom parameters to FS
 | 
			
		||||
  if (shouldSaveConfig) {
 | 
			
		||||
    Serial.println("saving config");
 | 
			
		||||
    DynamicJsonDocument config(2048);
 | 
			
		||||
    debug.outln("saving config ... NYI", DEBUG_MED_INFO);
 | 
			
		||||
    // DynamicJsonDocument config(2048);
 | 
			
		||||
    //JsonObject json = jsonBuffer.createObject();
 | 
			
		||||
    /*
 | 
			
		||||
    config["clock_name"] = clockName;
 | 
			
		||||
    config["clock_channel"] = clockChannelString;
 | 
			
		||||
    config["clock_color"] = clockColor;
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
    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");
 | 
			
		||||
@@ -177,38 +152,40 @@ void setupWifiConnection() {
 | 
			
		||||
    serializeJson(config, configFile);
 | 
			
		||||
    configFile.close();
 | 
			
		||||
    //end save
 | 
			
		||||
*/
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Serial.print("local ip: "); Serial.println(WiFi.localIP());
 | 
			
		||||
  Serial.print("gateway: "); Serial.println(WiFi.gatewayIP());
 | 
			
		||||
  Serial.print("subnet: "); Serial.println(WiFi.subnetMask());
 | 
			
		||||
  debug.out("local ip: ", DEBUG_MAX_INFO); debug.outln(WiFi.localIP(), DEBUG_MAX_INFO);
 | 
			
		||||
  debug.out("gateway: ", DEBUG_MAX_INFO); debug.outln(WiFi.gatewayIP(), DEBUG_MAX_INFO);
 | 
			
		||||
  debug.out("subnet: ", DEBUG_MAX_INFO); debug.outln(WiFi.subnetMask(), DEBUG_MAX_INFO);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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>";
 | 
			
		||||
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>";
 | 
			
		||||
const char _SCRIPT[] PROGMEM          = "<script>function c(l){document.getElementById('s').value=l.innerText||l.textContent;document.getElementById('p').focus();}</script>";
 | 
			
		||||
const char _HEAD_END[] PROGMEM        = "</head><body><div style='text-align:left;display:inline-block;min-width:260px;'>";
 | 
			
		||||
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>";
 | 
			
		||||
const char _ITEM[] PROGMEM            = "<div><a href='#p' onclick='c(this)'>{v}</a> <span class='q {i}'>{r}%</span></div>";
 | 
			
		||||
const char _FORM_START[] PROGMEM      = "<form method='get' action='configsave'>";
 | 
			
		||||
const char _FORM_CLOCKNAME[] PROGMEM  = "<label for='n'>Fastclock name</label><input id='n' name='n' length=32 placeholder='clock name'><br/>";
 | 
			
		||||
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>";
 | 
			
		||||
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>";
 | 
			
		||||
const char _SCRIPT[] PROGMEM            = "<script>function c(l){document.getElementById('s').value=l.innerText||l.textContent;document.getElementById('p').focus();}</script>";
 | 
			
		||||
const char _HEAD_END[] PROGMEM          = "</head><body><div style='text-align:left;display:inline-block;min-width:260px;'>";
 | 
			
		||||
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>";
 | 
			
		||||
const char _ITEM[] PROGMEM              = "<div><a href='#p' onclick='c(this)'>{v}</a> <span class='q {i}'>{r}%</span></div>";
 | 
			
		||||
const char _FORM_START[] PROGMEM        = "<form method='get' action='configsave'>";
 | 
			
		||||
const char _FORM_CLOCKNAME[] PROGMEM    = "<label for='n'>Fastclock name</label><input id='n' name='n' length=32 placeholder='clock name'><br/>";
 | 
			
		||||
const char _FORM_CLOCKMODE_HEADLINE[] PROGMEM = "<br/>Clock mode:<br/>";
 | 
			
		||||
const char _FORM_CLOCKMODE_DEMO[] PROGMEM = "<input class='r' id='md' name='m' type='radio' value='demo' {check}><label for='md'>Demo</label><br/>";
 | 
			
		||||
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/>";
 | 
			
		||||
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/>";
 | 
			
		||||
const char _FORM_UTC_OFFSET[] PROGMEM = "<label for='utc'>UTC offset (minutes)</label><input id='utc' name='utc' length=4 placeholder='120'><br/>";
 | 
			
		||||
const char _FORM_PARAM[] PROGMEM      = "<br/><input id='{i}' name='{n}' maxlength={l} placeholder='{p}' value='{v}' {c}>";
 | 
			
		||||
const char _FORM_UTC_OFFSET[] PROGMEM   = "<label for='utc'>UTC offset (minutes)</label><input id='utc' name='utc' length=4 placeholder='120'><br/>";
 | 
			
		||||
const char _FORM_PARAM[] PROGMEM        = "<br/><input id='{i}' name='{n}' maxlength={l} placeholder='{p}' value='{v}' {c}>";
 | 
			
		||||
const char _FORM_COLOR_HEADLINE[] PROGMEM = "<br/>Display color:<br/>";
 | 
			
		||||
const char _FORM_COLOR_BLUE[] PROGMEM = "<input class='r' id='cb' name='c' type='radio' value='blue' {check}><label for='cb'>Blue</label><br/>";
 | 
			
		||||
const char _FORM_COLOR_RED[] PROGMEM  = "<input class='r' id='cr' name='c' type='radio' value='red' {check}><label for='cr'>Red</label><br/>";
 | 
			
		||||
const char _FORM_COLOR_GREEN[] PROGMEM = "<input class='r' id='cg' name='c' type='radio' value='green' {check}><label for='cg'>Green</label><br/>";
 | 
			
		||||
const char _FORM_COLOR_WHITE[] PROGMEM = "<input class='r' id='cw' name='c' type='radio' value='white' {check}><label for='cw'>White</label><br/>";
 | 
			
		||||
const char _FORM_COLOR_BLUE[] PROGMEM   = "<input class='r' id='cb' name='c' type='radio' value='blue' {check}><label for='cb'>Blue</label><br/>";
 | 
			
		||||
const char _FORM_COLOR_RED[] PROGMEM    = "<input class='r' id='cr' name='c' type='radio' value='red' {check}><label for='cr'>Red</label><br/>";
 | 
			
		||||
const char _FORM_COLOR_GREEN[] PROGMEM  = "<input class='r' id='cg' name='c' type='radio' value='green' {check}><label for='cg'>Green</label><br/>";
 | 
			
		||||
const char _FORM_COLOR_WHITE[] PROGMEM  = "<input class='r' id='cw' name='c' type='radio' value='white' {check}><label for='cw'>White</label><br/>";
 | 
			
		||||
const char _FORM_COLOR_YELLOW[] PROGMEM = "<input class='r' id='cy' name='c' type='radio' value='yellow' {check}><label for='cy'>Yellow</label><br/>";
 | 
			
		||||
const char _FORM_BRIGHTNESS[] PROGMEM = "<br/><label for='b'>Brightness:</label><input id='b' name='b' type='range' min='10' max='255' value='{bright}'><br/>";
 | 
			
		||||
const char _FORM_END[] PROGMEM        = "<br/><button type='submit'>save</button></form>";
 | 
			
		||||
const char _SCAN_LINK[] PROGMEM       = "<br/><div class=\"c\"><a href=\"/wifi\">Scan</a></div>";
 | 
			
		||||
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>";
 | 
			
		||||
const char _END[] PROGMEM             = "</div></body></html>";
 | 
			
		||||
const char _FORM_BRIGHTNESS[] PROGMEM   = "<br/><label for='b'>Brightness:</label><input id='b' name='b' type='range' min='10' max='255' value='{bright}'><br/>";
 | 
			
		||||
const char _FORM_FASTCLOCK_INFO[] PROGMEM = "<br/><br/><div>Number of fastclocks found: {nfc}</div>";
 | 
			
		||||
const char _FORM_END[] PROGMEM          = "<br/><button type='submit'>save</button></form>";
 | 
			
		||||
const char _CONFIG_LINK[] PROGMEM       = "<br/><div class=\"c\"><a href=\"/config\">Configure</a></div>";
 | 
			
		||||
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>";
 | 
			
		||||
const char _END[] PROGMEM               = "</div></body></html>";
 | 
			
		||||
 | 
			
		||||
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("</h1>"));
 | 
			
		||||
 | 
			
		||||
  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; i<server->args(); ++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(".</div>");
 | 
			
		||||
  }
 | 
			
		||||
  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("<div>Set clock mode to ");
 | 
			
		||||
    page += server->arg("m");
 | 
			
		||||
    page += F(".</div>");
 | 
			
		||||
    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("<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";
 | 
			
		||||
      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.</div>");
 | 
			
		||||
  }
 | 
			
		||||
  page += String(F("<div>Configuration updated.</div>"));
 | 
			
		||||
  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();
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user