diff --git a/Latest/MorphingClock/Digit.cpp b/Latest/MorphingClock/Digit.cpp index 0ec3d4c..7bebd33 100644 --- a/Latest/MorphingClock/Digit.cpp +++ b/Latest/MorphingClock/Digit.cpp @@ -1,16 +1,12 @@ #include "Digit.h" -const byte sA = 0; -const byte sB = 1; -const byte sC = 2; -const byte sD = 3; -const byte sE = 4; -const byte sF = 5; -const byte sG = 6; -const int segHeight = 6; -const int segWidth = segHeight; -const uint16_t height = 31; -const uint16_t width = 63; +enum {sA, sB, sC, sD, sE, sF, sG}; + +#define segHeight 6 +#define segWidth segHeight +#define height 31u +#define width 63u +#define _black 0 byte digitBits[] = { B11111100, // 0 ABCDEF-- @@ -35,19 +31,25 @@ byte digitBits[] = { // B100100100 //}; -uint16_t black; - Digit::Digit(PxMATRIX* d, byte value, uint16_t xo, uint16_t yo, uint16_t color) { _display = d; _value = value; + _oldvalue = 10; + _morphcnt = 0; xOffset = xo; yOffset = yo; _color = color; } -byte Digit::Value() { - return _value; +void Digit::SetValue(byte value) { + _value = value; + _morphcnt = 0; +} + +void Digit::SetColor(uint16_t color) { + _color = color; } + void Digit::drawPixel(uint16_t x, uint16_t y, uint16_t c) { _display->drawPixel(xOffset + x, height - (y + yOffset), c); @@ -93,206 +95,238 @@ void Digit::Draw(byte value) { if (bitRead(pattern, 2)) drawSeg(sF); if (bitRead(pattern, 1)) drawSeg(sG); _value = value; + _oldvalue = value; + _morphcnt = segWidth + 2; } void Digit::Morph2() { // TWO - for (int i = 0; i <= segWidth; i++) + if (_morphcnt <= segWidth) { - if (i < segWidth) { + int i = _morphcnt; + if (i < segWidth) { drawPixel(segWidth - i, segHeight * 2 + 2, _color); drawPixel(segWidth - i, segHeight + 1, _color); drawPixel(segWidth - i, 0, _color); } - drawLine(segWidth + 1 - i, 1, segWidth + 1 - i, segHeight, black); + drawLine(segWidth + 1 - i, 1, segWidth + 1 - i, segHeight, _black); drawLine(segWidth - i, 1, segWidth - i, segHeight, _color); - delay(animSpeed); + _morphcnt++; } + else + _oldvalue = _value; } void Digit::Morph3() { // THREE - for (int i = 0; i <= segWidth; i++) + if (_morphcnt <= segWidth) { - drawLine(0 + i, 1, 0 + i, segHeight, black); + int i = _morphcnt; + drawLine(0 + i, 1, 0 + i, segHeight, _black); drawLine(1 + i, 1, 1 + i, segHeight, _color); - delay(animSpeed); + _morphcnt++; } + else + _oldvalue = _value; } void Digit::Morph4() { // FOUR - for (int i = 0; i < segWidth; i++) + if (_morphcnt < segWidth) { - drawPixel(segWidth - i, segHeight * 2 + 2, black); // Erase A + int i = _morphcnt; + drawPixel(segWidth - i, segHeight * 2 + 2, _black); // Erase A drawPixel(0, segHeight * 2 + 1 - i, _color); // Draw as F - drawPixel(1 + i, 0, black); // Erase D - delay(animSpeed); + drawPixel(1 + i, 0, _black); // Erase D + _morphcnt++; } + else + _oldvalue = _value; } void Digit::Morph5() { // FIVE - for (int i = 0; i < segWidth; i++) + if (_morphcnt < segWidth) { - drawPixel(segWidth + 1, segHeight + 2 + i, black); // Erase B + int i = _morphcnt; + drawPixel(segWidth + 1, segHeight + 2 + i, _black); // Erase B drawPixel(segWidth - i, segHeight * 2 + 2, _color); // Draw as A drawPixel(segWidth - i, 0, _color); // Draw D - delay(animSpeed); + _morphcnt++; } + else + _oldvalue = _value; } void Digit::Morph6() { // SIX - for (int i = 0; i <= segWidth; i++) + if (_morphcnt <= segWidth) { + int i = _morphcnt; // Move C right to left drawLine(segWidth - i, 1, segWidth - i, segHeight, _color); - if (i > 0) drawLine(segWidth - i + 1, 1, segWidth - i + 1, segHeight, black); - delay(animSpeed); + if (i > 0) drawLine(segWidth - i + 1, 1, segWidth - i + 1, segHeight, _black); + _morphcnt++; } + else + _oldvalue = _value; } void Digit::Morph7() { // SEVEN - for (int i = 0; i <= (segWidth + 1); i++) + if (_morphcnt <= (segWidth + 1)) { + int i = _morphcnt; // Move E left to right - drawLine(0 + i - 1, 1, 0 + i - 1, segHeight, black); + drawLine(0 + i - 1, 1, 0 + i - 1, segHeight, _black); drawLine(0 + i, 1, 0 + i, segHeight, _color); // Move F left to right - drawLine(0 + i - 1, segHeight * 2 + 1, 0 + i - 1, segHeight + 2, black); + drawLine(0 + i - 1, segHeight * 2 + 1, 0 + i - 1, segHeight + 2, _black); drawLine(0 + i, segHeight * 2 + 1, 0 + i, segHeight + 2, _color); // Erase D and G gradually - drawPixel(1 + i, 0, black); // D - drawPixel(1 + i, segHeight + 1, black); // G - delay(animSpeed); + drawPixel(1 + i, 0, _black); // D + drawPixel(1 + i, segHeight + 1, _black); // G + _morphcnt++; } + else + _oldvalue = _value; } void Digit::Morph8() { // EIGHT - for (int i = 0; i <= segWidth; i++) + if (_morphcnt <= segWidth) { + int i = _morphcnt; // Move B right to left drawLine(segWidth - i, segHeight * 2 + 1, segWidth - i, segHeight + 2, _color); - if (i > 0) drawLine(segWidth - i + 1, segHeight * 2 + 1, segWidth - i + 1, segHeight + 2, black); + if (i > 0) drawLine(segWidth - i + 1, segHeight * 2 + 1, segWidth - i + 1, segHeight + 2, _black); // Move C right to left drawLine(segWidth - i, 1, segWidth - i, segHeight, _color); - if (i > 0) drawLine(segWidth - i + 1, 1, segWidth - i + 1, segHeight, black); + if (i > 0) drawLine(segWidth - i + 1, 1, segWidth - i + 1, segHeight, _black); // Gradually draw D and G if (i < segWidth) { drawPixel(segWidth - i, 0, _color); // D drawPixel(segWidth - i, segHeight + 1, _color); // G } - delay(animSpeed); + _morphcnt++; } + else + _oldvalue = _value; } void Digit::Morph9() { // NINE - for (int i = 0; i <= (segWidth + 1); i++) + if (_morphcnt <= (segWidth + 1)) { + int i = _morphcnt; // Move E left to right - drawLine(0 + i - 1, 1, 0 + i - 1, segHeight, black); + drawLine(0 + i - 1, 1, 0 + i - 1, segHeight, _black); drawLine(0 + i, 1, 0 + i, segHeight, _color); - delay(animSpeed); + _morphcnt++; } + else + _oldvalue = _value; } void Digit::Morph0() { // ZERO - for (int i = 0; i <= segWidth; i++) + if (_morphcnt <= segWidth) { - if (_value==1) { // If 1 to 0, slide B to F and E to C + int i = _morphcnt; + if (_oldvalue==1) { // If 1 to 0, slide B to F and E to C // slide B to F drawLine(segWidth - i, segHeight * 2+1 , segWidth - i, segHeight + 2, _color); - if (i > 0) drawLine(segWidth - i + 1, segHeight * 2+1, segWidth - i + 1, segHeight + 2, black); + if (i > 0) drawLine(segWidth - i + 1, segHeight * 2+1, segWidth - i + 1, segHeight + 2, _black); // slide E to C drawLine(segWidth - i, 1, segWidth - i, segHeight, _color); - if (i > 0) drawLine(segWidth - i + 1, 1, segWidth - i + 1, segHeight, black); + if (i > 0) drawLine(segWidth - i + 1, 1, segWidth - i + 1, segHeight, _black); if (i 0) drawLine(segWidth - i + 1, segHeight * 2+1, segWidth - i + 1, segHeight + 2, black); + if (i > 0) drawLine(segWidth - i + 1, segHeight * 2+1, segWidth - i + 1, segHeight + 2, _black); - drawPixel(1+i, segHeight + 1, black); // Erase G left to right + drawPixel(1+i, segHeight + 1, _black); // Erase G left to right if (i 0) drawLine(segWidth - i + 1, segHeight * 2+1, segWidth - i + 1, segHeight + 2, black); + if (i > 0) drawLine(segWidth - i + 1, segHeight * 2+1, segWidth - i + 1, segHeight + 2, _black); // Move C to E drawLine(segWidth - i, 1, segWidth - i, segHeight, _color); - if (i > 0) drawLine(segWidth - i + 1, 1, segWidth - i + 1, segHeight, black); + if (i > 0) drawLine(segWidth - i + 1, 1, segWidth - i + 1, segHeight, _black); // Erase G from right to left - drawPixel(segWidth - i, segHeight + 1, black); // G + drawPixel(segWidth - i, segHeight + 1, _black); // G } - if (_value==5) { // If 5 to 0, we also need to slide F to B + if (_oldvalue==5) { // If 5 to 0, we also need to slide F to B if (i0) drawLine(1 + i, segHeight * 2 + 1, 1 + i, segHeight + 2, black); + if (i>0) drawLine(1 + i, segHeight * 2 + 1, 1 + i, segHeight + 2, _black); drawLine(2 + i, segHeight * 2 + 1, 2 + i, segHeight + 2, _color); } } - if (_value==5 || _value==9) { // If 9 or 5 to 0, Flow G into E - if (i @@ -34,118 +37,517 @@ Ticker display_ticker; #define P_D 12 #define P_E 0 #define P_OE 2 - #endif -// Pins for LED MATRIX -PxMATRIX display(64, 32, P_LAT, P_OE, P_A, P_B, P_C, P_D, P_E); +// Pins for LED MATRIX 1/16 +PxMATRIX display(64, 32, P_LAT, P_OE, P_A, P_B, P_C, P_D); + +// digit color (for ESP8266 only pure red, green or blue will work, dimmed colors will flicker) +uint16_t COL_ACT = display.color565(0, 255, 0); // green + +// Pins for LED MATRIX 1/32 +//PxMATRIX display(64, 32, P_LAT, P_OE, P_A, P_B, P_C, P_D, P_E); + +// the web-server-object +#include +ESP8266WebServer server(80); //instantiate server at port 80 (http port) + +// structure holds the parameters to save nonvolatile memory +#include +#define EEPROM_SIZE 128 // size of NV-memory +typedef struct +{ + unsigned char h24; + unsigned char fade; + unsigned char r, g, b; + unsigned char brightness; + unsigned char timezone[32]; + unsigned char nm_start; + unsigned char nm_end; + unsigned char nm_brightness; + unsigned char valid; +} mclock_struct; +mclock_struct mclock; +mclock_struct *mclockp = &mclock; +unsigned char *eptr; +uint16_t brightness; +bool nightmode; + +char outstr[512] = ""; +// string to hold the last displayed word-sequence +char tstr1[128], tstr2[128] = ""; + +#ifdef ESP8266 +#define refreshRate 0.020 // higher allows more time for WiFi, but makes the display dimmer. Originally 0.002 +#define persistenceMicroSeconds 200 // Higher = brighter. Originally 70 +#else +#define refreshRate 0.002 // higher allows more time for WiFi, but makes the display dimmer. Originally 0.002 +#define persistenceMicroSeconds 70 // Higher = brighter. Originally 70 +#endif #ifdef ESP8266 // ISR for display refresh void display_updater() { //display.displayTestPattern(70); - display.display(70); + display.display(persistenceMicroSeconds); // How many microseconds to enable the display } #endif -#ifdef ESP32 -void IRAM_ATTR display_updater() { - // Increment the counter and set the time of ISR - portENTER_CRITICAL_ISR(&timerMux); - //isplay.display(70); - display.displayTestPattern(70); - portEXIT_CRITICAL_ISR(&timerMux); -} -#endif +//#ifdef ESP32 +//void IRAM_ATTR display_updater() { +// // Increment the counter and set the time of ISR +// portENTER_CRITICAL_ISR(&timerMux); +// //display.display(70); +// display.displayTestPattern(70); +// portEXIT_CRITICAL_ISR(&timerMux); +//} +//#endif //=== SEGMENTS === #include "Digit.h" -Digit digit0(&display, 0, 63 - 1 - 9*1, 8, display.color565(0, 0, 255)); -Digit digit1(&display, 0, 63 - 1 - 9*2, 8, display.color565(0, 0, 255)); -Digit digit2(&display, 0, 63 - 4 - 9*3, 8, display.color565(0, 0, 255)); -Digit digit3(&display, 0, 63 - 4 - 9*4, 8, display.color565(0, 0, 255)); -Digit digit4(&display, 0, 63 - 7 - 9*5, 8, display.color565(0, 0, 255)); -Digit digit5(&display, 0, 63 - 7 - 9*6, 8, display.color565(0, 0, 255)); +enum +{ + DIG_S0, DIG_S1, DIG_M0, DIG_M1, DIG_H0, DIG_H1, NUM_DIGITS +}; + +Digit digit0(&display, 0, 63 - 1 - 9 * 1, 8, COL_ACT); +Digit digit1(&display, 0, 63 - 1 - 9 * 2, 8, COL_ACT); +Digit digit2(&display, 0, 63 - 4 - 9 * 3, 8, COL_ACT); +Digit digit3(&display, 0, 63 - 4 - 9 * 4, 8, COL_ACT); +Digit digit4(&display, 0, 63 - 7 - 9 * 5, 8, COL_ACT); +Digit digit5(&display, 0, 63 - 7 - 9 * 6, 8, COL_ACT); +Digit *Digits[NUM_DIGITS] = +{ &digit0, &digit1, &digit2, &digit3, &digit4, &digit5 }; //=== CLOCK === -#include "NTPClient.h" -NTPClient ntpClient; -unsigned long prevEpoch; +#include "WTAClient.h" +WTAClient wtaClient; +volatile unsigned long prevEpoch; byte prevhh; byte prevmm; byte prevss; +void update_color(void) +{ + COL_ACT = display.color565((brightness * mclockp->r) / 40, (brightness * mclockp->g) / 40, (brightness * mclockp->b) / 40); + for (int i = 0; i < NUM_DIGITS; i++) + Digits[i]->SetColor(COL_ACT); + display.fillScreen(display.color565(0, 0, 0)); + Digits[DIG_S1]->DrawColon(COL_ACT); + Digits[DIG_M1]->DrawColon(COL_ACT); + prevEpoch = 0; +} + +// dynamic generation of the web-servers response +void page_out(void) +{ + if (strlen(outstr)) + { + server.sendContent(outstr); + *outstr = 0; + } + else + { + server.sendContent("MorphClock_Server\r\n

MorphingClock-Server

"); + server.sendContent(tstr2); + server.sendContent("

"); + server.sendContent(""); -void setup() { - Serial.begin(9600); +#ifdef ESP32 + server.sendContent(""); +#endif + server.sendContent(""); +#ifdef ESP32 + server.sendContent(""); +#endif + server.sendContent(""); + server.sendContent(""); +#ifdef ESP32 + server.sendContent(""); +#else + server.sendContent(""); + server.sendContent(""); + server.sendContent(""); +#endif + server.sendContent("
Hour mode h24) + server.sendContent(" checked"); + server.sendContent("> 24h
Timezonetimezone); + server.sendContent("\">
See List (\"ip\" means \"automatic\")

Brightnessbrightness)); + server.sendContent("\"> %
NightmodeBrightness
nm_brightness)); + server.sendContent("\"> %
Start
nm_start > 9) ? "" : "0") + String(mclockp->nm_start)); + server.sendContent("\"> h
End
nm_end > 9) ? "" : "0") + String(mclockp->nm_end)); + server.sendContent("\"> h

Fadingfade)); + server.sendContent("\"> ms
Redr)); + server.sendContent("\"> %
Greeng)); + server.sendContent("\"> %
Blueb)); + server.sendContent("\"> %
Redr) + server.sendContent(" checked"); + server.sendContent(">
Greeng) + server.sendContent(" checked"); + server.sendContent(">
Blueb) + server.sendContent(" checked"); + server.sendContent(">

\r\n"); + } +} + +void setup() +{ + Serial.begin(115200); display.begin(16); #ifdef ESP8266 - display_ticker.attach(0.002, display_updater); + //display_ticker.attach(0.002, display_updater); + // First parameter is how often the display should be refreshed. + // Originally 0.002 caused unreliable WiFi on some NodeMCU. + // Hari changed this to 0.02 so it refresh less frequently. + // This caused display to be dimmer, so I increased how long the display should be latched from 70 to 500 + display_ticker.attach(refreshRate, display_updater); #endif + //#ifdef ESP32 + // timer = timerBegin(0, 80, true); + // timerAttachInterrupt(timer, &display_updater, true); + // timerAlarmWrite(timer, 2000, true); + // timerAlarmEnable(timer); + //#endif + + // read stored parameters + EEPROM.begin(EEPROM_SIZE); + eptr = (unsigned char*) mclockp; + for (int i = 0; i < sizeof(mclock_struct); i++) + *(eptr++) = EEPROM.read(i); + + // EEPROM-parameters invalid, use default-values and store them + if (mclockp->valid != 0xA5) + { + mclockp->h24 = 1; + mclockp->fade = 30; + mclockp->r = 0; + mclockp->g = 100; + mclockp->b = 0; + strcpy((char*)&mclockp->timezone, "ip"); + mclockp->brightness = 100; + mclockp->nm_start = 0; + mclockp->nm_end = 0; #ifdef ESP32 - timer = timerBegin(0, 80, true); - timerAttachInterrupt(timer, &display_updater, true); - timerAlarmWrite(timer, 2000, true); - timerAlarmEnable(timer); + mclockp->nm_brightness = 50; +#else + mclockp->nm_brightness = 0; #endif + mclockp->valid = 0xA5; + + eptr = (unsigned char*) mclockp; + for (int i = 0; i < sizeof(mclock_struct); i++) + EEPROM.write(i, *(eptr++)); + EEPROM.commit(); + } - ntpClient.Setup(&display); + brightness = mclockp->brightness; + strcpy(timezone, (char*)&mclockp->timezone); + military = mclockp->h24; + wtaClient.Setup(&display); display.fillScreen(display.color565(0, 0, 0)); - digit1.DrawColon(display.color565(0, 0, 255)); - digit3.DrawColon(display.color565(0, 0, 255)); -} + update_color(); + + // parsing function for the commands of the web-server + server.on("/", []() + { + int i; + unsigned int j; + long pval; + + if (server.args()) + { + for (i = 0; i < server.args(); i++) + { + if (server.argName(i) == "SEND") + { + *outstr = 0; + mclockp->h24 = 0; + military = 0; +#ifndef ESP32 + mclockp->r = 0; + mclockp->g = 0; + mclockp->b = 0; +#endif + } + } + for (i = 0; i < server.args(); i++) + { + errno = 0; + if (server.argName(i) == "SAVE") + { + unsigned char *eptr = (unsigned char*)mclockp; + + for (j = 0; j < sizeof(mclock_struct); j++) + EEPROM.write(j, *(eptr++)); + EEPROM.commit(); + if (!server.arg(i).length()) + sprintf(outstr + strlen(outstr), "OK"); + } + else if (server.argName(i) == "HMOD24") + { + if (server.arg(i) == "on") + mclockp->h24 = 1; + else if (server.arg(i) == "off") + mclockp->h24 = 0; + else + sprintf(outstr + strlen(outstr), "HMOD24=%s\r\n", (mclockp->h24) ? "on" : "off"); + prevEpoch = 0; + if (military != mclockp->h24) + { + military = mclockp->h24; + wtaClient.GetCurrentTime(true); + } + } + else if (server.argName(i) == "TIMEZONE") + { + if (server.arg(i).length() && !errno) + { + strcpy((char*)&mclockp->timezone, server.arg(i).c_str()); + strcpy((char*)&timezone, (char*)&mclockp->timezone); + wtaClient.GetCurrentTime(true); + } + else + sprintf(outstr + strlen(outstr), "TIMEZONE=%s\r\n", mclockp->timezone); + } + else if (server.argName(i) == "FADE") + { + pval = strtol(server.arg(i).c_str(), NULL, 10); + if (server.arg(i).length() && !errno) + { + mclockp->fade = pval; + if (mclockp->fade > 120) + mclockp->fade = 120; + if (mclockp->fade < 1) + mclockp->fade = 1; + } + else + sprintf(outstr + strlen(outstr), "FADE=%d\r\n", mclockp->fade); + } +#ifdef ESP32 + else if (server.argName(i) == "BRIGHT") + { + pval = strtol(server.arg(i).c_str(), NULL, 10); + if (server.arg(i).length() && !errno) + { + mclockp->brightness = pval; + if (mclockp->brightness > 100) + mclockp->brightness = 100; + prevEpoch = 0; + } + else + sprintf(outstr + strlen(outstr), "BRIGHT=%d\r\n", mclockp->brightness); + } + else if (server.argName(i) == "NMBRIGHT") + { + pval = strtol(server.arg(i).c_str(), NULL, 10); + if (server.arg(i).length() && !errno) + { + mclockp->nnm_brightness = pval; + if (mclockp->nm_brightness > 100) + mclockp->nm_brightness = 100; + prevEpoch = 0; + } + else + sprintf(outstr + strlen(outstr), "NMBRIGHT=%d\r\n", mclockp->nm_brightness); + } + else if (server.argName(i) == "RED") + { + pval = strtol(server.arg(i).c_str(), NULL, 10); + if (server.arg(i).length() && !errno) + { + mclockp->r = pval; + if (mclockp->r > 100) + mclockp->r = 100; + } + else + sprintf(outstr + strlen(outstr), "RED=%d\r\n", mclockp->r); + } + else if (server.argName(i) == "GREEN") + { + pval = strtol(server.arg(i).c_str(), NULL, 10); + if (server.arg(i).length() && !errno) + { + mclockp->g = pval; + if (mclockp->g > 100) + mclockp->g = 100; + } + else + sprintf(outstr + strlen(outstr), "GREEN=%d\r\n", mclockp->g); + } + else if (server.argName(i) == "BLUE") + { + pval = strtol(server.arg(i).c_str(), NULL, 10); + if (server.arg(i).length() && !errno) + { + mclockp->b = pval; + if (mclockp->b > 100) + mclockp->b = 100; + } + else + sprintf(outstr + strlen(outstr), "BLUE=%d\r\n", mclockp->b); + } +#else + else if (server.argName(i) == "RED") + { + if (server.arg(i) == "on") + mclockp->r = 100; + else if (server.arg(i) == "off") + mclockp->r = 0; + else + sprintf(outstr + strlen(outstr), "RED=%s\r\n", (mclockp->r) ? "on" : "off"); + } + else if (server.argName(i) == "GREEN") + { + if (server.arg(i) == "on") + mclockp->g = 100; + else if (server.arg(i) == "off") + mclockp->g = 0; + else + sprintf(outstr + strlen(outstr), "GREEN=%s\r\n", (mclockp->g) ? "on" : "off"); + } + else if (server.argName(i) == "BLUE") + { + if (server.arg(i) == "on") + mclockp->b = 100; + else if (server.arg(i) == "off") + mclockp->b = 0; + else + sprintf(outstr + strlen(outstr), "BLUE=%s\r\n", (mclockp->b) ? "on" : "off"); + } +#endif + else if (server.argName(i) == "NMSTART") + { + pval = strtol(server.arg(i).c_str(), NULL, 10); + if (server.arg(i).length() && !errno) + { + mclockp->nm_start = abs(pval); + if (mclockp->nm_start > 23) + mclockp->nm_start = 0; + } + else + sprintf(outstr + strlen(outstr), "NMSTART=%d\r\n", mclockp->nm_start); + } + else if (server.argName(i) == "NMEND") + { + pval = strtol(server.arg(i).c_str(), NULL, 10); + if (server.arg(i).length() && !errno) + { + mclockp->nm_end = abs(pval); + if (mclockp->nm_end > 23) + mclockp->nm_end = 0; + } + else + sprintf(outstr + strlen(outstr), "NMEND=%d\r\n", mclockp->nm_end); + } + } + *tstr2 = 0; + } + page_out(); + update_color(); + }); + // start web-server + server.begin(); + Serial.println("Web server started!"); +} -void loop() { - unsigned long epoch = ntpClient.GetCurrentTime(); +void loop() +{ + int tbright = brightness; + unsigned long epoch = wtaClient.GetCurrentTime(false); //Serial.print("GetCurrentTime returned epoch = "); //Serial.println(epoch); - if (epoch != 0) ntpClient.PrintTime(); - - if (epoch != prevEpoch) { - int hh = ntpClient.GetHours(); - int mm = ntpClient.GetMinutes(); - int ss = ntpClient.GetSeconds(); - if (prevEpoch == 0) { // If we didn't have a previous time. Just draw it without morphing. - digit0.Draw(ss % 10); - digit1.Draw(ss / 10); - digit2.Draw(mm % 10); - digit3.Draw(mm / 10); - digit4.Draw(hh % 10); - digit5.Draw(hh / 10); + if (epoch != 0) + wtaClient.PrintTime(); + + server.handleClient(); // handle HTTP-requests + + if (epoch != prevEpoch) + { + int hh = wtaClient.GetHours(); + int mm = wtaClient.GetMinutes(); + int ss = wtaClient.GetSeconds(); + + if (mclockp->nm_start || mclockp->nm_end) + { + if (mclockp->nm_start > mclockp->nm_end) + { + tbright = ((hh >= mclockp->nm_start) || (hh < mclockp->nm_end)) ? mclockp->nm_brightness : mclockp->brightness; + } + else + { + tbright = ((hh >= mclockp->nm_start) && (hh < mclockp->nm_end)) ? mclockp->nm_brightness : mclockp->brightness; + } + } + else + tbright = mclockp->brightness; + + if (tbright != brightness) + { + brightness = tbright; + update_color(); + } + + if (prevEpoch == 0) + { // If we didn't have a previous time. Just draw it without morphing. + Digits[DIG_S0]->Draw(ss % 10); + Digits[DIG_S1]->Draw(ss / 10); + Digits[DIG_M0]->Draw(mm % 10); + Digits[DIG_M1]->Draw(mm / 10); + Digits[DIG_H0]->Draw(hh % 10); + Digits[DIG_H1]->Draw(hh / 10); } else { // epoch changes every miliseconds, we only want to draw when digits actually change. - if (ss!=prevss) { + if (ss != prevss) + { int s0 = ss % 10; int s1 = ss / 10; - if (s0!=digit0.Value()) digit0.Morph(s0); - if (s1!=digit1.Value()) digit1.Morph(s1); - //ntpClient.PrintTime(); + Digits[DIG_S0]->SetValue(s0); + Digits[DIG_S1]->SetValue(s1); prevss = ss; } - if (mm!=prevmm) { + if (mm != prevmm) + { int m0 = mm % 10; int m1 = mm / 10; - if (m0!=digit2.Value()) digit2.Morph(m0); - if (m1!=digit3.Value()) digit3.Morph(m1); + Digits[DIG_M0]->SetValue(m0); + Digits[DIG_M1]->SetValue(m1); prevmm = mm; } - - if (hh!=prevhh) { + + if (hh != prevhh) + { int h0 = hh % 10; int h1 = hh / 10; - if (h0!=digit4.Value()) digit4.Morph(h0); - if (h1!=digit5.Value()) digit5.Morph(h1); + Digits[DIG_H0]->SetValue(h0); + Digits[DIG_H1]->SetValue(h1); prevhh = hh; } } prevEpoch = epoch; } + + for (int i = 0; i < NUM_DIGITS; i++) + Digits[i]->Morph(); + delay(mclockp->fade); } diff --git a/Latest/MorphingClock/NTPClient.cpp b/Latest/MorphingClock/NTPClient.cpp deleted file mode 100644 index ce32b8a..0000000 --- a/Latest/MorphingClock/NTPClient.cpp +++ /dev/null @@ -1,370 +0,0 @@ -//=== WIFI MANAGER === -#include -#include -#include //https://github.com/tzapu/WiFiManager -char wifiManagerAPName[] = "MorphClk"; -char wifiManagerAPPassword[] = "HariFun"; - - -//== DOUBLE-RESET DETECTOR == -#include -#define DRD_TIMEOUT 10 // Second-reset must happen within 10 seconds of first reset to be considered a double-reset -#define DRD_ADDRESS 0 // RTC Memory Address for the DoubleResetDetector to use -DoubleResetDetector drd(DRD_TIMEOUT, DRD_ADDRESS); - -//== SAVING CONFIG == -#include "FS.h" -#include -bool shouldSaveConfig = false; // flag for saving data - -//callback notifying us of the need to save config -void saveConfigCallback () { - Serial.println("Should save config"); - shouldSaveConfig = true; -} - - -//=== NTP CLIENT === -#include -#include -#include "NTPClient.h" - -#define DEBUG 0 -const unsigned long askFrequency = 60*60*1000; // How frequent should we get current time? in miliseconds. 60minutes = 60*60s = 60*60*1000ms -unsigned long timeToAsk; -unsigned long timeToRead; -unsigned long lastEpoch; // We don't want to continually ask for epoch from time server, so this is the last epoch we received (could be up to an hour ago based on askFrequency) -unsigned long lastEpochTimeStamp; // What was millis() when asked server for Epoch we are currently using? -unsigned long nextEpochTimeStamp; // What was millis() when we asked server for the upcoming epoch -unsigned long currentTime; -char timezone[5] = ""; -char military[3] = ""; // 24 hour mode? Y/N - -const char* ntpServerName = "time.google.com"; // NTP google server -IPAddress timeServerIP; // time.nist.gov NTP server address -const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message -byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets -WiFiUDP udp; // A UDP instance to let us send and receive packets over UDP -unsigned int localPort = 2390; // local port to listen for UDP packets - -bool error_getTime = false; - -void configModeCallback (WiFiManager *myWiFiManager) { - Serial.println("Entered config mode"); - Serial.println(WiFi.softAPIP()); - - // You could indicate on your screen or by an LED you are in config mode here - - // We don't want the next time the boar resets to be considered a double reset - // so we remove the flag - drd.stop(); -} - - -bool loadConfig() { - File configFile = SPIFFS.open("/config.json", "r"); - if (!configFile) { - Serial.println("Failed to open config file"); - return false; - } - - size_t size = configFile.size(); - if (size > 1024) { - Serial.println("Config file size is too large"); - return false; - } - - // Allocate a buffer to store contents of the file. - std::unique_ptr buf(new char[size]); - - configFile.readBytes(buf.get(), size); - - StaticJsonBuffer<200> jsonBuffer; - JsonObject& json = jsonBuffer.parseObject(buf.get()); - - if (!json.success()) { - Serial.println("Failed to parse config file"); - return false; - } - - strcpy(timezone, json["timezone"]); - strcpy(military, json["military"]); - return true; -} - -bool saveConfig() { - StaticJsonBuffer<200> jsonBuffer; - JsonObject& json = jsonBuffer.createObject(); - json["timezone"] = timezone; - json["military"] = military; - - File configFile = SPIFFS.open("/config.json", "w"); - if (!configFile) { - Serial.println("Failed to open config file for writing"); - return false; - } - - Serial.print("timezone="); - Serial.println(timezone); - - Serial.print("military="); - Serial.println(military); - - json.printTo(configFile); - return true; -} - - -NTPClient::NTPClient() -{ -} - - -void NTPClient::Setup(PxMATRIX* d) -{ - //-- Config -- - if (!SPIFFS.begin()) { - Serial.println("Failed to mount FS"); - return; - } - loadConfig(); - - //-- Display -- - _display = d; - _display->fillScreen(_display->color565(0, 0, 0)); - _display->setTextColor(_display->color565(0, 0, 255)); - //_display->setFont(&FreeMono9pt7b); - //_display->setTextSize(1); - const byte row0 = 2+0*10; - const byte row1 = 2+1*10; - const byte row2 = 2+2*10; - - //-- WiFiManager -- - //Local intialization. Once its business is done, there is no need to keep it around - WiFiManager wifiManager; - wifiManager.setSaveConfigCallback(saveConfigCallback); - WiFiManagerParameter timeZoneParameter("timeZone", "Time Zone", timezone, 5); - wifiManager.addParameter(&timeZoneParameter); - WiFiManagerParameter militaryParameter("military", "24Hr", military, 3); - wifiManager.addParameter(&militaryParameter); - - //-- Double-Reset -- - if (drd.detectDoubleReset()) { - Serial.println("Double Reset Detected"); - digitalWrite(LED_BUILTIN, LOW); - - _display->setCursor(1, row0); _display->print("AP"); - _display->setCursor(1+10, row0); _display->print(":"); - _display->setCursor(1+10+5, row0); _display->print(wifiManagerAPName); - - _display->setCursor(1, row1); _display->print("Pw"); - _display->setCursor(1+10, row1); _display->print(":"); - _display->setCursor(1+10+5, row1); _display->print(wifiManagerAPPassword); - - _display->setCursor(1, row2); _display->print("192"); - _display->setCursor(1+3*6 -1, row2); _display->print(".168"); - _display->setCursor(1+3*6 -1 + 5+ 3*6, row2); _display->print(".4"); - _display->setCursor(1+3*6 -1 + 5+ 3*6 + 5 + 6, row2); _display->print(".1"); - - wifiManager.startConfigPortal(wifiManagerAPName, wifiManagerAPPassword); - - _display->fillScreen(_display->color565(0, 0, 0)); - } - else - { - Serial.println("No Double Reset Detected"); - digitalWrite(LED_BUILTIN, HIGH); - - _display->setCursor(2, row1); - _display->print("Connecting"); - - //fetches ssid and pass from eeprom and tries to connect - //if it does not connect it starts an access point with the specified name wifiManagerAPName - //and goes into a blocking loop awaiting configuration - wifiManager.autoConnect(wifiManagerAPName, wifiManagerAPPassword); - } - - //-- Status -- - _display->fillScreen(_display->color565(0, 0, 0)); - _display->setCursor(2, row0); - _display->print("Connected!"); - Serial.println("WiFi connected"); - - Serial.println("IP address: "); - Serial.println(WiFi.localIP()); - - Serial.println("Starting UDP"); - udp.begin(localPort); - Serial.print("Local port: "); - Serial.println(udp.localPort()); - - //-- Timezone -- - strcpy(timezone,timeZoneParameter.getValue()); - _display->setCursor(2, row1); - _display->print("Zone:"); - _display->print(timezone); - - //-- Military -- - strcpy(military,militaryParameter.getValue()); - _display->setCursor(2, row2); - _display->print("24Hr:"); - _display->print(military); - - if (shouldSaveConfig) { - saveConfig(); - } - drd.stop(); - - delay(3000); -} - - -// send an NTP request to the time server at the given address -unsigned long NTPClient::sendNTPpacket(IPAddress& address) -{ - if (DEBUG) Serial.println("sending NTP packet..."); - // set all bytes in the buffer to 0 - memset(packetBuffer, 0, NTP_PACKET_SIZE); - // Initialize values needed to form NTP request - // (see URL above for details on the packets) - packetBuffer[0] = 0b11100011; // LI, Version, Mode - packetBuffer[1] = 0; // Stratum, or type of clock - packetBuffer[2] = 6; // Polling Interval - packetBuffer[3] = 0xEC; // Peer Clock Precision - // 8 bytes of zero for Root Delay & Root Dispersion - packetBuffer[12] = 49; - packetBuffer[13] = 0x4E; - packetBuffer[14] = 49; - packetBuffer[15] = 52; - - // all NTP fields have been given values, now - // you can send a packet requesting a timestamp: - udp.beginPacket(address, 123); //NTP requests are to port 123 - udp.write(packetBuffer, NTP_PACKET_SIZE); - udp.endPacket(); -} - -void NTPClient::AskCurrentEpoch() -{ - if (DEBUG) Serial.println("AskCurrentEpoch called"); - //get a random server from the pool - WiFi.hostByName(ntpServerName, timeServerIP); - - sendNTPpacket(timeServerIP); // send an NTP packet to a time server -} - -unsigned long NTPClient::ReadCurrentEpoch() -{ - if (DEBUG) Serial.println("ReadCurrentEpoch called"); - int cb = udp.parsePacket(); - if (!cb) { - error_getTime = false; - if (DEBUG) Serial.println("no packet yet"); - } - else { - error_getTime = true; - if (DEBUG) Serial.print("packet received, length="); - if (DEBUG) Serial.println(cb); - // We've received a packet, read the data from it - udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer - - //the timestamp starts at byte 40 of the received packet and is four bytes, - // or two words, long. First, esxtract the two words: - - unsigned long highWord = word(packetBuffer[40], packetBuffer[41]); - unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]); - // combine the four bytes (two words) into a long integer - // this is NTP time (seconds since Jan 1 1900): - unsigned long secsSince1900 = highWord << 16 | lowWord; - if (DEBUG) Serial.print("Seconds since Jan 1 1900 = " ); - if (DEBUG) Serial.println(secsSince1900); - - // now convert NTP time into everyday time: - if (DEBUG) Serial.print("Unix time = "); - // Unix time starts on Jan 1 1970. In seconds, that's 2208988800: - const unsigned long seventyYears = 2208988800UL; - // subtract seventy years: - lastEpoch = secsSince1900 - seventyYears; // 1530082740=Fake 6:59:00, 1530795595=Fake 12:59:55, 1530835195=fake 23:59:55 - lastEpochTimeStamp = nextEpochTimeStamp; // Now that we have a new epoch, finally update lastEpochTimeStamp so all time calculations would be offset by the time we ask for this new epoch. - - if (DEBUG) Serial.println(lastEpoch); - return lastEpoch; - } -} - -unsigned long NTPClient::GetCurrentTime() -{ - //if (DEBUG) Serial.println("GetCurrentTime called"); - unsigned long timeNow = millis(); - if (timeNow > timeToAsk || !error_getTime) { // Is it time to ask server for current time? - if (DEBUG) Serial.println(" Time to ask"); - timeToAsk = timeNow + askFrequency; // Don't ask again for a while - if (timeToRead == 0) { // If we have not asked... - timeToRead = timeNow + 1000; // Wait one second for server to respond - AskCurrentEpoch(); // Ask time server what is the current time? - nextEpochTimeStamp = millis(); // next epoch we receive is for "now". - } - } - - if (timeToRead>0 && timeNow > timeToRead) // Is it time to read the answer of our AskCurrentEpoch? - { - // Yes, it is time to read the answer. - ReadCurrentEpoch(); // Read the server response - timeToRead = 0; // We have read the response, so reset for next time we need to ask for time. - } - - if (lastEpoch != 0) { // If we don't have lastEpoch yet, return zero so we won't try to display millis on the clock - unsigned long zoneOffset = String(timezone).toInt() * 3600; - unsigned long elapsedMillis = millis() - lastEpochTimeStamp; - currentTime = lastEpoch + zoneOffset + (elapsedMillis / 1000); - } - return currentTime; -} - -byte NTPClient::GetHours() -{ - int hours = (currentTime % 86400L) / 3600; - - // Convert to AM/PM if military time option is off (N) - if (military[0] == 'N') { - if (hours == 0) hours = 12; // Midnight in military time is 0:mm, but we want midnight to be 12:mm - if (hours > 12) hours -= 12; // After noon 13:mm should show as 01:mm, etc... - } - return hours; -} - -byte NTPClient::GetMinutes() -{ - return (currentTime % 3600) / 60; -} - -byte NTPClient::GetSeconds() -{ - return currentTime % 60; -} - -void NTPClient::PrintTime() -{ - if (DEBUG) - { - // print the hour, minute and second: - Serial.print("The UTC time is "); // UTC is the time at Greenwich Meridian (GMT) - byte hh = GetHours(); - byte mm = GetMinutes(); - byte ss = GetSeconds(); - - Serial.print(hh); // print the hour (86400 equals secs per day) - Serial.print(':'); - if ( mm < 10 ) { - // In the first 10 minutes of each hour, we'll want a leading '0' - Serial.print('0'); - } - Serial.print(mm); // print the minute (3600 equals secs per minute) - Serial.print(':'); - if ( ss < 10 ) { - // In the first 10 seconds of each minute, we'll want a leading '0' - Serial.print('0'); - } - Serial.println(ss); // print the second - } -} diff --git a/Latest/MorphingClock/NTPClient.h b/Latest/MorphingClock/NTPClient.h deleted file mode 100644 index b5ba786..0000000 --- a/Latest/MorphingClock/NTPClient.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - - Udp NTP Client - - Get the time from a Network Time Protocol (NTP) time server - Demonstrates use of UDP sendPacket and ReceivePacket - For more on NTP time servers and the messages needed to communicate with them, - see http://en.wikipedia.org/wiki/Network_Time_Protocol - - created 4 Sep 2010 - by Michael Margolis - modified 9 Apr 2012 - by Tom Igoe - updated for the ESP8266 12 Apr 2015 - by Ivan Grokhotkov - Refactored into NTPClient class by Hari Wiguna, 2018 - - This code is in the public domain. - - */ - -#include -#include -#include -//#include - - class NTPClient { - public: - NTPClient(); - void Setup(PxMATRIX* d); - unsigned long GetCurrentTime(); - byte GetHours(); - byte GetMinutes(); - byte GetSeconds(); - void PrintTime(); - - private: - PxMATRIX* _display; - unsigned long sendNTPpacket(IPAddress& address); - void AskCurrentEpoch(); - unsigned long ReadCurrentEpoch(); - }; - diff --git a/Latest/MorphingClock/TinyFont.cpp b/Latest/MorphingClock/TinyFont.cpp new file mode 100644 index 0000000..490cf76 --- /dev/null +++ b/Latest/MorphingClock/TinyFont.cpp @@ -0,0 +1,924 @@ +/* + * mirel.lazar@gmail.com + * + * provided 'AS IS', use it at your own risk + */ +#include "TinyFont.h" +/* + * we want to draw with pixels on an area of 4 cols and 5 rows like: + * spc 1 2 3 4 + * ..... ...*. .***. .***. .*.*. + * ..... ...*. ...*. ...*. .*.*. + * ..... ...*. .***. .***. .***. + * ..... ...*. .*... ...*. ...*. + * ..... ...*. .***. .***. ...*. + */ +/* + * +Dec Char Dec Char Dec Char Dec Char +--------- --------- --------- ---------- + 0 NUL (null) 32 SPACE 64 @ 96 ` + 1 SOH (start of heading) 33 ! 65 A 97 a + 2 STX (start of text) 34 " 66 B 98 b + 3 ETX (end of text) 35 # 67 C 99 c + 4 EOT (end of transmission) 36 $ 68 D 100 d + 5 ENQ (enquiry) 37 % 69 E 101 e + 6 ACK (acknowledge) 38 & 70 F 102 f + 7 BEL (bell) 39 ' 71 G 103 g + 8 BS (backspace) 40 ( 72 H 104 h + 9 TAB (horizontal tab) 41 ) 73 I 105 i + 10 LF (NL line feed, new line) 42 * 74 J 106 j + 11 VT (vertical tab) 43 + 75 K 107 k + 12 FF (NP form feed, new page) 44 , 76 L 108 l + 13 CR (carriage return) 45 - 77 M 109 m + 14 SO (shift out) 46 . 78 N 110 n + 15 SI (shift in) 47 / 79 O 111 o + 16 DLE (data link escape) 48 0 80 P 112 p + 17 DC1 (device control 1) 49 1 81 Q 113 q + 18 DC2 (device control 2) 50 2 82 R 114 r + 19 DC3 (device control 3) 51 3 83 S 115 s + 20 DC4 (device control 4) 52 4 84 T 116 t + 21 NAK (negative acknowledge) 53 5 85 U 117 u + 22 SYN (synchronous idle) 54 6 86 V 118 v + 23 ETB (end of trans. block) 55 7 87 W 119 w + 24 CAN (cancel) 56 8 88 X 120 x + 25 EM (end of medium) 57 9 89 Y 121 y + 26 SUB (substitute) 58 : 90 Z 122 z + 27 ESC (escape) 59 ; 91 [ 123 { + 28 FS (file separator) 60 < 92 \ 124 | + 29 GS (group separator) 61 = 93 ] 125 } + 30 RS (record separator) 62 > 94 ^ 126 ~ + 31 US (unit separator) 63 ? 95 _ 127 DEL + * + */ +const TFFace tinyFont[] = +{ + //space + { + //pixels + { + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + }, + }, + //! + { + //pixels + { + 0b00001000, + 0b00001000, + 0b00001000, + 0b00000000, + 0b00001000, + }, + }, + //" + { + //pixels + { + 0b00001010, + 0b00001010, + 0b00000000, + 0b00000000, + 0b00000000, + }, + }, + //# + { + //pixels + { + 0b00001010, + 0b00011111, + 0b00001010, + 0b00011111, + 0b00001010, + }, + }, + //$ + { + //pixels + { + 0b00001110, + 0b00001100, + 0b00001110, + 0b00000110, + 0b00001110, + }, + }, + //% + { + //pixels + { + 0b00001010, + 0b00000010, + 0b00000100, + 0b00001000, + 0b00001010, + }, + }, + //& + { + //pixels + { + 0b00001110, + 0b00001010, + 0b00001110, + 0b00001010, + 0b00001111, + }, + }, + //' + { + //pixels + { + 0b00000100, + 0b00000100, + 0b00000000, + 0b00000000, + 0b00000000, + }, + }, + //( + { + //pixels + { + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + }, + }, + //) + { + //pixels + { + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + }, + }, + //* + { + //pixels + { + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + }, + }, + //+ + { + //pixels + { + 0b00000000, + 0b00000100, + 0b00001110, + 0b00000100, + 0b00000000, + }, + }, + //, + { + //pixels + { + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000100, + 0b00001000, + }, + }, + //- + { + //pixels + { + 0b00000000, + 0b00000000, + 0b00001110, + 0b00000000, + 0b00000000, + }, + }, + //. + { + //pixels + { + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000100, + }, + }, + /// + { + //pixels + { + 0b00000010, + 0b00000010, + 0b00000100, + 0b00001000, + 0b00001000, + }, + }, + //0 + { + //pixels + { + 0b00001110, + 0b00001010, + 0b00001010, + 0b00001010, + 0b00001110, + }, + }, + //1 + { + //pixels + { + 0b000000100, + 0b000000100, + 0b000000100, + 0b000000100, + 0b000000100, + }, + }, + //2 + { + //pixels + { + 0b00001110, + 0b00000010, + 0b00001110, + 0b00001000, + 0b00001110, + }, + }, + //3 + { + //pixels + { + 0b00001110, + 0b00000010, + 0b00001110, + 0b00000010, + 0b00001110, + }, + }, + //4 + { + //pixels + { + 0b00001010, + 0b00001010, + 0b00001110, + 0b00000010, + 0b00000010, + }, + }, + //5 + { + //pixels + { + 0b00001110, + 0b00001000, + 0b00001110, + 0b00000010, + 0b00001110, + }, + }, + //6 + { + //pixels + { + 0b00001000, + 0b00001000, + 0b00001110, + 0b00001010, + 0b00001110, + }, + }, + //7 + { + //pixels + { + 0b00001110, + 0b00000010, + 0b00000010, + 0b00000010, + 0b00000010, + }, + }, + //8 + { + //pixels + { + 0b00001110, + 0b00001010, + 0b00001110, + 0b00001010, + 0b00001110, + }, + }, + //9 + { + //pixels + { + 0b00001110, + 0b00001010, + 0b00001110, + 0b00000010, + 0b00000010, + }, + }, + //: + { + //pixels + { + 0b00000000, + 0b00000100, + 0b00000000, + 0b00000100, + 0b00000000, + }, + }, + //; + { + //pixels + { + 0b00000000, + 0b00000100, + 0b00000000, + 0b00000100, + 0b00001000, + }, + }, + //< + { + //pixels + { + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + }, + }, + //= + { + //pixels + { + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + }, + }, + //> + { + //pixels + { + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + }, + }, + //? + { + //pixels + { + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + }, + }, + //@ + { + //pixels + { + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + }, + }, + //A + { + //pixels + { + 0b00001100, + 0b00001010, + 0b00001110, + 0b00001010, + 0b00001010, + }, + }, + //B + { + //pixels + { + 0b00001100, + 0b00001010, + 0b00001110, + 0b00001010, + 0b00001110, + }, + }, + //C + { + //pixels + { + 0b00001110, + 0b00001000, + 0b00001000, + 0b00001000, + 0b00001110, + }, + }, + //D + { + //pixels + { + 0b00001100, + 0b00001010, + 0b00001010, + 0b00001010, + 0b00001110, + }, + }, + //E + { + //pixels + { + 0b00001110, + 0b00001000, + 0b00001110, + 0b00001000, + 0b00001110, + }, + }, + //F + { + //pixels + { + 0b00001110, + 0b00001000, + 0b00001100, + 0b00001000, + 0b00001000, + }, + }, + //G + { + //pixels + { + 0b00001100, + 0b00001000, + 0b00001110, + 0b00001010, + 0b00001110, + }, + }, + //H + { + //pixels + { + 0b00001010, + 0b00001010, + 0b00001110, + 0b00001010, + 0b00001010, + }, + }, + //I + { + //pixels + { + 0b00001110, + 0b00000100, + 0b00000100, + 0b00000100, + 0b00001110, + }, + }, + //J + { + //pixels + { + 0b00000010, + 0b00000010, + 0b00000010, + 0b00001010, + 0b00001110, + }, + }, + //K + { + //pixels + { + 0b00001010, + 0b00001010, + 0b00001100, + 0b00001010, + 0b00001010, + }, + }, + //L + { + //pixels + { + 0b00001000, + 0b00001000, + 0b00001000, + 0b00001000, + 0b00001110, + }, + }, + //M + { + //pixels + { + 0b00001010, + 0b00001110, + 0b00001010, + 0b00001010, + 0b00001010, + }, + }, + //N + { + //pixels + { + 0b00001110, + 0b00001010, + 0b00001010, + 0b00001010, + 0b00001010, + }, + }, + //O + { + //pixels + { + 0b00000100, + 0b00001010, + 0b00001010, + 0b00001010, + 0b00000100, + }, + }, + //P + { + //pixels + { + 0b00001100, + 0b00001010, + 0b00001100, + 0b00001000, + 0b00001000, + }, + }, + //Q + { + //pixels + { + 0b00000100, + 0b00001010, + 0b00001010, + 0b00001010, + 0b00000101, + }, + }, + //R + { + //pixels + { + 0b00001100, + 0b00001010, + 0b00001100, + 0b00001010, + 0b00001010, + }, + }, + //S + { + //pixels + { + 0b00001110, + 0b00001000, + 0b00001110, + 0b00000010, + 0b00001110, + }, + }, + //T + { + //pixels + { + 0b00001110, + 0b00000100, + 0b00000100, + 0b00000100, + 0b00000100, + }, + }, + //U + { + //pixels + { + 0b00001010, + 0b00001010, + 0b00001010, + 0b00001010, + 0b00001110, + }, + }, + //V + { + //pixels + { + 0b00001010, + 0b00001010, + 0b00001010, + 0b00001010, + 0b00000100, + }, + }, + //W + { + //pixels + { + 0b00001010, + 0b00001010, + 0b00001010, + 0b00001110, + 0b00001010, + }, + }, + //X + { + //pixels + { + 0b00001010, + 0b00001010, + 0b00000100, + 0b00001010, + 0b00001010, + }, + }, + //Y + { + //pixels + { + 0b00001010, + 0b00001010, + 0b00000100, + 0b00000100, + 0b00000100, + }, + }, + //Z + { + //pixels + { + 0b00001110, + 0b00000010, + 0b00001110, + 0b00001000, + 0b00001110, + }, + }, + //[ + { + //pixels + { + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + }, + }, + // + { + //pixels + { + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + }, + }, + //] + { + //pixels + { + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + }, + }, + //^ + { + //pixels + { + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + }, + }, + //_ + { + //pixels + { + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + }, + }, + //` + { + //pixels + { + 0b00001010, + 0b00011111, + 0b00011111, + 0b00001110, + 0b00000100, + }, + }, + //a + { + //pixels + { + 0b00000010, + 0b00001000, + 0b00000011, + 0b00010111, + 0b00000111, + }, + }, + //b + { + //pixels + { + 0b00001000, + 0b00000010, + 0b00011000, + 0b00011101, + 0b00011100, + }, + }, + //c + { + //pixels + { + 0b00000000, + 0b00000110, + 0b00001111, + 0b00001111, + 0b00000111, + }, + }, + //d + { + //pixels + { + 0b00001100, + 0b00011110, + 0b00011110, + 0b00011110, + 0b00011100, + }, + }, + //e + { + //pixels + { + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + }, + }, + //f + { + //pixels + { + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + }, + }, + //g + { + //pixels + { + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + }, + }, + //h + { + //pixels + { + 0b00000110, + 0b00001111, + 0b00001111, + 0b00000111, + 0b00000001, + }, + }, + //i + { + //pixels + { + 0b000000110, + 0b000001111, + 0b000001111, + 0b000001110, + 0b000001000, + }, + }, +}; + +const int cfblack = 0; + +static const char single_col_chars[] = " !.|1:"; +void TFDrawChar (PxMATRIX* d, char value, char xo, char yo, int col) +{ + int i, j, s = 0, cfi = value - ' '; + if (cfi > (int)(sizeof (tinyFont) / sizeof (TFFace))) + { + Serial.print ("character code not supported: "); + Serial.println (cfi + ' '); + } + else + { + if(strchr(single_col_chars, value)) + { + s = 1; + } + for (i = 0; i < TF_ROWS; i++) + { + for (j = s; j < ((s)?3:TF_COLS); j++) + { + if (tinyFont[cfi].fface[i] & (1 << j)) + d->drawPixel (xo + TF_COLS - j - s, yo + i, col); + else + d->drawPixel (xo + TF_COLS - j - s, yo + i, cfblack); + } + } + } +} + +void TFDrawText (PxMATRIX* d, String text, char xo, char yo, int col) +{ + unsigned char i, pixels = 0; + unsigned char lpbuf[33], *lptr = lpbuf; + char s, *sc; + bool loop = true; + + for(i = 0; i < text.length() && loop; i++) + { + s = (strchr(single_col_chars, text[i]))?2:4; + if((pixels + s) > 64) + loop = false; + else + pixels += s; + } + text.getBytes (lpbuf, i + 1); + Serial.println ((char*)&lpbuf); + for (; *lptr; lptr++, xo += ((sc)?2:TF_COLS)) + { + sc = strchr(single_col_chars, *lptr); + TFDrawChar (d, *lptr, xo, yo, col); + } +} diff --git a/Latest/MorphingClock/TinyFont.h b/Latest/MorphingClock/TinyFont.h new file mode 100644 index 0000000..a919d6e --- /dev/null +++ b/Latest/MorphingClock/TinyFont.h @@ -0,0 +1,38 @@ +/* + * mirel.lazar@gmail.com + * + * provided 'AS IS', use it at your own risk + */ +#ifndef TINYFONT_H +#define TINYFONT_H + +#include +#include // https://github.com/2dom/PxMatrix + +#define TF_COLS 4 +#define TF_ROWS 5 +struct TFFace +{ + char fface[TF_ROWS]; //4 cols x 5 rows +}; +/* + * example: + yo = 1; + int cc = random (25, 65535); + xo = 0; TFDrawChar (&display, 'L', xo, yo, cc); cc = random (25, 65535); + xo += 5; TFDrawChar (&display, 'O', xo, yo, cc); cc = random (25, 65535); + xo += 5; TFDrawChar (&display, 'V', xo, yo, cc); cc = random (25, 65535); + xo += 5; TFDrawChar (&display, 'E', xo, yo, cc); cc = random (25, 65535); + */ +void TFDrawChar (PxMATRIX* d, char value, char xo, char yo, int col); + +/* + * example: + String lstr = String (tempM) + "C"; + int cc_red = display.color565 (255, 0, 0); + xo = 1; yo = 1; + TFDrawText (&display, lstr, xo, yo, cc_red); + */ +void TFDrawText (PxMATRIX* d, String text, char xo, char yo, int col); + +#endif diff --git a/Latest/MorphingClock/WTAClient.cpp b/Latest/MorphingClock/WTAClient.cpp new file mode 100644 index 0000000..7f1db13 --- /dev/null +++ b/Latest/MorphingClock/WTAClient.cpp @@ -0,0 +1,421 @@ +//=== WIFI MANAGER === +#include +#include +#include +#include //https://github.com/alanswx/ESPAsyncWiFiManager +#include +#include "TinyFont.h" + +char wifiManagerAPName[] = "MorphClk"; +char wifiManagerAPPassword[] = "MorphClk"; + +//== DOUBLE-RESET DETECTOR == +#include +#define DRD_TIMEOUT 10 // Second-reset must happen within 10 seconds of first reset to be considered a double-reset +#define DRD_ADDRESS 0 // RTC Memory Address for the DoubleResetDetector to use +DoubleResetDetector drd(DRD_TIMEOUT, DRD_ADDRESS); + +//== SAVING CONFIG == +#include "FS.h" +#include +bool shouldSaveConfig = false; // flag for saving data + +//callback notifying us of the need to save config +void saveConfigCallback () { + Serial.println("Should save config"); + shouldSaveConfig = true; +} + +//=== WTA CLIENT === +#include "WTAClient.h" +#define WTAServerName "http://worldtimeapi.org/api/" +HTTPClient http; +String payload; + +#define DEBUG 0 +const unsigned long askFrequency = 60 * 60 * 1000; // How frequent should we get current time? in miliseconds. 60minutes = 60*60s = 60*60*1000ms +unsigned long timeToAsk; +unsigned long timeToRead; +unsigned long lastEpoch; // We don't want to continually ask for epoch from time server, so this is the last epoch we received (could be up to an hour ago based on askFrequency) +unsigned long lastEpochTimeStamp; // What was millis() when asked server for Epoch we are currently using? +unsigned long nextEpochTimeStamp; // What was millis() when we asked server for the upcoming epoch +unsigned long currentTime; + +//== PREFERENCES == (Fill these appropriately if you could not connect to the ESP via your phone) +char homeWifiName[] = ""; // PREFERENCE: The name of the home WiFi access point that you normally connect to. +char homeWifiPassword[] = ""; // PREFERENCE: The password to the home WiFi access point that you normally connect to. +char timezone[32] = "ip"; // PREFERENCE: TimeZone. Go to http://worldtimeapi.org/api/timezone to find your timezone string or chose "ip" to use IP-localisation for timezone detection +bool military; // PREFERENCE: 24 hour mode? + +char configFilename[] = "/config.json"; + +bool error_getTime = false; + +void configModeCallback (WiFiManager *myWiFiManager) { + Serial.println("Entered config mode"); + Serial.println(WiFi.softAPIP()); + + // You could indicate on your screen or by an LED you are in config mode here + + // We don't want the next time the boar resets to be considered a double reset + // so we remove the flag + drd.stop(); +} + + +bool loadConfig() { + Serial.println("=== Loading Config ==="); + File configFile = SPIFFS.open(configFilename, "r"); + if (!configFile) { + Serial.println("Failed to open config file"); + return false; + } + + size_t size = configFile.size(); + if (size > 1024) { + Serial.println("Config file size is too large"); + return false; + } + + // Allocate a buffer to store contents of the file. + std::unique_ptr buf(new char[size]); + + configFile.readBytes(buf.get(), size); + + StaticJsonBuffer<200> jsonBuffer; + JsonObject& json = jsonBuffer.parseObject(buf.get()); + + if (!json.success()) { + Serial.println("Failed to parse config file"); + return false; + } + return true; +} + +bool saveConfig() { + Serial.println("=== Saving Config ==="); + StaticJsonBuffer<200> jsonBuffer; + JsonObject& json = jsonBuffer.createObject(); + + File configFile = SPIFFS.open(configFilename, "w"); + if (!configFile) { + Serial.println("Failed to open config file for writing"); + return false; + } + + Serial.print("timezone="); + Serial.println(timezone); + + Serial.print("military="); + Serial.println(military); + + json.printTo(configFile); + //configFile.close(); + return true; +} + + +WTAClient::WTAClient() +{ +} + + +void WTAClient::Setup(PxMATRIX* d) +{ + char tstr[32]; + + //-- Config -- + if (!SPIFFS.begin()) { + Serial.println("Failed to mount FS"); + return; + } + + loadConfig(); + + //-- Display -- + _display = d; + _display->fillScreen(_display->color565(0, 0, 0)); + _display->setTextColor(_display->color565(0, 0, 255)); + //_display->setFont(&FreeMono9pt7b); + //_display->setTextSize(1); + const byte row0 = 2 + 0 * 10; + const byte row1 = 2 + 1 * 10; + const byte row2 = 2 + 2 * 10; + + //-- WiFiManager -- + //Local intialization. Once its business is done, there is no need to keep it around + WiFiManager wifiManager; + //wifiManager.resetSettings(); // Uncomment this to reset saved WiFi credentials. Comment it back after you run once. + //wifiManager.setBreakAfterConfig(true); // Get out of WiFiManager even if we fail to connect after config. So our Hail Mary pass could take care of it. + wifiManager.setSaveConfigCallback(saveConfigCallback); + + int connectionStatus = WL_IDLE_STATUS; + + if (strlen(homeWifiName) > 0) { + Serial.println("USING IN SKETCH CREDENTIALS:"); + Serial.println(homeWifiName); + Serial.println(homeWifiPassword); + + _display->setCursor(2, row1); + _display->print("Connecting"); + + connectionStatus = WiFi.begin(homeWifiName, homeWifiPassword); + Serial.print("WiFi.begin returned "); + Serial.println(connectionStatus); + } + else { + + //-- Double-Reset -- + if (drd.detectDoubleReset()) { + Serial.println("DOUBLE Reset Detected"); + digitalWrite(LED_BUILTIN, LOW); + + _display->setCursor(1, row0); _display->print("AP"); + _display->setCursor(1 + 10, row0); _display->print(":"); + _display->setCursor(1 + 10 + 5, row0); _display->print(wifiManagerAPName); + + _display->setCursor(1, row1); _display->print("Pw"); + _display->setCursor(1 + 10, row1); _display->print(":"); + _display->setCursor(1 + 10 + 5, row1); _display->print(wifiManagerAPPassword); + + _display->setCursor(1, row2); _display->print("192"); + _display->setCursor(1 + 3 * 6 - 1, row2); _display->print(".168"); + _display->setCursor(1 + 3 * 6 - 1 + 5 + 3 * 6, row2); _display->print(".4"); + _display->setCursor(1 + 3 * 6 - 1 + 5 + 3 * 6 + 5 + 6, row2); _display->print(".1"); + + WiFi.disconnect(); + connectionStatus = wifiManager.startConfigPortal(wifiManagerAPName, wifiManagerAPPassword); + + Serial.print("startConfigPortal returned "); + Serial.println(connectionStatus); + + _display->fillScreen(_display->color565(0, 0, 0)); + } + else + { + Serial.println("SINGLE reset Detected"); + digitalWrite(LED_BUILTIN, HIGH); + + _display->setCursor(2, row1); + _display->print("Connecting"); + + //fetches ssid and pass from eeprom and tries to connect + //if it does not connect it starts an access point with the specified name wifiManagerAPName + //and goes into a blocking loop awaiting configuration + + + connectionStatus = wifiManager.autoConnect(); //wifiManagerAPName, wifiManagerAPPassword); + Serial.print("autoConnect returned "); + Serial.println(connectionStatus); + } + } + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + // Hail Mary pass. If WiFiManager fail to connect user to home wifi, connect manually :-( + // if (WiFi.status() != WL_CONNECTED) { + // Serial.println("Hail Mary!"); + // + // ETS_UART_INTR_DISABLE(); + // wifi_station_disconnect(); + // ETS_UART_INTR_ENABLE(); + // + // WiFi.begin(homeWifiName, homeWifiPassword); + // Serial.println("Connected?"); + // } + + //-- Status -- + Serial.print("WiFi.status() = "); + Serial.println(WiFi.status()); + + _display->fillScreen(_display->color565(0, 0, 0)); + _display->setCursor(2, row0); + _display->print("Connected!"); + + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + //-- Timezone -- + _display->setCursor(2, row1); + _display->print("TZ:"); + if(strlen(timezone) < 9) + _display->print(timezone); + else + { + char *tzptr = strrchr(timezone, '/'); + if(!tzptr) + tzptr = timezone; + else + ++tzptr; + if(strlen(tzptr) < 7) + _display->print(tzptr); + else + { + char tstr[8]; + strncpy(tstr, tzptr, 6); + _display->print(tstr); + } + } + + //-- show IP -- + sprintf(tstr, "IP:%s", WiFi.localIP().toString().c_str()); + TFDrawText(_display, tstr, 1, 23, _display->color565(0, 0, 255)); + + if (shouldSaveConfig) { + saveConfig(); + } + drd.stop(); + delay(3000); +} + +void WTAClient::AskCurrentEpoch() +{ + char url[128]; + int httpCode; + + if(strcmp(timezone, "ip")) + sprintf(url, "%stimezone/%s", WTAServerName, timezone); + else + sprintf(url, "%s%s", WTAServerName, timezone); + http.begin(url); + + //if (DEBUG) Serial.println("AskCurrentEpoch called"); + + httpCode = http.GET(); + + payload = ""; + if(httpCode > 0) + { + payload = http.getString(); + } + http.end(); +} + +unsigned long WTAClient::ReadCurrentEpoch() +{ + if (DEBUG) Serial.println("ReadCurrentEpoch called"); + int cb = payload.length(); + if (!cb) { + error_getTime = false; + if (DEBUG) Serial.println("no packet yet"); + } + else { + const size_t capacity = JSON_OBJECT_SIZE(15) + 512; + DynamicJsonBuffer jsonBuffer(capacity); + + error_getTime = true; + Serial.print("time answer received, length="); + Serial.println(cb); + // We've received a time, read the data from it + // the timedate are JSON values + + JsonObject& root = jsonBuffer.parseObject(payload.c_str()); + if(!root.success()) { + Serial.println("parseObject() failed"); + } + else + { +// int week_number = root["week_number"]; // 31 +// const char* utc_offset = root["utc_offset"]; // "-04:00" +// const char* utc_datetime = root["utc_datetime"]; // "2019-08-01T16:58:40.68279+00:00" + long unixtime = root["unixtime"]; // 1564678720 +// const char* timezone = root["timezone"]; // "America/New_York" + int raw_offset = root["raw_offset"]; // -18000 +// const char* dst_until = root["dst_until"]; // "2019-11-03T06:00:00+00:00" + int dst_offset = root["dst_offset"]; // 3600 +// const char* dst_from = root["dst_from"]; // "2019-03-10T07:00:00+00:00" +// bool dst = root["dst"]; // true +// int day_of_year = root["day_of_year"]; // 213 +// int day_of_week = root["day_of_week"]; // 4 +// const char* datetime = root["datetime"]; // "2019-08-01T12:58:40.682790-04:00" +// const char* client_ip = root["client_ip"]; // "23.235.227.109" +// const char* abbreviation = root["abbreviation"]; // "EDT" + + // now convert WTA time into everyday time: + if (DEBUG) Serial.print("Unix time = "); + lastEpoch = unixtime + raw_offset + dst_offset + 1; + lastEpochTimeStamp = nextEpochTimeStamp; + if (DEBUG) Serial.println(lastEpoch); + } + return lastEpoch; + } +} + +unsigned long WTAClient::GetCurrentTime(bool force) +{ + //if (DEBUG) Serial.println("GetCurrentTime called"); + unsigned long timeNow = millis(); + if (force || (timeNow > timeToAsk || !error_getTime)) { // Is it time to ask server for current time? + if (DEBUG) Serial.println(" Time to ask"); + timeToAsk = timeNow + askFrequency; // Don't ask again for a while + if (force || (timeToRead == 0)) { // If we have not asked... + timeToRead = timeNow + 1000; // Wait one second for server to respond + AskCurrentEpoch(); // Ask time server what is the current time? + nextEpochTimeStamp = millis(); // next epoch we receive is for "now". + } + } + + if (force || (timeToRead > 0 && timeNow > timeToRead)) // Is it time to read the answer of our AskCurrentEpoch? + { + // Yes, it is time to read the answer. + ReadCurrentEpoch(); // Read the server response + timeToRead = 0; // We have read the response, so reset for next time we need to ask for time. + } + + if (force || (lastEpoch != 0)) { // If we don't have lastEpoch yet, return zero so we won't try to display millis on the clock + unsigned long elapsedMillis = millis() - lastEpochTimeStamp; + currentTime = lastEpoch + (elapsedMillis / 1000); + } + return currentTime; +} + +byte WTAClient::GetHours() +{ + int hours = (currentTime % 86400L) / 3600; + + // Convert to AM/PM if military time option is off + if (!military) { + if (hours == 0) hours = 12; // Midnight in military time is 0:mm, but we want midnight to be 12:mm + if (hours > 12) hours -= 12; // After noon 13:mm should show as 01:mm, etc... + } + return hours; +} + +byte WTAClient::GetMinutes() +{ + return (currentTime % 3600) / 60; +} + +byte WTAClient::GetSeconds() +{ + return currentTime % 60; +} + +void WTAClient::PrintTime() +{ + if (DEBUG) + { + // print the hour, minute and second: + Serial.print("The local time is "); + byte hh = GetHours(); + byte mm = GetMinutes(); + byte ss = GetSeconds(); + + Serial.print(hh); // print the hour (86400 equals secs per day) + Serial.print(':'); + if ( mm < 10 ) { + // In the first 10 minutes of each hour, we'll want a leading '0' + Serial.print('0'); + } + Serial.print(mm); // print the minute (3600 equals secs per minute) + Serial.print(':'); + if ( ss < 10 ) { + // In the first 10 seconds of each minute, we'll want a leading '0' + Serial.print('0'); + } + Serial.println(ss); // print the second + } +} diff --git a/Latest/MorphingClock/WTAClient.h b/Latest/MorphingClock/WTAClient.h new file mode 100644 index 0000000..2323306 --- /dev/null +++ b/Latest/MorphingClock/WTAClient.h @@ -0,0 +1,45 @@ +#ifndef __WTA_CLIENT_H__ +#define __WTA_CLIENT_H__ +/* + + WTA Client + + Get the time from the worldtimeapi-server to prevent timezone and DST-mess + Demonstrates use http-client and json-parser + + created 4 Sep 2010 + by Michael Margolis + modified 9 Apr 2012 + by Tom Igoe + updated for the ESP8266 12 Apr 2015 + by Ivan Grokhotkov + Refactored into NTPClient class by Hari Wiguna, 2018 + changed from NTP to worldtimeapi by SnowHead, 2019 + + This code is in the public domain. + + */ +#include +#include +#include +//#include + +extern char timezone[32]; // PREFERENCE: TimeZone. Go to http://worldtimeapi.org/api/timezone to find your timezone string or chose "ip" to use IP-localisation for timezone detection +extern bool military; // PREFERENCE: 24 hour mode? + + class WTAClient { + public: + WTAClient(); + void Setup(PxMATRIX* d); + unsigned long GetCurrentTime(bool force); + byte GetHours(); + byte GetMinutes(); + byte GetSeconds(); + void PrintTime(); + + private: + PxMATRIX* _display; + unsigned long ReadCurrentEpoch(); + void AskCurrentEpoch(); +}; +#endif // __WTA_CLIENT_H__ diff --git a/README.md b/README.md index 15f6ddd..4679b86 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,33 @@ -# HariFun_166_Morphing_Clock +# HariFun\_166\_Morphing\_Clock modified by SnowHead -Read what this code is all about on [Instructable](https://www.instructables.com/id/Morphing-Digital-Clock/). +---------- + +# Attention! At the moment this fork only works for ESP8266 ! +## The search and inlucde of the suitable libraries for ESP32 is in progress +---------- + + +Read what this code primary was all about on [Instructable](https://www.instructables.com/id/Morphing-Digital-Clock/). + +**CHANGES BY SNOWHEAD** + +- Changed from NTP to HTTP time request (via ESP8266HTTPClient library) +- No further mess around with timezones and DST settings. The default timezone "ip" (case sensitive!) will force the server to determine your location and DST state by analyzing your public IP. Only if you're connected via a foreign proxy to the internet you will have to set your local timezone regarding the list on [WTA](http://worldtimeapi.org/api/timezones). DST settings will always be detected automatically. +- Digits will not further morphed digit by digit but all in parallel. +- Easier selection of digit color. +- Patch for weak ESP8266-12E included +- Night-Mode (ESP32 only), if "Start" or "End" hour are different from zero, the displays brighness will be reduced between this both times to the percentage set in "Brightness" +- permanent accessible Web-Interface for live change of the settings (military, timezone, color, fadingspeed, (ESP32 only) nightmode and brightness), settings take only effect after clicking button "Send", clicking the button "Save" stores the changes permanent in the clock Click photo for a quick demo. -[![Morphing Clock](https://img.youtube.com/vi/i0M6F4wRxGc/0.jpg)](https://www.youtube.com/watch?v=i0M6F4wRxGc) +original: [![Morphing Clock](https://img.youtube.com/vi/i0M6F4wRxGc/0.jpg)](https://www.youtube.com/watch?v=i0M6F4wRxGc) + +modified: [![Morphing Clock](https://img.youtube.com/vi/GLg5dzmM7W4/0.jpg)](https://youtu.be/GLg5dzmM7W4) + +Because the ESP8266 is not able to dimm the brightness by PWM (trying this will produce an ugly flicker) the webinterface differs for ESP32 and ESP8266. The ESP8266 can set the display only to off in nightmode, can in normal state not reduce the brightness and must use all colors in full brightness. + + ESP32:    ![MorphingClock](images/IF_32.jpg) +ESP8266:![MorphingClock](images/IF_8266.jpg) diff --git a/images/IF_32.jpg b/images/IF_32.jpg new file mode 100644 index 0000000..fec82de Binary files /dev/null and b/images/IF_32.jpg differ diff --git a/images/IF_8266.jpg b/images/IF_8266.jpg new file mode 100644 index 0000000..a3863f9 Binary files /dev/null and b/images/IF_8266.jpg differ