Monday, February 7, 2022

Retirement Clock Goes Digital

 

-----

We overheard in a conversation recently something like: "Now that I'm retired I really only focus on what day of the week it is and the general time of day."  We then discovered this awful looking Day of the Week Clock and the vision of a more modern version came to mind.

-----

The rig is based around this MakerFocus ESP32 Development Board mainly because it works with the Arduino IDE and has WiFi with a display on board.   The code (source below) gets it's time/date from a NTP server and then simply figures out and displays the day of the week and what percentage of the day has past.  Evidently this is all the critical information needed to guide the non-working through their day.

-----

To dress up the look a case was 3D printed.  After seeing the result we needed to celebrate with a beer.   As shown in the video below it was a bit early in the day for a drink, but as the ol' saying goes "It's 71% somewhere!".

-----

Retirement Clock Source Code:

/*
 * HelTec Automation(TM) ESP32 Series Dev boards OLED "Retirement Clock"
 * Adruino IDE Board Setting: WiFi Kit32, Disabled, 240MHz, 921600, None
 *
 * "Retirement Clock" shows only Day of Week and percentage time left in the day.
 *  
 * FEB 2022
 * Search whiskeytangohotel.com for project details.
 *
*/

#include "Arduino.h"
#include "heltec.h"

#include <TimeLib.h>
#include <WiFi.h>
#include <WiFiUdp.h>

const char ssid[] = "xxxxxxx";  //  your network SSID (name)
const char pass[] = "xxxxxxx";       // your network password
long Sync_Delay = 60000;  //  60000 is sync every ten minutes,  We aren't going for precision here. LOL

int DOWNUM = 0;     //  DOWNUM from NTP (expect 0 - 6)
String DOW = "DOW";  // DOWNUM to a Day of Week String
int hourNUM = 0;    //  hour() value from NTP
float minuteNUM = 0;   // minute() value from NPT

// NTP Servers:
static const char ntpServerName[] = "pool.ntp.org";
const int timeZone = 0;     // leave as 0 for UTC.  We do the offset math in void setup

WiFiUDP Udp;
unsigned int localPort = 8888;  // local port to listen for UDP packets

time_t getNtpTime();
void digitalClockDisplay();
void printDigits(int digits);
void sendNTPpacket(IPAddress &address);

void setup() {
  //NTP setup
  Serial.begin(115200);
  while (!Serial) ; // Needed for Leonardo only
  delay(250);
  Serial.println("TimeNTP");
  Serial.print("Connecting to... ");
  Serial.println(ssid);
  WiFi.disconnect();
  WiFi.mode(WIFI_MODE_STA);
  WiFi.begin(ssid, pass);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.print("IP number assigned by DHCP is: ");
  Serial.println(WiFi.localIP());
  Serial.println("Starting UDP");
  Udp.begin(localPort);
  Serial.print("Local port: ");
//  Serial.println(Udp.localPort());
  Serial.println("waiting for sync... ");
  setSyncProvider(getNtpTime);
  setSyncInterval(Sync_Delay);  //   defines how often to check NTP time

  // Hetec setup
  Heltec.begin(true /*DisplayEnable Enable*/, false /*LoRa Disable*/, true /*Serial Enable*/);
  Heltec.display->flipScreenVertically();
  Heltec.display->setFont(ArialMT_Plain_24);
} // end setup link

time_t prevDisplay = 0; // when the digital clock was last displayed timer

void loop() {   // main program loop
  if (timeStatus() != timeNotSet) {
    if (now() != prevDisplay) { //update the display only if time has changed
      prevDisplay = now();
    }
  }

  // Parse time info and send info to serial monitor (for debug)
  Serial.print(hour());
  printDigits(minute());
  printDigits(second());
  Serial.print(" ");
  Serial.print(day());
  Serial.print(".");
  Serial.print(month());
  Serial.print(".");
  Serial.print(year());
  Serial.print(" : ");

  if (int(year()) < 2022) {
     Serial.print(String(int(year())) + " is the NTP server year.  That aint right, so try again!!!");
     Serial.println();      
     setup();
  }   // the NTP server return something 'wierd' (probably 1970) so try again  

  //Covert Time in percent of day completed.  Midnight Local = 0%, Noon Local = 50%  
  minuteNUM = minute();
  hourNUM =   int(hour());  
  // manually adjust hourNUM and minuteNUM for debug
  // hourNUM =  5;    // valid values are INTERGERS 0 thru 24
  // minuteNUM = 49;    // valid values are INTEGERS 0 thru 60  

  minuteNUM = minuteNUM / 60;    // covert minutes to 'fraction'  
  hourNUM = hourNUM - 6;   // hourNUM is in UTC.   Adjust to local.   -6 = CST, -5 = CDT, 0 = UTC
  DOWNUM = int(weekday());
   
  // Calculate percentage of the day pasted into 'progress'
  int progress = round(   (  ((hourNUM + minuteNUM))   /  24   )  * 100    );
  if (progress < 0) {   // it's past UTC midnight and the timezone offset created a negative %
    progress = 100 + progress;
    DOWNUM = DOWNUM -  1;
  }  //end progress < 0

  // Convert DOWNUM into printable String.  Sunday is 1
  if (DOWNUM  == 1) { DOW = "SUN"; }
  if (DOWNUM  == 2) { DOW = "MON"; }
  if (DOWNUM  == 3) { DOW = "TUE"; }
  if (DOWNUM  == 4) { DOW = "WED"; }
  if (DOWNUM  == 5) { DOW = "THU"; }
  if (DOWNUM  == 6) { DOW = "FRI"; }
  if (DOWNUM  == 7) { DOW = "SAT"; }

  Serial.print(String(int(weekday())) + " is " + DOW);
  Serial.println();   
  Serial.print(String(hourNUM) + " + " + String(minuteNUM) + " = " + String(progress) + "%");
  Serial.println();
 
  // clear the display
  Heltec.display->clear();

  // Draw the Precent of Day pasted onto progress bar
  Heltec.display->drawProgressBar(0, 0, 127, 20, progress);

  // Label the Day Of Week and percentage as String
  Heltec.display->setTextAlignment(TEXT_ALIGN_CENTER);
  Heltec.display->drawString(60, 31, DOW + " " + String(progress) + "%");
  // Write buffer to Heltec display
  Heltec.display->display();
 
  delay(5000); // wait some.  we are in no hurry to update the screen
}  // end void loop main program


void printDigits(int digits)
{
  // utility for digital clock display: prints preceding colon and leading 0
  Serial.print(":");
  if (digits < 10)
    Serial.print('0');
  Serial.print(digits);
}

/*-------- NTP code ----------*/

const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets

time_t getNtpTime()
{
  IPAddress ntpServerIP; // NTP server's ip address

  while (Udp.parsePacket() > 0) ; // discard any previously received packets
  Serial.println("Transmit NTP Request");
  // get a random server from the pool
  WiFi.hostByName(ntpServerName, ntpServerIP);
  Serial.print(ntpServerName);
  Serial.print(": ");
  Serial.println(ntpServerIP);
  sendNTPpacket(ntpServerIP);
  uint32_t beginWait = millis();
  while (millis() - beginWait < 1500) {
    int size = Udp.parsePacket();
    if (size >= NTP_PACKET_SIZE) {
      Serial.println("Receive NTP Response");
      Udp.read(packetBuffer, NTP_PACKET_SIZE);  // read packet into the buffer
      unsigned long secsSince1900;
      // convert four bytes starting at location 40 to a long integer
      secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
      secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long)packetBuffer[43];
      return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
    }
  }
  Serial.println("No NTP Response :-(");
  return 0; // return 0 if unable to get the time
}

// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress &address)
{
  // 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();
}

-----