├── .gitattributes ├── LICENSE ├── NTP_DualClock └── NTP_DualClock.ino ├── PCB ├── BN32 │ ├── BN32 gerbers.zip │ └── BN32 layout.jpg ├── BN33 │ ├── BN33 gerbers.zip │ └── BN33 layout.jpg └── WA2FZW │ ├── NTP CLock V1.1.zip │ └── NTP Clock PCB.pdf ├── README.md ├── Tutorials ├── step1 │ └── step1.ino ├── step3 │ └── step3.ino ├── step4 │ └── step4.ino ├── step5 │ └── step5.ino ├── step6 │ └── step6.ino ├── step7 │ └── step7.ino └── step8 │ └── step8.ino └── ntp_clock.jpg /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Bruce E. Hall 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NTP_DualClock/NTP_DualClock.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | Title: NTP Dual Clock 3 | Author: Bruce E. Hall, w8bh.net 4 | Date: 13 Feb 2021 5 | Hardware: HiLetGo ESP32 or D1 Mini ESP8266, ILI9341 TFT display 6 | Software: Arduino IDE 1.8.13 with Expressif ESP32 package 7 | TFT_eSPI Library 8 | ezTime Library 9 | Legal: Copyright (c) 2021 Bruce E. Hall. 10 | Open Source under the terms of the MIT License. 11 | 12 | Description: Dual UTC/Local NTP Clock with TFT display 13 | Time is refreshed via NTP every 30 minutes 14 | Optional time output to serial port 15 | Status indicator for time freshness & WiFi strength 16 | 17 | Before using, please update WIFI_SSID and WIFI_PWD 18 | with your personal WiFi credentials. Also, modify 19 | TZ_RULE with your own Posix timezone string. 20 | 21 | see w8bh.net for a detailled, step-by-step tutorial 22 | 23 | Version Hx: 11/27/20 Initial GitHub commit 24 | 11/28/20 added code to handle dropped WiFi connection 25 | 11/30/20 showTimeDate() mod by John Price (WA2FZW) 26 | 12/01/20 showAMPM() added by John Price (WA2FZW) 27 | 02/05/21 added support for ESP8266 modules 28 | 02/07/21 added day-above-month option 29 | 02/10/21 added date-leading-zero option 30 | 31 | **************************************************************************/ 32 | 33 | 34 | #include // https://github.com/Bodmer/TFT_eSPI 35 | #include // https://github.com/ropg/ezTime 36 | #if defined(ESP32) 37 | #include // use this WiFi lib for ESP32, or 38 | #elif defined (ESP8266) 39 | #include // use this WiFi lib for ESP8266 40 | #endif 41 | 42 | #define TITLE "NTP TIME" 43 | #define WIFI_SSID "yourSSID" 44 | #define WIFI_PWD "yourPASSWORD" 45 | #define NTP_SERVER "pool.ntp.org" // time.nist.gov, pool.ntp.org, etc 46 | 47 | #define TZ_RULE "EST5EDT,M3.2.0/2:00:00,M11.1.0/2:00:00" 48 | 49 | #define SCREEN_ORIENTATION 1 // screen portrait mode: use 1 or 3 50 | #define LED_PIN 2 // built-in LED is on GPIO 2 51 | #define DEBUGLEVEL INFO // NONE, ERROR, INFO, or DEBUG 52 | #define PRINTED_TIME 1 // 0=NONE, 1=UTC, or 2=LOCAL 53 | #define TIME_FORMAT COOKIE // COOKIE, ISO8601, RFC822, RFC850, RFC3339, RSS 54 | #define BAUDRATE 115200 // serial output baudrate 55 | #define SYNC_MARGINAL 3600 // orange status if no sync for 1 hour 56 | #define SYNC_LOST 86400 // red status if no sync for 1 day 57 | #define LOCAL_FORMAT_12HR true // local time format 12hr "11:34" vs 24hr "23:34" 58 | #define UTC_FORMAT_12HR false // UTC time format 12 hr "11:34" vs 24hr "23:34" 59 | #define DISPLAY_AMPM true // if true, show 'A' for AM, 'P' for PM 60 | #define HOUR_LEADING_ZERO false // "01:00" vs " 1:00" 61 | #define DATE_LEADING_ZERO true // "Feb 07" vs. "Feb 7" 62 | #define DATE_ABOVE_MONTH false // "12 Feb" vs. "Feb 12" 63 | #define TIMECOLOR TFT_CYAN // color of 7-segment time display 64 | #define DATECOLOR TFT_YELLOW // color of displayed month & day 65 | #define LABEL_FGCOLOR TFT_YELLOW // color of label text 66 | #define LABEL_BGCOLOR TFT_BLUE // color of label background 67 | 68 | 69 | // ============ GLOBAL VARIABLES ===================================================== 70 | 71 | TFT_eSPI tft = TFT_eSPI(); // display object 72 | Timezone local; // local timezone variable 73 | time_t t,oldT; // current & displayed UTC 74 | time_t lt,oldLt; // current & displayed local time 75 | bool useLocalTime = false; // temp flag used for display updates 76 | 77 | 78 | // ============ DISPLAY ROUTINES ===================================================== 79 | 80 | void showClockStatus() { 81 | const int x=290,y=1,w=28,h=29,f=2; // screen position & size 82 | int color; 83 | if (second()%10) return; // update every 10 seconds 84 | int syncAge = now()-lastNtpUpdateTime(); // how long has it been since last sync? 85 | if (syncAge < SYNC_MARGINAL) // GREEN: time is good & in sync 86 | color = TFT_GREEN; 87 | else if (syncAge < SYNC_LOST) // ORANGE: sync is 1-24 hours old 88 | color = TFT_ORANGE; 89 | else color = TFT_RED; // RED: time is stale, over 24 hrs old 90 | if (WiFi.status()!=WL_CONNECTED) { // 91 | color = TFT_DARKGREY; // GRAY: WiFi connection was lost 92 | WiFi.disconnect(); // so drop current connection 93 | WiFi.begin(WIFI_SSID,WIFI_PWD); // and attempt to reconnect 94 | } 95 | tft.fillRoundRect(x,y,w,h,10,color); // show clock status as a color 96 | tft.setTextColor(TFT_BLACK,color); 97 | tft.drawNumber(-WiFi.RSSI(),x+8,y+6,f); // WiFi strength as a positive value 98 | } 99 | 100 | /* 101 | * Modified by John Price (WA2FZW) 102 | * 103 | * In the original code, this was an empty function. I added code to display either 104 | * an 'A' or 'P' to the right of the local time 105 | * 106 | */ 107 | 108 | void showAMPM ( int hr, int x, int y ) 109 | { 110 | char ampm; // Will be either 'A' or 'P' 111 | if ( hr <= 11 ) // If the hour is 11 or less 112 | ampm = 'A'; // It's morning 113 | else // Otherwise, 114 | ampm = 'P'; // It's afternoon 115 | tft.drawChar ( ampm, x, y, 4 ); // Show AM/PM indicator 116 | } 117 | 118 | void showTime(time_t t, bool hr12, int x, int y) { 119 | const int f=7; // screen font 120 | tft.setTextColor(TIMECOLOR, TFT_BLACK); // set time color 121 | int h=hour(t); int m=minute(t); int s=second(t); // get hours, minutes, and seconds 122 | if (hr12) { // if using 12hr time format, 123 | if (DISPLAY_AMPM) showAMPM(h,x+220,y+14); // show AM/PM indicator 124 | if (h==0) h=12; // 00:00 becomes 12:00 125 | if (h>12) h-=12; // 13:00 becomes 01:00 126 | } 127 | if (h<10) { // is hour a single digit? 128 | if ((!hr12)||(HOUR_LEADING_ZERO)) // 24hr format: always use leading 0 129 | x+= tft.drawChar('0',x,y,f); // show leading zero for hours 130 | else { 131 | tft.setTextColor(TFT_BLACK,TFT_BLACK); // black on black text 132 | x+=tft.drawChar('8',x,y,f); // will erase the old digit 133 | tft.setTextColor(TIMECOLOR,TFT_BLACK); 134 | } 135 | } 136 | x+= tft.drawNumber(h,x,y,f); // hours 137 | x+= tft.drawChar(':',x,y,f); // show ":" 138 | if (m<10) x+= tft.drawChar('0',x,y,f); // leading zero for minutes 139 | x+= tft.drawNumber(m,x,y,f); // show minutes 140 | x+= tft.drawChar(':',x,y,f); // show ":" 141 | if (s<10) x+= tft.drawChar('0',x,y,f); // add leading zero for seconds 142 | x+= tft.drawNumber(s,x,y,f); // show seconds 143 | } 144 | 145 | void showDate(time_t t, int x, int y) { 146 | const int f=4,yspacing=30; // screen font, spacing 147 | const char* months[] = {"JAN","FEB","MAR", 148 | "APR","MAY","JUN","JUL","AUG","SEP","OCT", 149 | "NOV","DEC"}; 150 | tft.setTextColor(DATECOLOR, TFT_BLACK); 151 | int i=0, m=month(t), d=day(t); // get date components 152 | tft.fillRect(x,y,50,60,TFT_BLACK); // erase previous date 153 | if (DATE_ABOVE_MONTH) { // show date on top ----- 154 | if ((DATE_LEADING_ZERO) && (d<10)) // do we need a leading zero? 155 | i = tft.drawNumber(0,x,y,f); // draw leading zero 156 | tft.drawNumber(d,x+i,y,f); // draw date 157 | y += yspacing; // and below it, the month 158 | tft.drawString(months[m-1],x,y,f); // draw month 159 | } else { // put month on top ---- 160 | tft.drawString(months[m-1],x,y,f); // draw month 161 | y += yspacing; // and below it, the date 162 | if ((DATE_LEADING_ZERO) && (d<10)) // do we need a leading zero? 163 | x+=tft.drawNumber(0,x,y,f); // draw leading zero 164 | tft.drawNumber(d,x,y,f); // draw date 165 | } 166 | } 167 | 168 | void showTimeZone (int x, int y) { 169 | const int f=4; // text font 170 | tft.setTextColor(LABEL_FGCOLOR,LABEL_BGCOLOR); // set text colors 171 | tft.fillRect(x,y,80,28,LABEL_BGCOLOR); // erase previous TZ 172 | if (!useLocalTime) 173 | tft.drawString("UTC",x,y,f); // UTC time 174 | else 175 | tft.drawString(local.getTimezoneName(),x,y,f); // show local time zone 176 | } 177 | 178 | void showTimeDate(time_t t, time_t oldT, bool hr12, int x, int y) { 179 | showTime(t,hr12,x,y); // display time HH:MM:SS 180 | if ((!oldT)||(hour(t)!=hour(oldT))) // did hour change? 181 | showTimeZone(x,y-42); // update time zone 182 | if ((!oldT)||(day(t)!=day(oldT))) // did date change? (Thanks John WA2FZW!) 183 | showDate(t,x+250,y); // update date 184 | } 185 | 186 | void updateDisplay() { 187 | t = now(); // check latest time 188 | if (t!=oldT) { // are we in a new second yet? 189 | lt = local.now(); // keep local time current 190 | useLocalTime = true; // use local timezone 191 | showTimeDate(lt,oldLt,LOCAL_FORMAT_12HR,10,46);// show new local time 192 | useLocalTime = false; // use UTC timezone 193 | showTimeDate(t,oldT,UTC_FORMAT_12HR,10,172); // show new UTC time 194 | showClockStatus(); // and clock status 195 | printTime(); // send timestamp to serial port 196 | oldT=t; oldLt=lt; // remember currently displayed time 197 | } 198 | } 199 | 200 | void newDualScreen() { 201 | tft.fillScreen(TFT_BLACK); // start with empty screen 202 | tft.fillRoundRect(0,0,319,32,10,LABEL_BGCOLOR); // title bar for local time 203 | tft.fillRoundRect(0,126,319,32,10,LABEL_BGCOLOR);// title bar for UTC 204 | tft.setTextColor(LABEL_FGCOLOR,LABEL_BGCOLOR); // set label colors 205 | tft.drawCentreString(TITLE,160,4,4); // show title at top 206 | tft.drawRoundRect(0,0,319,110,10,TFT_WHITE); // draw edge around local time 207 | tft.drawRoundRect(0,126,319,110,10,TFT_WHITE); // draw edge around UTC 208 | } 209 | 210 | void startupScreen() { 211 | tft.fillScreen(TFT_BLACK); // start with empty screen 212 | tft.fillRoundRect(0,0,319,30,10,LABEL_BGCOLOR); // title bar 213 | tft.drawRoundRect(0,0,319,239,10,TFT_WHITE); // draw edge screen 214 | tft.setTextColor(LABEL_FGCOLOR,LABEL_BGCOLOR); // set label colors 215 | tft.drawCentreString(TITLE,160,2,4); // show sketch title on screen 216 | tft.setTextColor(LABEL_FGCOLOR, TFT_BLACK); // set text color 217 | } 218 | 219 | 220 | // ============ MISC ROUTINES ===================================================== 221 | 222 | void showConnectionProgress(){ 223 | int elapsed = 0; 224 | tft.drawString("WiFi starting",5,50,4); 225 | while (WiFi.status()!=WL_CONNECTED) { // while waiting for connection 226 | tft.drawNumber(elapsed++,230,50,4); // show we are trying! 227 | delay(1000); 228 | } 229 | tft.drawString("IP = " + // connected to LAN now 230 | WiFi.localIP().toString(),5,80,4); // so show IP address 231 | elapsed = 0; 232 | tft.drawString("Waiting for NTP",5,140,4); // Now get NTP info 233 | while (timeStatus()!=timeSet) { // wait until time retrieved 234 | events(); // allow ezTime to work 235 | tft.drawNumber(elapsed++,230,140,4); // show we are trying 236 | delay(1000); 237 | } 238 | } 239 | 240 | void printTime() { // print time to serial port 241 | if (!PRINTED_TIME) return; // option 0: dont print 242 | Serial.print("TIME: "); 243 | if (PRINTED_TIME==1) 244 | Serial.println(dateTime(TIME_FORMAT)); // option 1: print UTC time 245 | else 246 | Serial.println(local.dateTime(TIME_FORMAT)); // option 2: print local time 247 | } 248 | 249 | void blink(int count=1) { // diagnostic LED blink 250 | pinMode(LED_PIN,OUTPUT); // make sure pin is an output 251 | for (int i=0; i ESP32 Arduino > Esp32 Dev Module 11 | Upload Speed: 921600 12 | Port: (Set port used by onboard Silicon Labs UART) 13 | 14 | Helpful Tip: Add a 1uF electrolytic cap between the RESET pin and ground 15 | to enable auto-boot. Otherwise, you will have to hold 16 | down the boot button every time you upload code. 17 | 18 | Description: Blink sketch; Tests basic microcontroller connections. 19 | Require no external components. 20 | 21 | **************************************************************************/ 22 | 23 | 24 | #define LED 2 // onboard LED on GPIO2 25 | 26 | void setup() { 27 | pinMode(LED,OUTPUT); // LED pin is an output 28 | } 29 | 30 | void loop() { 31 | digitalWrite(LED,HIGH); // turn LED on 32 | delay(500); // for 0.5 seconds. 33 | digitalWrite(LED,LOW); // then turn LED off 34 | delay(500); // for 0.5 seconds. 35 | } 36 | -------------------------------------------------------------------------------- /Tutorials/step3/step3.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | Title: NTP Clock, step 3 (there is no step 2) 3 | Author: Bruce E. Hall, w8bh.net 4 | Date: 05 Feb 2021 5 | Hardware: HiLetGo ESP32; 2.8" ILI9341 TFT 6 | Software: Arduino IDE 1.8.13, ESP32 boards by Expressif; TFT_eSPI Library 7 | Legal: Copyright (c) 2021 Bruce E. Hall. 8 | Open Source under the terms of the MIT License. 9 | 10 | Description: Hello, World! 11 | Blinking onboard LED confirms ESP32 config & code upload. 12 | Display confirms proper wiring & TFT_eSPI library config. 13 | 14 | Connections: Display ESP32 DevKitC 15 | ------- ------------- 16 | 1 Vcc "5V" 17 | 2 Gnd "GND" 18 | 3 CS GPIO 5 19 | 4 RST "3v3" or (10K resistor to 5V) 20 | 5 DC GPIO 21 21 | 6 MOSI GPIO 23 22 | 7 SCK GPIO 5 23 | 8 LED "5V" 24 | 9 MISO n/c 25 | 26 | **************************************************************************/ 27 | 28 | #include 29 | #define TITLE "Hello, World!" 30 | #define LED 2 // LED is on GPIO Pin 2 31 | #define SCREEN_ROTATION 3 // landscape: use '1' or '3' 32 | 33 | TFT_eSPI tft = TFT_eSPI(); // display object 34 | 35 | void setup() { 36 | pinMode(LED,OUTPUT); // pin for onboard LED 37 | tft.init(); 38 | tft.setRotation(SCREEN_ROTATION); // landscape screen orientation 39 | tft.fillScreen(TFT_BLUE); // start with empty screen 40 | tft.setTextColor(TFT_YELLOW); // yellow on blue text 41 | tft.drawString(TITLE,50,50,4); // display the text 42 | } 43 | 44 | void loop() { 45 | digitalWrite(LED,HIGH); // turn on LED 46 | delay(200); // for 0.2 seconds 47 | digitalWrite(LED,LOW); // turn off LED 48 | delay(200); // for 0.2 seconds 49 | } 50 | -------------------------------------------------------------------------------- /Tutorials/step4/step4.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | Title: NTP Clock, step 4 3 | Author: Bruce E. Hall, w8bh.net 4 | Date: 13 Feb 2021 5 | Hardware: ESP8266 or HiLetGo ESP32, 2.8" ILI9341 TFT 6 | Software: Arduino IDE 1.8.13 with Expressif ESP32 package 7 | TFT_eSPI Library 8 | ezTime Library 9 | Legal: Copyright (c) 2021 Bruce E. Hall. 10 | Open Source under the terms of the MIT License. 11 | 12 | Description: Added ezTime Library 13 | See previous step for ESP32/display connections. 14 | 15 | **************************************************************************/ 16 | 17 | 18 | #include // https://github.com/Bodmer/TFT_eSPI 19 | #include // https://github.com/ropg/ezTime 20 | #if defined(ESP32) 21 | #include // use this WiFi lib for ESP32, or 22 | #elif defined (ESP8266) 23 | #include // use this WiFi lib for ESP8266 24 | #endif 25 | 26 | #define WIFI_SSID "yourSSID" 27 | #define WIFI_PWD "yourPassword" 28 | #define TZ_RULE "EST5EDT,M3.2.0/2:00:00,M11.1.0/2:00:00" 29 | //#define TZ_NAME "US/Eastern" // zone name in TZ database 30 | 31 | TFT_eSPI tft = TFT_eSPI(); // display object 32 | Timezone local; // local timezone variable 33 | 34 | void showNetworkStartup() { 35 | Serial.print("WiFi starting"); 36 | while (WiFi.status()!=WL_CONNECTED) { // while waiting for connection 37 | Serial.print("."); // show we are trying! 38 | delay(300); 39 | } 40 | Serial.print("\nWiFi connected. IP = "); // Connected to LAN now 41 | Serial.println(WiFi.localIP()); // so show IP address 42 | } 43 | 44 | void setup() { 45 | tft.init(); 46 | tft.setRotation(1); // portrait screen orientation 47 | tft.fillScreen(TFT_BLACK); // start with empty screen 48 | Serial.begin(115200); // open serial port 49 | WiFi.begin(WIFI_SSID,WIFI_PWD); // attempt WiFi connection 50 | showNetworkStartup(); // show progress 51 | waitForSync(); // wait for NTP packet return 52 | Serial.println("UTC: "+UTC.dateTime()); // display UTC time 53 | //local.setLocation(TZ_NAME); // estab. local TZ by name 54 | local.setPosix(TZ_RULE); // estab. local TZ by rule 55 | } 56 | 57 | void loop() { 58 | events(); // refresh time every 30 min 59 | if (secondChanged()) // is it a new second yet? 60 | tft.drawString(local.dateTime("h:i:s"), // display time on screen 61 | 50,50,4); 62 | Serial.println("Local: "+local.dateTime()); // if so, display local time 63 | } 64 | -------------------------------------------------------------------------------- /Tutorials/step5/step5.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | Title: NTP Clock, step 5 3 | Author: Bruce E. Hall, w8bh.net 4 | Date: 13 Feb 2021 5 | Hardware: ESP8266 or HiLetGo ESP32, 2.8" ILI9341 TFT 6 | Software: Arduino IDE 1.8.13 with Expressif ESP32 package 7 | TFT_eSPI Library 8 | ezTime Library 9 | Legal: Copyright (c) 2021 Bruce E. Hall. 10 | Open Source under the terms of the MIT License. 11 | 12 | Description: Step 5 of building an NTP-based clock with TFT display. 13 | This step improves display formatting. 14 | 15 | see w8bh.net for a detailled, step-by-step tutorial 16 | 17 | **************************************************************************/ 18 | 19 | 20 | #include // https://github.com/Bodmer/TFT_eSPI 21 | #include // https://github.com/ropg/ezTime 22 | #if defined(ESP32) 23 | #include // use this WiFi lib for ESP32, or 24 | #elif defined (ESP8266) 25 | #include // use this WiFi lib for ESP8266 26 | #endif 27 | 28 | #define WIFI_SSID "yourSSID" 29 | #define WIFI_PWD "yourPassword" 30 | #define TZ_RULE "EST5EDT,M3.2.0/2:00:00,M11.1.0/2:00:00" 31 | 32 | #define TITLE "NTP TIME (UTC)" 33 | #define TIMECOLOR TFT_CYAN // color of 7-segment time display 34 | #define DATECOLOR TFT_YELLOW // color of displayed month & day 35 | #define LABEL_FGCOLOR TFT_YELLOW 36 | #define LABEL_BGCOLOR TFT_BLUE 37 | 38 | TFT_eSPI tft = TFT_eSPI(); // display object 39 | Timezone local; // local timezone variable 40 | time_t t; 41 | 42 | void displayTime() { 43 | int x=10, y=50, f=7; // screen position & font 44 | tft.setTextColor(TIMECOLOR, TFT_BLACK); // set time color 45 | int h=hour(t); int m=minute(t); int s=second(t); // get hours, minutes, and seconds 46 | if (h<10) x+= tft.drawChar('0',x,y,f); // leading zero for hours 47 | x+= tft.drawNumber(h,x,y,f); // hours 48 | x+= tft.drawChar(':',x,y,f); // hour:min separator 49 | if (m<10) x+= tft.drawChar('0',x,y,f); // leading zero for minutes 50 | x+= tft.drawNumber(m,x,y,f); // show minutes 51 | x+= tft.drawChar(':',x,y,f); // show ":" 52 | if (s<10) x+= tft.drawChar('0',x,y,f); // add leading zero if needed 53 | x+= tft.drawNumber(s,x,y,f); // show seconds 54 | } 55 | 56 | void displayDate() { 57 | int x=50,y=130,f=4; // screen position & font 58 | const char* days[] = {"Sunday","Monday","Tuesday", 59 | "Wednesday","Thursday","Friday","Saturday"}; 60 | tft.setTextColor(DATECOLOR, TFT_BLACK); 61 | tft.fillRect(x,y,265,26,TFT_BLACK); // erase previous date 62 | x+=tft.drawString(days[weekday()-1],x,y,f); // show day of week 63 | x+=tft.drawString(", ",x,y,f); // and 64 | x+=tft.drawNumber(month(),x,y,f); // show date as month/day/year 65 | x+=tft.drawChar('/',x,y,f); 66 | x+=tft.drawNumber(day(),x,y,f); 67 | x+=tft.drawChar('/',x,y,f); 68 | x+=tft.drawNumber(year(),x,y,f); 69 | } 70 | 71 | void updateDisplay() { 72 | if (t!=now()) { // is it a new second yet? 73 | displayTime(); // and display it 74 | if (day(t)!=day()) // did date change? 75 | displayDate(); // yes, so display it 76 | t=now(); // Remember current time 77 | } 78 | } 79 | 80 | void newScreen() { 81 | tft.fillScreen(TFT_BLACK); // start with empty screen 82 | tft.fillRoundRect(2,6,316,32,10,LABEL_BGCOLOR); // put title bar at top 83 | tft.drawRoundRect(2,6,316,234,10,TFT_WHITE); // draw edge around screen 84 | tft.setTextColor(LABEL_FGCOLOR,LABEL_BGCOLOR); // set label colors 85 | tft.drawCentreString(TITLE,160,12,4); // show title at top 86 | } 87 | 88 | void setup() { 89 | tft.init(); 90 | tft.setRotation(1); // portrait screen orientation 91 | newScreen(); 92 | WiFi.begin(WIFI_SSID,WIFI_PWD); // attempt WiFi connection 93 | waitForSync(); // wait for NTP packet return 94 | local.setPosix(TZ_RULE); // estab. local TZ by rule 95 | } 96 | 97 | void loop() { 98 | events(); // allow NTP updates 99 | updateDisplay(); // update clock every second 100 | } 101 | -------------------------------------------------------------------------------- /Tutorials/step6/step6.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | Title: NTP Clock, step 6 3 | Author: Bruce E. Hall, w8bh.net 4 | Date: 13 Feb 2021 5 | Hardware: ESP8266 or HiLetGo ESP32 6 | Software: Arduino IDE 1.8.13 with Expressif ESP32 package 7 | TFT_eSPI Library 8 | ezTime Library 9 | Legal: Copyright (c) 2021 Bruce E. Hall. 10 | Open Source under the terms of the MIT License. 11 | 12 | Description: Step 6 of building an NTP-based clock with TFT display. 13 | This step displays local time & date, in 12 hour format, 14 | with automatic DST adjustment. 15 | 16 | see w8bh.net for a detailled, step-by-step tutorial 17 | 18 | **************************************************************************/ 19 | 20 | 21 | #include // https://github.com/Bodmer/TFT_eSPI 22 | #include // https://github.com/ropg/ezTime 23 | #if defined(ESP32) 24 | #include // use this WiFi lib for ESP32, or 25 | #elif defined (ESP8266) 26 | #include // use this WiFi lib for ESP8266 27 | #endif 28 | 29 | #define TITLE "NTP TIME" 30 | #define WIFI_SSID "yourSSID" 31 | #define WIFI_PWD "yourPassword" 32 | #define TZ_RULE "EST5EDT,M3.2.0/2:00:00,M11.1.0/2:00:00" 33 | 34 | #define USE_12HR_FORMAT true // preferred format for local time 35 | #define LEADING_ZERO false // show "01:00" vs " 1:00" 36 | 37 | #define TIMECOLOR TFT_CYAN // color of 7-segment time display 38 | #define DATECOLOR TFT_YELLOW // color of displayed month & day 39 | #define LABEL_FGCOLOR TFT_YELLOW 40 | #define LABEL_BGCOLOR TFT_BLUE 41 | 42 | TFT_eSPI tft = TFT_eSPI(); // display object 43 | Timezone local; // local timezone variable 44 | time_t t,lt; 45 | 46 | 47 | void showAMPM (int hr) { 48 | int x=250,y=90,ft=4; // screen position & font 49 | tft.setTextColor(TIMECOLOR,TFT_BLACK); // use same color as time 50 | if (!USE_12HR_FORMAT) 51 | tft.fillRect(x,y,50,20,TFT_BLACK); // 24hr display, so no AM/PM 52 | else if (hr<12) 53 | tft.drawString("AM",x,y,ft); // before noon, so draw AM 54 | else 55 | tft.drawString("PM",x,y,ft); // after noon, so draw PM 56 | } 57 | 58 | void displayTime(time_t t) { 59 | int x=20, y=65, f=7; // screen position & font 60 | tft.setTextColor(TIMECOLOR, TFT_BLACK); // set time color 61 | int h=hour(t); int m=minute(t); int s=second(t); // get hours, minutes, and seconds 62 | showAMPM(h); // display AM/PM, if needed 63 | if (USE_12HR_FORMAT) { // adjust hours for 12 vs 24hr format: 64 | if (h==0) h=12; // 00:00 becomes 12:00 65 | if (h>12) h-=12; // 13:00 becomes 01:00 66 | } 67 | if (h<10) { // is hour a single digit? 68 | if ((!USE_12HR_FORMAT)||(LEADING_ZERO)) // 24hr format: always use leading 0 69 | x+= tft.drawChar('0',x,y,f); // show leading zero for hours 70 | else { 71 | tft.setTextColor(TFT_BLACK,TFT_BLACK); // black on black text 72 | x+=tft.drawChar('8',x,y,f); // will erase the old digit 73 | tft.setTextColor(TIMECOLOR,TFT_BLACK); // restore time color 74 | } 75 | } 76 | x+= tft.drawNumber(h,x,y,f); // hours 77 | x+= tft.drawChar(':',x,y,f); // hour:min separator 78 | if (m<10) x+= tft.drawChar('0',x,y,f); // leading zero for minutes 79 | x+= tft.drawNumber(m,x,y,f); // show minutes 80 | x+= tft.drawChar(':',x,y,f); // show ":" 81 | if (s<10) x+= tft.drawChar('0',x,y,f); // add leading zero if needed 82 | x+= tft.drawNumber(s,x,y,f); // show seconds 83 | } 84 | 85 | void displayDate(time_t t) { 86 | int x=20,y=130,f=4; // screen position & font 87 | const char* days[] = {"Sunday","Monday","Tuesday", 88 | "Wednesday","Thursday","Friday","Saturday"}; 89 | tft.setTextColor(DATECOLOR, TFT_BLACK); 90 | tft.fillRect(x,y,265,26,TFT_BLACK); // erase previous date 91 | x+=tft.drawString(days[weekday()-1],x,y,f); // show day of week 92 | x+=tft.drawString(", ",x,y,f); // and 93 | x+=tft.drawNumber(month(),x,y,f); // show date as month/day/year 94 | x+=tft.drawChar('/',x,y,f); 95 | x+=tft.drawNumber(day(),x,y,f); 96 | x+=tft.drawChar('/',x,y,f); 97 | x+=tft.drawNumber(year(),x,y,f); 98 | } 99 | 100 | void updateDisplay() { 101 | if (t!=now()) { // is it a new second yet? 102 | time_t newLt = local.now(); // get local time 103 | displayTime(newLt); // and display it 104 | if (day(newLt)!=day(lt)) // did date change? 105 | displayDate(newLt); // yes, so display it 106 | t=now(); // remember current UTC 107 | lt = newLt; // remember current local time 108 | } 109 | } 110 | 111 | void newScreen() { 112 | tft.fillScreen(TFT_BLACK); // start with empty screen 113 | tft.fillRoundRect(2,6,316,32,10,LABEL_BGCOLOR); // put title bar at top 114 | tft.drawRoundRect(2,6,316,234,10,TFT_WHITE); // draw edge around screen 115 | tft.setTextColor(LABEL_FGCOLOR,LABEL_BGCOLOR); // set label colors 116 | tft.drawCentreString(TITLE,160,12,4); // show title at top 117 | } 118 | 119 | void setup() { 120 | tft.init(); 121 | tft.setRotation(1); // portrait screen orientation 122 | newScreen(); 123 | WiFi.begin(WIFI_SSID,WIFI_PWD); // attempt WiFi connection 124 | waitForSync(); // wait for NTP packet return 125 | local.setPosix(TZ_RULE); // estab. local TZ by rule 126 | } 127 | 128 | void loop() { 129 | events(); // allow NTP updates 130 | updateDisplay(); // update clock every second 131 | } 132 | -------------------------------------------------------------------------------- /Tutorials/step7/step7.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | Title: NTP Clock, step 7 3 | Author: Bruce E. Hall, w8bh.net 4 | Date: 13 Feb 2021 5 | Hardware: ESP8266 or HiLetGo ESP32, 2.8" ILI9341 TFT 6 | Software: Arduino IDE 1.8.13 with Expressif ESP32 package 7 | TFT_eSPI Library 8 | ezTime Library 9 | Legal: Copyright (c) 2021 Bruce E. Hall. 10 | Open Source under the terms of the MIT License. 11 | 12 | Description: Step 7 of building an NTP-based clock with TFT display. 13 | This step add an NTP status indicator. 14 | 15 | see w8bh.net for a detailled, step-by-step tutorial 16 | 17 | **************************************************************************/ 18 | 19 | 20 | #include // https://github.com/Bodmer/TFT_eSPI 21 | #include // https://github.com/ropg/ezTime 22 | #if defined(ESP32) 23 | #include // use this WiFi lib for ESP32, or 24 | #elif defined (ESP8266) 25 | #include // use this WiFi lib for ESP8266 26 | #endif 27 | 28 | #define TITLE "NTP TIME" 29 | #define WIFI_SSID "yourSSID" 30 | #define WIFI_PWD "yourPassword" 31 | #define TZ_RULE "EST5EDT,M3.2.0/2:00:00,M11.1.0/2:00:00" 32 | 33 | #define USE_12HR_FORMAT true // preferred format for local time 34 | #define LEADING_ZERO false // show "01:00" vs " 1:00" 35 | #define SYNC_MARGINAL 3600 // orange status if no sync for 1 hour 36 | #define SYNC_LOST 86400 // red status if no sync for 1 day 37 | 38 | #define TIMECOLOR TFT_CYAN // color of 7-segment time display 39 | #define DATECOLOR TFT_YELLOW // color of displayed month & day 40 | #define LABEL_FGCOLOR TFT_YELLOW 41 | #define LABEL_BGCOLOR TFT_BLUE 42 | 43 | TFT_eSPI tft = TFT_eSPI(); // display object 44 | Timezone local; // local timezone variable 45 | time_t t,oldT; // current & displayed UTC 46 | time_t lt,oldLt; // current & displayed local time 47 | 48 | 49 | void showClockStatus() { 50 | const int x=290,y=1,w=28,h=29,f=2; // screen position & size 51 | int color; 52 | if (second()%10) return; // update every 10 seconds 53 | int syncAge = now()-lastNtpUpdateTime(); // how long has it been since last sync? 54 | if (syncAge < SYNC_MARGINAL) // time is good & in sync 55 | color = TFT_GREEN; 56 | else if (syncAge < SYNC_LOST) // sync is 1-24 hours old 57 | color = TFT_ORANGE; 58 | else color = TFT_RED; // time is stale & should not be trusted 59 | tft.fillRoundRect(x,y,w,h,10,color); // show clock status as a color 60 | tft.setTextColor(TFT_BLACK,color); 61 | tft.drawNumber(-WiFi.RSSI(),x+8,y+6,f); // WiFi strength as a positive value 62 | } 63 | 64 | void showAMPM (int hr) { 65 | int x=250,y=90,ft=4; // screen position & font 66 | tft.setTextColor(TIMECOLOR,TFT_BLACK); // use same color as time 67 | if (!USE_12HR_FORMAT) 68 | tft.fillRect(x,y,50,20,TFT_BLACK); // 24hr display, so no AM/PM 69 | else if (hr<12) 70 | tft.drawString("AM",x,y,ft); // before noon, so draw AM 71 | else 72 | tft.drawString("PM",x,y,ft); // after noon, so draw PM 73 | } 74 | 75 | void displayTime(time_t t) { 76 | int x=20, y=65, f=7; // screen position & font 77 | tft.setTextColor(TIMECOLOR, TFT_BLACK); // set time color 78 | int h=hour(t); int m=minute(t); int s=second(t); // get hours, minutes, and seconds 79 | showAMPM(h); // display AM/PM, if needed 80 | if (USE_12HR_FORMAT) { // adjust hours for 12 vs 24hr format: 81 | if (h==0) h=12; // 00:00 becomes 12:00 82 | if (h>12) h-=12; // 13:00 becomes 01:00 83 | } 84 | if (h<10) { // is hour a single digit? 85 | if ((!USE_12HR_FORMAT)||(LEADING_ZERO)) // 24hr format: always use leading 0 86 | x+= tft.drawChar('0',x,y,f); // show leading zero for hours 87 | else { 88 | tft.setTextColor(TFT_BLACK,TFT_BLACK); // black on black text 89 | x+=tft.drawChar('8',x,y,f); // will erase the old digit 90 | tft.setTextColor(TIMECOLOR,TFT_BLACK); // restore time color 91 | } 92 | } 93 | x+= tft.drawNumber(h,x,y,f); // hours 94 | x+= tft.drawChar(':',x,y,f); // hour:min separator 95 | if (m<10) x+= tft.drawChar('0',x,y,f); // leading zero for minutes 96 | x+= tft.drawNumber(m,x,y,f); // show minutes 97 | x+= tft.drawChar(':',x,y,f); // show ":" 98 | if (s<10) x+= tft.drawChar('0',x,y,f); // add leading zero if needed 99 | x+= tft.drawNumber(s,x,y,f); // show seconds 100 | } 101 | 102 | void displayDate(time_t t) { 103 | int x=20,y=130,f=4; // screen position & font 104 | const char* days[] = {"Sunday","Monday","Tuesday", 105 | "Wednesday","Thursday","Friday","Saturday"}; 106 | tft.setTextColor(DATECOLOR, TFT_BLACK); 107 | tft.fillRect(x,y,265,26,TFT_BLACK); // erase previous date 108 | x+=tft.drawString(days[weekday()-1],x,y,f); // show day of week 109 | x+=tft.drawString(", ",x,y,f); // and 110 | x+=tft.drawNumber(month(),x,y,f); // show date as month/day/year 111 | x+=tft.drawChar('/',x,y,f); 112 | x+=tft.drawNumber(day(),x,y,f); 113 | x+=tft.drawChar('/',x,y,f); 114 | x+=tft.drawNumber(year(),x,y,f); 115 | } 116 | 117 | void updateDisplay() { 118 | t = now(); // check latest time 119 | if (t!=oldT) { // is it a new second yet? 120 | lt = local.now(); // get local time 121 | displayTime(lt); // and display it 122 | if (day(lt)!=day(oldLt)) // did date change? 123 | displayDate(lt); // yes, so display it 124 | showClockStatus(); // and clock status 125 | oldT=t; oldLt=lt; // remember currently displayed times 126 | } 127 | } 128 | 129 | void newScreen() { 130 | tft.fillScreen(TFT_BLACK); // start with empty screen 131 | tft.fillRoundRect(0,0,319,31,10,LABEL_BGCOLOR); // put title bar at top 132 | tft.drawRoundRect(0,0,319,239,10,TFT_WHITE); // draw edge around screen 133 | tft.setTextColor(LABEL_FGCOLOR,LABEL_BGCOLOR); // set label colors 134 | tft.drawCentreString(TITLE,160,4,4); // show title at top 135 | } 136 | 137 | void setup() { 138 | tft.init(); 139 | tft.setRotation(1); // portrait screen orientation 140 | newScreen(); 141 | WiFi.begin(WIFI_SSID,WIFI_PWD); // attempt WiFi connection 142 | waitForSync(); // wait for NTP packet return 143 | local.setPosix(TZ_RULE); // estab. local TZ by rule 144 | } 145 | 146 | void loop() { 147 | events(); // allow NTP updates 148 | updateDisplay(); // update clock every second 149 | } 150 | -------------------------------------------------------------------------------- /Tutorials/step8/step8.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | Title: NTP Clock, step 8 3 | Author: Bruce E. Hall, w8bh.net 4 | Date: 13 Feb 2021 5 | Hardware: HiLetGo ESP32, ESP8266, 2.8" ILI9341 TFT 6 | Software: Arduino IDE 1.8.13 with Expressif ESP32 package 7 | TFT_eSPI Library 8 | ezTime Library 9 | Legal: Copyright (c) 2021 Bruce E. Hall. 10 | Open Source under the terms of the MIT License. 11 | 12 | Description: Step 8 of building an NTP-based clock with TFT display. 13 | This step creates a dual clock for UTC and local time. 14 | 15 | see w8bh.net for a detailled, step-by-step tutorial 16 | 17 | **************************************************************************/ 18 | 19 | 20 | #include // https://github.com/Bodmer/TFT_eSPI 21 | #include // https://github.com/ropg/ezTime 22 | #if defined(ESP32) 23 | #include // use this WiFi lib for ESP32, or 24 | #elif defined (ESP8266) 25 | #include // use this WiFi lib for ESP8266 26 | #endif 27 | 28 | #define TITLE "NTP TIME" 29 | #define WIFI_SSID "yourSSID" 30 | #define WIFI_PWD "yourPassword" 31 | #define TZ_RULE "EST5EDT,M3.2.0/2:00:00,M11.1.0/2:00:00" 32 | 33 | #define USE_12HR_FORMAT true // preferred format for local time 34 | #define LEADING_ZERO false // show "01:00" vs " 1:00" 35 | #define SYNC_MARGINAL 3600 // orange status if no sync for 1 hour 36 | #define SYNC_LOST 86400 // red status if no sync for 1 day 37 | #define LOCAL_FORMAT_12HR true // local time format 12hr "11:34" vs 24hr "23:34" 38 | #define UTC_FORMAT_12HR false // UTC time format 12 hr "11:34" vs 24hr "23:34" 39 | #define TIMECOLOR TFT_CYAN // color of 7-segment time display 40 | #define DATECOLOR TFT_YELLOW // color of displayed month & day 41 | #define LABEL_FGCOLOR TFT_YELLOW 42 | #define LABEL_BGCOLOR TFT_BLUE 43 | 44 | TFT_eSPI tft = TFT_eSPI(); // display object 45 | Timezone local; // local timezone variable 46 | time_t t,oldT; // current & displayed UTC 47 | time_t lt,oldLt; // current & displayed local time 48 | bool useLocalTime = false; // temp flag used for display updates 49 | 50 | 51 | void showClockStatus() { 52 | const int x=290,y=1,w=28,h=29,f=2; // screen position & size 53 | int color; 54 | if (second()%10) return; // update every 10 seconds 55 | int syncAge = now()-lastNtpUpdateTime(); // how long has it been since last sync? 56 | if (syncAge < SYNC_MARGINAL) // time is good & in sync 57 | color = TFT_GREEN; 58 | else if (syncAge < SYNC_LOST) // sync is 1-24 hours old 59 | color = TFT_ORANGE; 60 | else color = TFT_RED; // time is stale & should not be trusted 61 | tft.fillRoundRect(x,y,w,h,10,color); // show clock status as a color 62 | tft.setTextColor(TFT_BLACK,color); 63 | tft.drawNumber(-WiFi.RSSI(),x+8,y+6,f); // WiFi strength as a positive value 64 | } 65 | 66 | void showAMPM (int hr) { 67 | } 68 | 69 | void showTime(time_t t, bool hr12, int x, int y) { 70 | const int f=7; // screen font 71 | tft.setTextColor(TIMECOLOR, TFT_BLACK); // set time color 72 | int h=hour(t); int m=minute(t); int s=second(t); // get hours, minutes, and seconds 73 | showAMPM(h); // display AM/PM, if needed 74 | if (hr12) { // adjust hours for 12 vs 24hr format: 75 | if (h==0) h=12; // 00:00 becomes 12:00 76 | if (h>12) h-=12; // 13:00 becomes 01:00 77 | } 78 | if (h<10) { // is hour a single digit? 79 | if ((!hr12)||(LEADING_ZERO)) // 24hr format: always use leading 0 80 | x+= tft.drawChar('0',x,y,f); // show leading zero for hours 81 | else { 82 | tft.setTextColor(TFT_BLACK,TFT_BLACK); // black on black text 83 | x+=tft.drawChar('8',x,y,f); // will erase the old digit 84 | tft.setTextColor(TIMECOLOR,TFT_BLACK); 85 | } 86 | } 87 | x+= tft.drawNumber(h,x,y,f); // hours 88 | x+= tft.drawChar(':',x,y,f); // show ":" 89 | if (m<10) x+= tft.drawChar('0',x,y,f); // leading zero for minutes 90 | x+= tft.drawNumber(m,x,y,f); // show minutes 91 | x+= tft.drawChar(':',x,y,f); // show ":" 92 | if (s<10) x+= tft.drawChar('0',x,y,f); // add leading zero for seconds 93 | x+= tft.drawNumber(s,x,y,f); // show seconds 94 | } 95 | 96 | void showDate(time_t t, int x, int y) { 97 | const int f=4,yspacing=30; // screen font, spacing 98 | const char* months[] = {"JAN","FEB","MAR", 99 | "APR","MAY","JUN","JUL","AUG","SEP","OCT", 100 | "NOV","DEC"}; 101 | tft.setTextColor(DATECOLOR, TFT_BLACK); 102 | int m=month(t), d=day(t); // get date components 103 | tft.fillRect(x,y,50,60,TFT_BLACK); // erase previous date 104 | tft.drawString(months[m-1],x,y,f); // show month on top 105 | y += yspacing; // put day below month 106 | if (d<10) x+=tft.drawNumber(0,x,y,f); // draw leading zero for day 107 | tft.drawNumber(d,x,y,f); // draw day 108 | } 109 | 110 | void showTimeZone (int x, int y) { 111 | const int f=4; // text font 112 | tft.setTextColor(LABEL_FGCOLOR,LABEL_BGCOLOR); // set text colors 113 | tft.fillRect(x,y,80,28,LABEL_BGCOLOR); // erase previous TZ 114 | if (!useLocalTime) 115 | tft.drawString("UTC",x,y,f); // UTC time 116 | else 117 | tft.drawString(local.getTimezoneName(),x,y,f); // show local time zone 118 | } 119 | 120 | void showTimeDate(time_t t, time_t oldT, bool hr12, int x, int y) { 121 | showTime(t,hr12,x,y); // display time HH:MM:SS 122 | if ((!oldT)||(hour(t)!=hour(oldT))) // did hour change? 123 | showTimeZone(x,y-42); // update time zone 124 | if (day(t)!=day(oldT)) // did date change? 125 | showDate(t,x+250,y); // update date 126 | } 127 | 128 | void updateDisplay() { 129 | t = now(); // check latest time 130 | if (t!=oldT) { // are we in a new second yet? 131 | lt = local.now(); // keep local time current 132 | useLocalTime = true; // use local timezone 133 | showTimeDate(lt,oldLt,LOCAL_FORMAT_12HR,10,46);// show new local time 134 | useLocalTime = false; // use UTC timezone 135 | showTimeDate(t,oldT,UTC_FORMAT_12HR,10,172); // show new UTC time 136 | showClockStatus(); // and clock status 137 | oldT=t; oldLt=lt; // remember currently displayed time 138 | } 139 | } 140 | 141 | void newDualScreen() { 142 | tft.setTextColor(LABEL_FGCOLOR,LABEL_BGCOLOR); // set label colors 143 | tft.fillScreen(TFT_BLACK); // start with empty screen 144 | tft.fillRoundRect(0,0,319,32,10,LABEL_BGCOLOR); // title bar for local time 145 | tft.fillRoundRect(0,126,319,32,10,LABEL_BGCOLOR);// title bar for UTC 146 | tft.setTextColor(LABEL_FGCOLOR,LABEL_BGCOLOR); // set label colors 147 | tft.drawCentreString(TITLE,160,4,4); // show title at top 148 | tft.drawRoundRect(0,0,319,110,10,TFT_WHITE); // draw edge around local time 149 | tft.drawRoundRect(0,126,319,110,10,TFT_WHITE); // draw edge around UTC 150 | } 151 | 152 | void setup() { 153 | tft.init(); 154 | tft.setRotation(1); // portrait screen orientation 155 | newDualScreen(); 156 | WiFi.begin(WIFI_SSID,WIFI_PWD); // attempt WiFi connection 157 | waitForSync(); // wait for NTP packet return 158 | local.setPosix(TZ_RULE); // estab. local TZ by rule 159 | } 160 | 161 | void loop() { 162 | events(); // allow NTP updates 163 | updateDisplay(); // update clock every second 164 | } 165 | -------------------------------------------------------------------------------- /ntp_clock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhall66/NTP-clock/4cb0719a54f85527dfdc18add7d6092f6cf042a5/ntp_clock.jpg --------------------------------------------------------------------------------