/* vk3pe 10th Dec 2020: 15th Dec 2020 this version has been altered for TTGO screen vk3pe Since the TTGO screen is much smaller, not all text is shown on the TTGO screen. 19th Dec: There is now a "#define TTGO" to enable EITHER a 2.8" TFT display with ILI9341 controller OR a TTGO with integrated 1.16" display to be selected. 1st Jan 2021: V1.3a Fixed delete of last date before writing new date. 8th Jan 2021 Fixed power up screen, numbers to right will overflow right side if >9 For the TTGO board, in the Tools/Board: selection vk3pe used "TTGO_LoRa32-OLED_V1" ("TTGO T1" also seems to work) For the Programmer, I selected "ArduinoISP" NB! in the TFT_eSPI Library, in the text file "User_Setup_Select.h" you must enable ONLY the TFT type you are using. For the TTGO, select (#25) --------------------------------------------------------------------------- */ /*****from John 021220 Bruce updated his github with Johns fixes*********** Title: NTP Dual Clock Author: Bruce E. Hall, w8bh.net Date: 02 Dec 2020 Hardware: Adafruit ESP32 Feather, ILI9341 TFT display module Software: Arduino IDE 1.8.13 with Expressif ESP32 package TFT_eSPI Library ezTime Library Legal: Copyright (c) 2020 Bruce E. Hall. Open Source under the terms of the MIT License. Description: Dual UTC/Local NTP Clock with TFT display Time is refreshed via NTP every 30 minutes Optional time output to serial port Status indicator for time freshness & WiFi strength Before using, please update WIFI_SSID and WIFI_PWD with your personal WiFi credentials. Also, modify TZ_RULE with your own Posix timezone string. see w8bh.net for a detailled, step-by-step tutorial Version Hx: 11/27/20 Initial GitHub commit 11/28/20 added code to handle dropped WiFi connection 11/30/20 showTimeDate() mod by John Price (WA2FZW) 12/01/20 showAMPM() added by John Price (WA2FZW) 12/19/20 V1.3a Added select either 2.8" TFT or TTGO board (VK3PE) **************************************************************************/ #include // https://github.com/Bodmer/TFT_eSPI #include // https://github.com/ropg/ezTime #include #define TITLE "NTP Time" //#define WIFI_SSID "your SSID" // "your SSID" of your WiFi if not using access point //#define WIFI_PWD "your password" // "your password" Password for your WiFi #define WIFI_SSID "Telstra9D26AF" // SSID of your WiFi if not using access point gpp 291120 #define WIFI_PWD "8A6EA1318F" // Password for your WiFi //#define NTP_SERVER "pool.ntp.org" // time.nist.gov, pool.ntp.org, etc #define NTP_SERVER "ntp.cs.mu.OZ.AU" //Melbourne uni //"time.nist.gov" // /* * T1me Zone rules in "Posix timezone string" format. For an explanation and a list of * the strings appropriate for various locations, see the following web pages: * * https://support.cyberdata.net/index.php?/Knowledgebase/Article/View/438/10/posix-timezone-strings * https://developer.ibm.com/technologies/systems/articles/au-aix-posix/ * * The ones listed here are for the folks playing with this program along with me; * remove the comments from the appropriate one or make your own! */ //#define TZ_RULE "EST5EDT,M3.2.0/2:00:00,M11.1.0/2:00:00" // John - US Eastern time #define TZ_RULE "AEST-10AEDT,M10.1.0/2:00:00,M4.1.0/2:00:00" // Glenn - Aus Eastern time //#define TZ_RULE "GMT0BST,M3.5.0/1:00:00,M10.5.0/2:00:00" // Jim - England //#define TZ_RULE "MET-1METDST,M3.5.0/1:00:00,M10.5.0/2:00:00" // Jean-Marie Belgium GMT+1 time zone (Brussels, Paris #define DEBUGLEVEL INFO // NONE, ERROR, INFO, or DEBUG #define PRINTED_TIME 1 // 0=NONE, 1=UTC, or 2=LOCAL #define TIME_FORMAT COOKIE // COOKIE, ISO8601, RFC822, RFC850, RFC3339, RSS #define BAUDRATE 115200 // serial output baudrate #define LEADING_ZERO false // show "01:00" vs " 1:00" #define SYNC_MARGINAL 3600 // orange status if no sync for 1 hour #define SYNC_LOST 86400 // red status if no sync for 1 day #define LOCAL_FORMAT_12HR true // local time format 12hr "11:34" vs 24hr "23:34" #define UTC_FORMAT_12HR false // UTC time format 12 hr "11:34" vs 24hr "23:34" #define DISPLAY_AMPM true // if true, show 'A' for AM, 'P' for PM #define SCREEN_ORIENTATION 3 // screen portrait mode: use 1 or 3 #define TIMECOLOR TFT_CYAN // color of 7-segment time display #define DATECOLOR TFT_YELLOW // color of displayed month & day #define LABEL_FGCOLOR TFT_YELLOW // color of label text #define LABEL_BGCOLOR TFT_BLUE // color of label background #define TTGO true // alternate ESP32 TTGO board with in built 1.16" TFT display ('true' if used) // set 'true' for TTGO, false for 2.8" ILI9341 display // ============ GLOBAL VARIABLES ===================================================== TFT_eSPI tft = TFT_eSPI(); // display object Timezone local; // local timezone variable time_t t,oldT; // current & displayed UTC time_t lt,oldLt; // current & displayed local time bool useLocalTime = false; // temp flag used for display updates // ============ DISPLAY ROUTINES ===================================================== void showClockStatus() { int color; int x=290,y=1,w=28,h=29,f=2; //assume ILI9341 int values if (TTGO) { x=240,y=1,w=28,h=20,f=2; //TTGO ***** } if (second()%10) return; // update RSSI every 10 seconds int syncAge = now()-lastNtpUpdateTime(); // how long has it been since last sync? if (syncAge < SYNC_MARGINAL) // GREEN: time is good & in sync color = TFT_GREEN; else if (syncAge < SYNC_LOST) // ORANGE: sync is 1-24 hours old color = TFT_ORANGE; else color = TFT_RED; // RED: time is stale, over 24 hrs old if (WiFi.status()!=WL_CONNECTED) { // color = TFT_DARKGREY; // GRAY: WiFi connection was lost WiFi.disconnect(); // so drop current connection WiFi.begin(WIFI_SSID,WIFI_PWD); // and attempt to reconnect } if (!TTGO){ tft.fillRoundRect(x,y,w,h,10,color); // show clock status as a color ILI9341 tft.setTextColor(TFT_BLACK,color); tft.drawNumber(-WiFi.RSSI(),x+8,y+6,f); // WiFi strength as a positive value } else{ //tft.fillRoundRect(x,y,w,h,4,color); // not required for TTGO as too big a Rect TTGO } } void showTime(time_t t, bool hr12, int x, int y) { const int f=7; // screen font tft.setTextColor(TIMECOLOR, TFT_BLACK); // set time color int h=hour(t); int m=minute(t); int s=second(t); // get hours, minutes, and seconds if (hr12) { // if using 12hr time format, //if (DISPLAY_AMPM) showAMPM(h,x+220,y+14); // show AM/PM indicator TTGO if (DISPLAY_AMPM) showAMPM(h,x+220,y+14); // show AM/PM indicator ~ y +70 for UTC? ***TTGO******* if (h==0) h=12; // 00:00 becomes 12:00 if (h>12) h-=12; // 13:00 becomes 01:00 } if (h<10) { // is hour a single digit? if ((!hr12)||(LEADING_ZERO)) // 24hr format: always use leading 0 x+= tft.drawChar('0',x,y,f); // show leading zero for hours else { tft.setTextColor(TFT_BLACK,TFT_BLACK); // black on black text x+=tft.drawChar('8',x,y,f); // will erase the old digit tft.setTextColor(TIMECOLOR,TFT_BLACK); } } x+= tft.drawNumber(h,x,y,f); // hours x+= tft.drawChar(':',x,y,f); // show ":" if (m<10) x+= tft.drawChar('0',x,y,f); // leading zero for minutes x+= tft.drawNumber(m,x,y,f); // show minutes x+= tft.drawChar(':',x,y,f); // show ":" if (s<10) x+= tft.drawChar('0',x,y,f); // add leading zero for seconds x+= tft.drawNumber(s,x,y,f); // show seconds } void showDate(time_t t, int x, int y) { const char* months[] = {"JAN","FEB","MAR", "APR","MAY","JUN","JUL","AUG","SEP","OCT", "NOV","Dec"}; if (!TTGO){ const int f=4,yspacing=30; // screen font, spacing tft.setTextColor(DATECOLOR, TFT_BLACK); int m=month(t), d=day(t); // get date components tft.fillRect(x,y,50,60,TFT_BLACK); // erase previous date TTGO not used ! ? tft.drawString(months[m-1],x,y,f); // show month on top y += yspacing; // put day below month if (d<10) x+=tft.drawNumber(0,x,y,f); // draw leading zero for day //tft.drawNumber(d,160,3,f); // draw day tft.drawNumber(d,x,y,f); } else { //for TTGO //Serial.println (x); //Serial.println(y); const int f=2,yspacing=0; // screen font smaller for TTGO, spacing offset = 0 tft.setTextColor(DATECOLOR, TFT_BLACK); int m=month(t), d=day(t); // get date components tft.fillRect(x-20,y,50,15,TFT_BLACK); // erase previous date tft.drawString(months[m-1],x,y,f); // show month on top ///tft.drawString(months[m-1],x+10,y,f); // show month on top and moved to right !! if (d<10) x+=tft.drawNumber(0,x-20,y,f); // draw leading zero for day x-10 (was just 'x', vk3pe) tft.drawNumber(d,x-17,y,f); // draw day, TTGO } } void showTimeZone (int x, int y) { const int f=4; // text font TTGO use font 4 ie small tft.setTextColor(LABEL_FGCOLOR,LABEL_BGCOLOR); // set text colors if (!TTGO){ tft.fillRect(x,y,80,28,LABEL_BGCOLOR); // erase previous TZ <<<<< chnaged this for ttgo if (!useLocalTime) tft.drawString("UTC",x,y,f); // UTC time //tft.drawString("UTC",10,69,2); // UTC time TTGO else tft.drawString(local.getTimezoneName(),x,y,f); // show local time zone //tft.drawString(local.getTimezoneName(),10,2,2); // show local time zone eg "AEDT" for Melbourne Aust. TTGO } else { //tft.fillRect(x,y,80,28,LABEL_BGCOLOR); // erase previous TZ <<<<<<< chnaged this for ttgo if (!useLocalTime) //tft.drawString("UTC",x,y,f); // UTC time tft.drawString("UTC",10,69,2); // UTC time TTGO else //tft.drawString(local.getTimezoneName(),x,y,f); // show local time zone tft.drawString(local.getTimezoneName(),10,2,2); // show local time zone eg "AEDT" for Melbourne Aust. TTGO } } void showTimeDate(time_t t, time_t oldT, bool hr12, int x, int y) { if (!TTGO){ showTime(t,hr12,x,y); // display time HH:MM:SS if ((!oldT)||(hour(t)!=hour(oldT))) // did hour change? showTimeZone(x,y-42); // update time zone if ((!oldT)||(day(t)!=day(oldT))) // did date change? (Thanks John WA2FZW!) showDate(t,x+250,y); // update date not yet fixed for TTGO ?? and UTC date //Serial.println(y); } else { // TTGO showTime(t,hr12,x,y); // display time HH:MM:SS if ((!oldT)||(hour(t)!=hour(oldT))) // did hour change? showTimeZone(x,y-4); // update time zone UTC or AEDT etc if ((!oldT)||(day(t)!=day(oldT))) // did date change? (Thanks John WA2FZW!) showDate(t,x+173,y-15); // update date for TTGO //Serial.println(y-15); } } void updateDisplay() { t = now(); // check latest time if (!TTGO){ if (t!=oldT) { // are we in a new second yet? lt = local.now(); // keep local time current useLocalTime = true; // use local timezone showTimeDate(lt,oldLt,LOCAL_FORMAT_12HR,10,46);// show new local time useLocalTime = false; // use UTC timezone showTimeDate(t,oldT,UTC_FORMAT_12HR,10,172); // show new UTC time showClockStatus(); // and clock status printTime(); // send timestamp to serial port oldT=t; oldLt=lt; // remember currently displayed time } } else { if (t!=oldT) { // are we in a new second yet? lt = local.now(); // keep local time current useLocalTime = true; // use local timezone //showTimeDate(lt,oldLt,LOCAL_FORMAT_12HR,10,46);// show new local time <<<<<<< sets x,y for other operations called below ????????? showTimeDate(lt,oldLt,LOCAL_FORMAT_12HR,7,17);// show new local time TTGO eg 4,20 4 = x location? 20 = Y location (ie top of number location) useLocalTime = false; // use UTC timezone //showTimeDate(t,oldT,UTC_FORMAT_12HR,10,172); // show new UTC time TTGO //>showTimeDate(t,oldT,UTC_FORMAT_12HR,7,84); // show new UTC time was T_12HR,7,80); showTimeDate(t,oldT,UTC_FORMAT_12HR,7,84); // show new UTC time gp <<<<<<< sets x,y for other operations called showClockStatus(); // and clock status printTime(); // send timestamp to serial port oldT=t; oldLt=lt; // remember currently displayed time } } } void newDualScreen() { if (!TTGO){ tft.fillScreen(TFT_BLACK); // start with empty screen tft.fillRoundRect(0,0,319,32,10,LABEL_BGCOLOR); // title bar for local time ,319, 32 width and height of rectangle? tft.fillRoundRect(0,126,319,32,10,LABEL_BGCOLOR);// title bar for UTC tft.setTextColor(LABEL_FGCOLOR,LABEL_BGCOLOR); // set label colors tft.drawCentreString(TITLE,160,4,4); // show title at top tft.drawRoundRect(0,0,319,110,10,TFT_WHITE); // draw edge around local time tft.drawRoundRect(0,126,319,110,10,TFT_WHITE); // draw edge around UTC } else { tft.fillScreen(TFT_BLACK); // start with empty screen TTGO //tft.fillRoundRect(0,0,239,16,4,LABEL_BGCOLOR); // title bar for local time //tft.fillRoundRect(0,63,239,16,20,LABEL_BGCOLOR);// title bar for UTC tft.setTextColor(LABEL_FGCOLOR,LABEL_BGCOLOR); // set label colors //tft.drawCentreString(TITLE,120,2,2); // show title at top ie "NTP TIME" if TTGO, screen removed (tidier) ///tft.drawRoundRect(0,0,240,62,10,TFT_WHITE); // draw edge around local time *** ///tft.drawRoundRect(0,68,240,62,10,TFT_WHITE); // draw edge around UTC *** tft.drawRoundRect(0,0,240,66,10,TFT_WHITE); // draw edge around local time *** //tft.drawRoundRect(0,68,240,62,10,TFT_WHITE); // draw edge around UTC tft.drawRoundRect(0,68,240,66,10,TFT_WHITE); // draw edge around UTC move down more } } void startupScreen() { if (!TTGO){ tft.fillScreen(TFT_BLACK); // start with empty screen tft.fillRoundRect(0,0,319,30,10,LABEL_BGCOLOR); // title bar tft.drawRoundRect(0,0,319,239,10,TFT_WHITE); // draw edge screen tft.setTextColor(LABEL_FGCOLOR,LABEL_BGCOLOR); // set label colors tft.drawCentreString(TITLE,160,2,4); // show sketch title on screen tft.setTextColor(LABEL_FGCOLOR, TFT_BLACK); // set text color } else { tft.fillScreen(TFT_BLACK); // start with empty screen TTGO // tft.fillRoundRect(0,0,239,30,10,LABEL_BGCOLOR); // title bar tft.drawRoundRect(0,0,239,134,10,TFT_WHITE); // draw edge screen tft.setTextColor(LABEL_FGCOLOR,LABEL_BGCOLOR); // set label colors tft.drawCentreString(TITLE,120,2,4); // show sketch title on screen tft.setTextColor(LABEL_FGCOLOR, TFT_BLACK); // set text color } } // ============ MISC ROUTINES ===================================================== void showConnectionProgress(){ int elapsed = 0; //tft.drawString("WiFi starting",5,50,4); //TTGO tft.drawString("WiFi starting",5,50,4); while (WiFi.status()!=WL_CONNECTED) { // while waiting for connection //tft.drawNumber(elapsed++,230,50,4); // show we are trying!n TTGO tft.drawNumber(elapsed++,210,50,4); // show we are trying! was 220, delay(1000); } if (!TTGO){ tft.drawString("IP = " + // connected to LAN now WiFi.localIP().toString(),5,80,4); // so show IP address TTGO elapsed = 0; tft.drawString("Waiting for NTP",5,100,4); // Now get NTP info while (timeStatus()!=timeSet) { // wait until time retrieved events(); // allow ezTime to work tft.drawNumber(elapsed++,220,100,4); // show we are trying } } else { tft.drawString("IP = " + // connected to LAN now WiFi.localIP().toString(),5,70,4); // so show IP address elapsed = 0; tft.drawString("Waiting for NTP",5,100,4); // Now get NTP info } while (timeStatus()!=timeSet) { // wait until time retrieved events(); // allow ezTime to work //tft.drawNumber(elapsed++,220,100,4); // show we are trying tft.drawNumber(elapsed++,210,100,4); // show we are trying delay(2000); } } void printTime() { // print time to serial port if (!PRINTED_TIME) return; // option 0: dont print Serial.print("TIME: "); if (PRINTED_TIME==1) Serial.println(dateTime(TIME_FORMAT)); // option 1: print UTC time else Serial.println(local.dateTime(TIME_FORMAT)); // option 2: print local time } // ============ MAIN PROGRAM =================================================== void setup() { tft.init(); // initialize LCD screen object tft.setRotation(SCREEN_ORIENTATION); // landscape screen orientation startupScreen(); // show title Serial.begin(BAUDRATE); // open serial port setDebug(DEBUGLEVEL); // enable NTP debug info setServer(NTP_SERVER); // set NTP server WiFi.begin(WIFI_SSID, WIFI_PWD); // start WiFi showConnectionProgress(); // WiFi and NTP may take time local.setPosix(TZ_RULE); // estab. local TZ by rule newDualScreen(); // show title & labels } void loop() { events(); // get periodic NTP updates updateDisplay(); // update clock every second }