Components and supplies
DHT22 temperature-humidity sensor
Generic LDR photocell
Adafruit MPL115A2 Barometer sensor
Adafruit FX 2mb Soundboard with built in amp and speaker terminals
MAX7219/MAX7221 LED Display Drivers
Adafruit 8x8 LED matrix with I2C backpack
Some sort of Enclosure -- I used wooden wine boxes
Generic SPST Toggle switch
ESP8266 ESP-12E
Arduino Mega 2560
4 to 5 amp power supply
Adafruit 1.2" 7 segment LED with I2c backpack
Resistor 10k ohm
Generic SPST NO pushbuttons
Generic 8 ohm speaker
Generic SPDT center off toggle switch
Adafruit RTC DS3231 Real time clock
Apps and platforms
NPT time server
NYT API
NPR API
Project description
Code
ESP8266 "Co Processor" code
arduino
Used by main clock to get time, news and weather
1/* 2 News, Time and Weather: ESP 8266 co-processor for the Matrix Clock series 3 Responds to wired interrupt and takes a series of commands to return 4 time, NYT RSS Feed headline and Accuweather current weather and forecast 5 Requires SSID, password, offset from GMT and zip code 6 7 8-25-20 Update 'remove HTML apostrophe' code and edit degree sign 8 8-31-20 Remove yet another HTML apostrophe sneaking in 9 9-16-20 Add NPR news feed read and internal test code 10 9-17-20 Debugged version 11 9-28-20 Added 'disconnect from prior network' before wifi begins 12 9-29-20 Added tomorrow forecast to ACW updates 13 10-22-20 No change 14 11-30-20 Reduce time between segment sends from 400 ms to 40 ms 15 1-5-21 Remove redundant time validity test 16 01-29-21 Replace '&' with just '&' in strings 17*/ 18 19#define USING_AXTLS 20#include <ESP8266WiFi.h> 21#include <EasyNTPClient.h> 22#include <WiFiUdp.h> 23#include <TimeLib.h> 24#include <Wire.h> 25 26 27// force use of AxTLS (BearSSL is now default) 28#include <WiFiClientSecureAxTLS.h> 29using namespace axTLS; 30 31 // #define H7912 // house 1 32 #define H274 //house 2 33 34#ifdef H7912 35#define STASSID "xxxx" // Your SSID and 36#define STAPSK "zzzzzzzz" // Password 37#define MyOffset -8 // Your offset to GMT 38#define MyZipCode "98040" // Your zipcode used for localization of weather 39#endif 40 41#ifdef H274 42#define STASSID "xxxxxx" 43#define STAPSK "yyyyyyyyyyy" 44#define MyOffset -8 45#define MyZipCode "98358" 46#endif 47 48//#define test_mode // if defined code runs standalone to test readers 49 50#define DDT // define to turn on debugging tools 51void DDTv(String st, int vt) { // Print descriptor and value 52 #ifdef DDT 53 Serial.print(" "); 54 Serial.print(st); 55 Serial.print(" "); 56 Serial.print(vt); 57 #endif 58 } 59void DDTl(String st, int vt) { // Print descriptor and value and new line 60 #ifdef DDT 61 DDTv(st, vt); 62 Serial.println(" "); 63 #endif 64 } 65void DDTs(String st) { // Print string 66 #ifdef DDT 67 Serial.print(st); 68 #endif 69 } 70void DDTt(String st) { // Print string and new line 71 #ifdef DDT 72 Serial.println(st); 73 #endif 74 } 75void DDTf(String st, float fi) { // Print descriptor and floating point value and new line 76 #ifdef DDT 77 Serial.print(" "); 78 Serial.print(st); 79 Serial.print(" "); 80 Serial.println(fi,4); 81 #endif 82 } 83void DDTsl(String st, String vt) { // Print descriptor and string value and new line 84 #ifdef DDT 85 Serial.print(st); 86 Serial.println(vt); 87 #endif 88 } 89 90 91const char* ssid = STASSID; 92const char* password = STAPSK; 93WiFiUDP udp; 94EasyNTPClient ntpClient(udp, "pool.ntp.org", ((MyOffset * 60 * 60))); // initialize and set up offset 95 96unsigned long t_unix_date; 97int MyYear; // to receive the parsed date, time 98int MyMonth; 99int MyDay; 100int MyHour; 101int MyMinute; 102int MySecond; 103int MyWeekDay; 104char MyDaysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; 105#define SDA 4 // D2. 106#define SCL 5 // D1 107 108const int MasterPin = 2; // D4 // Pin to take interrupt on when asked for time and date 109bool ESPvalue; // Value to read interrupt status into 110bool Setflag = false; // Single flag set to 'true' when interrupt occurs 111const int ConnectTries = 20; // number of tries to wait for Wifi connection 112const int ConnectWait = 500; // milliseconds between tries 113const int WaitingTime = 2000; // milliseconds to fully delay between network status tries 114const int WaitingCounter = 50; // times to wait 115const int MasterChannel = 8; // although it seems like the master, to us its the slave (its I2C address) 116byte Master_Command = 0; 117const int MC_GetTime = 1; // potential values for clock to ask for 118const int MC_GetNYT = 2; // NYT RSS 119const int MC_GetACW = 3; // Current Accuweather 120const int MC_GetNPR = 4; // NPR 121const int MC_Bad = 0; 122const int MC_Null = 5; 123 124String line = " "; //150 125String NYTLine = line; 126String ACWLine = line; 127String NPRLine = line; 128int NYTLength; // length of returned headline 129int ACWLength; // length of returned accuweather line 130int NPRLength; // length of returned NPR line 131bool GotNYT = false; // set true by successful "GET" 132bool GotACW = false; // same - only need one for both currrent and forecast 133bool GotNPR = false; // same 134int Status_Flag; // result of wire transmission 135int Inptr; // 'from' string pointer 136int Outptr; // 'to' 137const int MaxBytes = 10; // I2C has a 32 byte limitation, but I can't get past 10 or so 138int NYTCCounter; // decrementing character counter 139int ACWCCounter; // decrementing character counter 140int NPRCCounter; // decrementing character counter 141char Segment [MaxBytes]; // segment header only in first segment: 142int Segment_Delay = 40; // milliseconds to wait between segment sends 143const int SSH_Type = 0; // segment header 'type' (will be MC_Command parroted back) 144const int SSH_Length = 1; // length of data (not including header) 145const int SSH_Contents = 2; // where the data lives 146const int Repeat_Sends = 5; // number of times to try to send segment 147 148 149WiFiClientSecure client; 150 151void setup() { 152 Serial.begin(9600); 153 WiFi.disconnect(); // Disconnect from any prior network 154 Wire.begin(SDA, SCL); // join i2c bus with address defined (int sda, int scl); 155 pinMode(MasterPin, INPUT_PULLUP); // initialize pin to listen to master on 156 157 // Serial.print("connecting to "); 158 //Serial.println(ssid); 159 WiFi.mode(WIFI_STA); 160 WiFi.begin(ssid, password); 161 while (WiFi.status() != WL_CONNECTED) { 162 delay(500); 163 Serial.print("."); } 164 165 // Serial.println("Initially connected"); 166 // Serial.println(""); 167 // Serial.println("WiFi connected"); 168 // Serial.println("IP address: "); 169 // Serial.println(WiFi.localIP()); 170 171 #ifndef test_mode 172 GetCommandByte(); // ask for first byte 173 #endif 174 175// DDTt("asking for initial byte"); 176 attachInterrupt(digitalPinToInterrupt(MasterPin), ISR, FALLING ); // set to interrupt when becomes LOW 177 178 } // end of setup 179 180void loop() { 181 182if (WiFi.status() != WL_CONNECTED){ // if disconnected from network, restart 183// DDTt("Not connected at top of loop"); 184 ESP.restart();} 185#ifdef test_mode 186Setflag = true; // if testing, we are always set 187#endif 188 189if (Setflag) { // we got a wired interrupt 190 191#ifdef test_mode // if testing a reader 192 Master_Command = MC_GetNPR; // THIS is the reader to test - change to suit test 193#else 194 GetCommandByte(); // ask Master for a command byte 195 DDTl("Command Byte = ",Master_Command); 196#endif 197 198 switch (Master_Command) { 199 case MC_Bad: 200// DDTs("Got a zero command"); 201 break; 202 case MC_GetTime: 203 if (!GetTheTime()) DDTt("GetThetime failed"); 204 break; 205 case MC_GetNYT: 206 GotNYT = GetNYT(); // do the fetch from RSS site and store success/fail 207 if (!GotNYT) {DDTt("GETNYT failed");} 208 if (!SendNYT()) DDTt("SendNYT failed"); 209 break; 210 case MC_GetACW: 211 GotACW = GetACW(); // do the fetch from RSS site and store success/fail 212 if (!GotACW) {DDTt("GETACW failed");} 213 if(!SendACW()) DDTt("SendAccuweather failed"); 214 break; 215 case MC_GetNPR: 216 GotNPR = GetNPR(); // do the fetch from RSS site and store success/fail 217 if (!GotNPR) {DDTt("GETNPR failed");} 218 if(!SendNPR()) DDTt("SendNPR failed"); 219 break; 220 case MC_Null: 221 DDTt("Got idle startup command"); 222 break; 223 default: 224 DDTl("Never on CASE",Master_Command); 225 break; 226 } // end of case switch 227 228 Setflag = false; // and clear "got interrupt" flag 229} 230 #ifdef test_mode 231 delay(30000); //delay between repeated reads 232 #endif 233} // end of Void Loop 234 235ICACHE_RAM_ATTR void ISR(){ // Simple routine to set 'true' on interrupt 236 Setflag = true; 237// DDTt("Got interrupt from Master"); 238} // end of ISR 239 240void GetCommandByte(){ // routine to ask for one byte from master clock 241 Wire.requestFrom (MasterChannel, 1); 242 if (Wire.available() == 1) {Master_Command = Wire.read(); 243// DDTv("Master Command received = ",Master_Command); 244 } else 245 {Master_Command = MC_Bad; 246 DDTv("Bad or no Command received = ",Master_Command); 247 DDTv("and length was",Wire.available()); 248 } 249 250} // end of get Command byte 251 252 bool GetNYT(){ 253 // Use WiFiClientSecure class to create TLS connection 254 255 const char* host = "rss.nytimes.com"; 256 const int httpsPort = 443; 257 258// Use web browser to view and copy 259// SHA1 fingerprint of the certificate 260 261//https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml 262//CB:29:78:50:52:F1:B9:1E:53:0C:BE:54:6C:11:DF:E6:29:94:D7:6E 263//const char* fingerprint = "CB 29 78 50 52 F1 B9 1E 53 0C BE 54 6C 11 DF E6 29 94 D7 6E"; 264 265 if (!client.connect(host, httpsPort)) { 266 DDTt("connection failed"); 267 return false;} 268 269// if (!client.verify(fingerprint, host)) { // it appears fingerprint not needed 270// DDTt("certificate doesn't match"); 271 // return false;} 272 273 String url = "/services/xml/rss/nyt/HomePage.xml"; 274// Serial.print("requesting URL: "); 275// Serial.println(url); 276 277 client.print(String("GET ") + url + 278 " HTTP/1.1\ \ 279" + 280 "Host: " + host + "\ \ 281" + 282 "Connection: close\ \ 283\ \ 284"); 285// Serial.println("request sent"); 286 while (client.connected()) { 287 line = client.readStringUntil('\n'); 288 if (line == "\ ") { 289// Serial.println("headers received"); 290 break; 291 } 292 } 293 line = client.readStringUntil('\n'); 294 if (line.startsWith("<?xml version")) { 295 // Serial.println("Successfull!"); 296 } else { 297 // Serial.println("Failed"); 298 return false; 299 } 300// Serial.println("reply was:"); 301// Serial.println("=========="); 302// Serial.println(line); 303// Serial.println("=========="); 304 SearchTag("<item>"); // search for item tag 305 line = client.readStringUntil('\n'); // read next line 306 RemoveTags(); 307 line.replace("'", "'"); // another instance of HTML apostrophe sneaking in - replace it 308 line.replace("&", "&"); // Replace '&' with just '&' 309 Serial.println(line); 310 NYTLength = line.length(); 311 DDTl("Length of Line is ",NYTLength); 312 NYTCCounter = NYTLength; // initialize the counter (send the terminator too) 313 line.setCharAt(NYTCCounter,'\n'); // and ensure it IS a terminator 314 NYTLine = line; // and copy the line for send 315// Serial.println("closing connection"); 316 return true; 317 } // end of GetNYT 318 319 bool SendNYT(){ 320 if (!GotNYT){NYTLine = "NY Times Not Available"; // if didn't get it, can't send it 321 NYTCCounter = NYTLine.length();} 322 // DDTl("SendNYT Called with data length of ",NYTCCounter); 323 Segment[SSH_Type] = MC_GetNYT; // first byte is a flag (command mirrored) 324 Segment[SSH_Length] = NYTCCounter; // second is number of data bytes past header 325 Inptr = 0; // initialize 'get' pointer 326 Outptr = SSH_Contents; // and the output pointer past header bytes 327 while (NYTCCounter > 0){ 328 Segment[Outptr] = NYTLine.charAt(Inptr); // copy the character 329 if (Segment[Outptr] == 226){ // if we hit the dreaded HTML apostrophe [sequence is 226,128,153] 330 Segment[Outptr] = 39; // set it to correct one 331 Inptr = Inptr+2; // skip next two character 332 NYTCCounter = NYTCCounter-2; // increment pointer, decrement count 333 } 334 // DDTv("Character",char(Segment[Outptr])); 335 // DDTl(" and value is ",int(Segment[Outptr])); 336 Inptr++; // increment in pointer and output 337 Outptr++; 338 NYTCCounter--; // and decrement remaining character 339 if (Outptr == MaxBytes) { 340 if (!Send_Segment(MaxBytes)) return false; 341 Outptr = 0; // and reset pointer 342// DDTl("Status of NYT transmission",Status_Flag); // 0 = success, 1 = data too long, 2 NACK on address, 3= NACK on data, 4= other error*/ 343 } // end of transmit segment 344 } // end of while 345 if (Outptr != 0){ // if we have a partial segment 346 if (!Send_Segment(Outptr)) return false; 347// DDTl("Status of NYT stub transmission",Status_Flag); // 0 = success, 1 = data too long, 2 NACK on address, 3= NACK on data, 4= other error*/ 348 } 349 return true; 350 } // end of SendNYT 351 352 bool GetNPR(){ // NPR News Feed 353 // Use WiFiClientSecure class to create TLS connection 354 355const char* host = "feeds.npr.org"; 356// https://feeds.npr.org/1001/rss.xml 357const int httpsPort = 443; 358 359//const char* fingerprint = "b13ec36903f8bf4701d498261a0802ef63642bc3"; 360 361 if (!client.connect(host, httpsPort)) { 362 DDTt("connection failed"); 363 return false;} 364 365// if (!client.verify(fingerprint, host)) { // does not appear to need fingerprint 366 // DDTt("certificate doesn't match"); 367 // return false;} 368 String url = "/1001/rss.xml"; 369 Serial.print("requesting URL: "); 370 Serial.println(url); 371 372 client.print(String("GET ") + url + 373 " HTTP/1.1\ \ 374" + 375 "Host: " + host + "\ \ 376" + 377 "Connection: close\ \ 378\ \ 379"); 380// Serial.println("request sent"); 381 while (client.connected()) { 382 line = client.readStringUntil('\n'); 383 if (line == "\ ") { 384 Serial.println("headers received"); 385 break; } 386 } 387 line = client.readStringUntil('\n'); 388 if (line.startsWith("<?xml version")) { 389 // Serial.println("Successfull!"); 390 } else { 391 Serial.println("Failed"); 392 return false; 393 } 394 // Serial.println("reply was:"); 395 // Serial.println("=========="); 396// Serial.println(line); 397// Serial.println("=========="); 398 SearchTag("<item>"); // search for item tag 399 line = client.readStringUntil('\n'); // read next line 400 RemoveTags(); 401 line.replace("'", "'"); // another instance of HTML apostrophe sneaking in - replace it 402 line.replace("&", "&"); // Replace '&' with just '&' 403 Serial.println(line); 404 NPRLength = line.length(); 405 DDTl("Length of Line is ",NPRLength); 406 NPRCCounter = NPRLength; // initialize the counter (send the terminator too) 407 line.setCharAt(NPRCCounter,'\n'); // and ensure it IS a terminator 408 NPRLine = line; // and copy the line for send 409 // Serial.println("closing connection"); 410 return true; 411 } // end of GetNPR 412 413 bool SendNPR(){ 414 if (!GotNPR){NPRLine = "NPR Not Available"; // if didn't get it, can't send it 415 NPRCCounter = NPRLine.length();} 416 // DDTl("SendNPR Called with data length of ",NPRCCounter); 417 Segment[SSH_Type] = MC_GetNPR; // first byte is a flag (command mirrored) 418 Segment[SSH_Length] = NPRCCounter; // second is number of data bytes past header 419 Inptr = 0; // initialize 'get' pointer 420 Outptr = SSH_Contents; // and the output pointer past header bytes 421 while (NPRCCounter > 0){ 422 Segment[Outptr] = NPRLine.charAt(Inptr); // copy the character 423 if (Segment[Outptr] == 226){ // if we hit the dreaded HTML apostrophe [sequence is 226,128,153] 424 Segment[Outptr] = 39; // set it to correct one 425 Inptr = Inptr+2; // skip next two character 426 NPRCCounter = NYTCCounter-2; // increment pointer, decrement count 427 } 428 // DDTv("Character",char(Segment[Outptr])); 429 // DDTl(" and value is ",int(Segment[Outptr])); 430 Inptr++; // increment in pointer and output 431 Outptr++; 432 NPRCCounter--; // and decrement remaining character 433 if (Outptr == MaxBytes) { 434 if (!Send_Segment(MaxBytes)) return false; 435 Outptr = 0; // and reset pointer 436// DDTl("Status of NPR transmission",Status_Flag); // 0 = success, 1 = data too long, 2 NACK on address, 3= NACK on data, 4= other error*/ 437 } // end of transmit segment 438 } // end of while 439 if (Outptr != 0){ // if we have a partial segment 440 if (!Send_Segment(Outptr)) return false; 441// DDTl("Status of NPR stub transmission",Status_Flag); // 0 = success, 1 = data too long, 2 NACK on address, 3= NACK on data, 4= other error*/ 442 } 443 return true; 444 } // end of SendNPR 445 446/* 447 void GetTheWeather() { // vestigial code for OpenWeatherMap get the weather... 448 449 //open weather map api key 450String apiKey= "a0517cca5c02ebb3b2075b89d4121d96"; 451//the city you want the weather for 452String location= "98040,us"; 453WiFiClient client; 454char server[] = "api.openweathermap.org"; 455//api.openweathermap.org/data/2.5/weather?q=London,uk&APPID=a0517cca5c02ebb3b2075b89d4121d96 456//api.openweathermap.org/data/2.5/weather?zip=98040,us 457//api.openweathermap.org/data/2.5/weather?q=98040,us&APPID=a0517cca5c02ebb3b2075b89d4121d96&units=imperial&mode=xml gets you xml and farenheit 458 Serial.println("\ 459Starting connection to server..."); 460 // if you get a connection, report back via serial: 461 if (client.connect(server, 80)) { 462 Serial.println("connected to server"); 463 // Make a HTTP request: 464 client.print("GET /data/2.5/weather?"); 465 client.print("q="+location); 466 client.print("&appid="+apiKey); 467 client.print("&cnt=3"); 468 client.println("&units=imperial"); 469 client.println("Host: api.openweathermap.org"); 470 client.println("Connection: close"); 471 client.println(); 472 } else { 473 Serial.println("unable to connect"); 474 } 475 delay(1000); 476 477 line = ""; 478 line = client.readStringUntil('\n'); 479 Serial.println(line); 480 481} // end of Get Weather 482*/ 483 484bool GetACW() { // get accuweather 485 486 // Use WiFiClientSecure class to create TLS connection 487 488 const char* host = "rss.accuweather.com"; 489 const int httpsPort = 443; 490 491// Use web browser to view and copy 492// SHA1 fingerprint of the certificate 493 494//https://rss.accuweather.com/rss/liveweather_rss.asp?locCode=98040 495//7B:E1:6E:70:F8:2F:64:52:3B:1A:71:F4:42:43:4A:1A:11:06:AC:8E 496const char* fingerprint = "7B E1 6E 70 F8 2F 64 52 3B 1A 71 F4 42 43 4A 1A 11 06 AC 8E"; 497 498 if (!client.connect(host, httpsPort)) { 499 DDTt("connection failed"); 500 return false;} 501 502 if (!client.verify(fingerprint, host)) { 503 DDTt("certificate doesn't match"); 504 return false; 505 } 506 507// String url = "/rss/liveweather_rss.asp?locCode=98040"; 508 String url = "/rss/liveweather_rss.asp?locCode="; 509 url = url + MyZipCode; 510// Serial.print("requesting URL: "); 511// Serial.println(url); 512 513 client.print(String("GET ") + url + 514 " HTTP/1.1\ \ 515" + 516 "Host: " + host + "\ \ 517" + 518 "Connection: close\ \ 519\ \ 520"); 521 // Serial.println("request sent"); 522 while (client.connected()) { 523 String line = client.readStringUntil('\n'); 524 if (line == "\ ") { 525 // Serial.println("headers received"); 526 break; 527 } 528 } 529 line = client.readStringUntil('\n'); 530 if (line.startsWith("<?xml version")) { 531 // Serial.println("Successful!"); 532 } else { 533 DDTsl("XML Version test failed",line); 534 return false; 535 } 536// Serial.println("reply was:"); 537// Serial.println("=========="); 538// Serial.println(line); 539// Serial.println("=========="); 540 541 542 SearchTag("<item>"); // read until <item> found 543 line = client.readStringUntil('\n'); // read next line 544 RemoveTags(); // remove tags on line 545 Serial.println(line); // should be today's conditions terse format 546 SearchTag("<description>"); // find today's longer version of day forecast 547 RemoveTags(); // and remove tags 548 line.replace(" °","~"); // Edit out HTML degree in weather feed and insert tilde 549 Serial.println(line); // should be today's conditions verbose 550 ACWLength = line.length(); 551// DDTl("Length of Weather Line is ",ACWLength); 552 ACWCCounter = ACWLength; // initialize the counter (send the terminator too) 553 line.setCharAt(ACWCCounter,'\n'); // and ensure it IS a terminator 554 ACWLine = line; // and copy the line for send 555//[UNCOMMENT TO GET TOMORROW FORECAST] 556 SearchTag("</item>"); // read string from RSS site til out of current weather 557 SearchTag("<item>"); 558 SearchTag("<title>"); 559 RemoveTags(); // get rid of the tags 560 Serial.println(line); // should be forecast description (e.g., "2/27/2020 FORECAST") 561 SearchTag("<description>"); 562 RemoveTags(); // clean it up 563 RemoveIMGThroughGIF(); 564 RemoveTags(); 565 Serial.println(line); // should be forecast detail (e.g., ") 566 SearchTag("</item>"); 567 SearchTag("<item>"); 568 SearchTag("<title>"); 569 RemoveTags(); // get rid of the tags 570 Serial.println(line); // should be forecast description (e.g., "2/27/2020 FORECAST") 571 SearchTag("<description>"); 572 RemoveTags(); // clean it up 573 RemoveIMGThroughGIF(); 574 RemoveTags(); 575 Serial.println(line); // should be forecast detail (e.g., ") 576 ACWLine = ACWLine + "$" + line; // combine lines (Current and forecast) 577 ACWLength = ACWLine.length(); // reset length to reflect combined line 578// DDTl("Length of Weather Forecast Line is ",ACWFLength); 579 ACWCCounter = ACWLength; // reset line length (send the terminator too) 580// line.setCharAt(ACWCCounter,'\n'); // and ensure it IS a terminator 581 582 //Serial.println("closing connection"); 583 return true; 584 } // end of GetAccuweather 585 586bool SendACW(){ // send "Current + "$" + "Forecast" 587 if (!GotACW){ACWLine = "Accuweather Not Available"; // if didn't get it, can't send it 588 ACWCCounter = ACWLine.length();} 589// DDTl("SendACW Called with data length of ",ACWCCounter+ACWFCCounter); 590 Segment[SSH_Type] = MC_GetACW; 591 Segment[SSH_Length] = ACWCCounter; 592 Inptr = 0; 593 Outptr = SSH_Contents; // and the output pointer 594 while (ACWCCounter > 0){ 595 Segment[Outptr] = ACWLine.charAt(Inptr); // copy the character 596 Inptr++; // increment in pointer and output 597 Outptr++; 598 ACWCCounter--; // and decrement remaining character 599 if (Outptr == MaxBytes) { 600 if (!Send_Segment(MaxBytes)) return false; 601 Outptr = 0; // and reset pointer 602// DDTl("Status of ACW transmission",Status_Flag); // 0 = success, 1 = data too long, 2 NACK on address, 3= NACK on data, 4= other error*/ 603 } // end of transmit segment 604 } // end of while 605 if (Outptr != 0){ // if we have a partial segment 606 if (!Send_Segment(Outptr)) return false; 607// DDTl("Status of ACW stub transmission",Status_Flag); // 0 = success, 1 = data too long, 2 NACK on address, 3= NACK on data, 4= other error*/ 608 } 609 return true; 610 } // end of SendACW 611 612bool GetTheTime(){ 613 // we got a request AND we are connected 614 t_unix_date = ntpClient.getUnixTime(); 615 616 if(t_unix_date == 0) return false; 617 618 MyYear = year(t_unix_date); 619 MyMonth = month(t_unix_date); 620 MyDay = day(t_unix_date); 621 MyHour = hour(t_unix_date); 622 MyMinute = minute(t_unix_date); 623 MySecond = second(t_unix_date); 624 MyWeekDay = weekday(t_unix_date); // note: returns 1-7, not 0-6 625/* 626 Serial.print("Today's Date and Time: "); 627 Serial.print(MyDaysOfTheWeek[MyWeekDay-1]); 628 Serial.print(" "); 629 Serial.print(MyWeekDay); 630 Serial.print(" "); 631 Serial.print(MyMonth); 632 Serial.print("/"); 633 Serial.print(MyDay); 634 Serial.print("/"); 635 Serial.print(MyYear); 636 Serial.print(" "); 637 Serial.print(MyHour); 638 Serial.print(":"); 639 if (MyMinute < 10) { 640 Serial.print("0"); 641 } 642 Serial.print(MyMinute); 643 Serial.print(":"); 644 if (MySecond < 10) { 645 Serial.print("0"); 646 } 647 Serial.print(MySecond); 648 Serial.println(" "); 649*/ 650 Segment[SSH_Type] = MC_GetTime; // load segment array up 651 Segment[SSH_Length] = 7; // number of data bytes 652 Segment[SSH_Contents] = MyYear-2019; // base year is 2019, so it fits in a byte 653 Segment[SSH_Contents+1] = MyMonth; 654 Segment[SSH_Contents+2] = MyDay; 655 Segment[SSH_Contents+3] = MyHour; 656 Segment[SSH_Contents+4] = MyMinute; 657 Segment[SSH_Contents+5] = MySecond; 658 Segment[SSH_Contents+6] = MyWeekDay; 659 Send_Segment(9); 660 return true; 661} 662 663 void SearchTag(String MyTag){ 664 do { line = client.readStringUntil('\n'); // read string from RSS site until this string is found 665 } while (line.indexOf(MyTag) <0); 666// Serial.print("Searchtag found "); 667// Serial.print(MyTag); 668// Serial.print(" in line "); 669// Serial.println(line); 670 } 671 672 void RemoveTags(){ 673 line.remove(0,(line.indexOf(">"))+1); 674 line.remove(line.indexOf("<"),line.indexOf(">")); 675 } // end of RemoveTags 676 void RemoveIMGThroughGIF(){ 677 line.remove(line.indexOf("img")-4,line.indexOf("gif")); 678 } // end of RIG 679 680bool Send_Segment(int bytestosend){ 681 int trycounter = Repeat_Sends; 682 do {Wire.beginTransmission(MasterChannel); // Send bytes result to Master 683 Wire.write(Segment,bytestosend); // 684// DDTl("segment transmission sent",bytestosend); 685 Status_Flag = Wire.endTransmission(true); 686 delay(Segment_Delay);} while ((Status_Flag != 0) && (trycounter-- >0)); // keep trying until good status or counter gone 687 if (trycounter > 0) {return true;} else {return false;} 688} 689
Arduino code for clock (Arduino Mega)
arduino
Runs the main clock
Arduino code for clock (Arduino Mega)
arduino
Runs the main clock
ESP8266 "Co Processor" code
arduino
Used by main clock to get time, news and weather
1/* 2 News, Time and Weather: ESP 8266 co-processor for the Matrix 3 Clock series 4 Responds to wired interrupt and takes a series of commands to 5 return 6 time, NYT RSS Feed headline and Accuweather current weather and forecast 7 8 Requires SSID, password, offset from GMT and zip code 9 10 8-25-20 Update 11 'remove HTML apostrophe' code and edit degree sign 12 8-31-20 Remove yet another 13 HTML apostrophe sneaking in 14 9-16-20 Add NPR news feed read and internal 15 test code 16 9-17-20 Debugged version 17 9-28-20 Added 'disconnect from 18 prior network' before wifi begins 19 9-29-20 Added tomorrow forecast to ACW 20 updates 21 10-22-20 No change 22 11-30-20 Reduce time between segment 23 sends from 400 ms to 40 ms 24 1-5-21 Remove redundant time validity test 25 26 01-29-21 Replace '&' with just '&' in strings 27*/ 28 29#define USING_AXTLS 30#include 31 <ESP8266WiFi.h> 32#include <EasyNTPClient.h> 33#include <WiFiUdp.h> 34#include 35 <TimeLib.h> 36#include <Wire.h> 37 38 39// force use of AxTLS (BearSSL is now 40 default) 41#include <WiFiClientSecureAxTLS.h> 42using namespace axTLS; 43 44 45 // #define H7912 // house 1 46 #define 47 H274 //house 2 48 49#ifdef H7912 50#define 51 STASSID "xxxx" // Your SSID and 52#define 53 STAPSK "zzzzzzzz" // Password 54#define MyOffset 55 -8 // Your offset to GMT 56#define MyZipCode 57 "98040" // Your zipcode used for localization 58 of weather 59#endif 60 61#ifdef H274 62#define STASSID "xxxxxx" 63#define 64 STAPSK "yyyyyyyyyyy" 65#define MyOffset -8 66#define MyZipCode "98358" 67#endif 68 69//#define 70 test_mode // if defined code runs standalone to test readers 71 72#define 73 DDT // define to turn on debugging tools 74void 75 DDTv(String st, int vt) { // Print descriptor and value 76 #ifdef 77 DDT 78 Serial.print(" "); 79 Serial.print(st); 80 Serial.print(" 81 "); 82 Serial.print(vt); 83 #endif 84 } 85void DDTl(String st, int 86 vt) { // Print descriptor and value and new line 87 #ifdef DDT 88 89 DDTv(st, vt); 90 Serial.println(" "); 91 #endif 92 } 93void DDTs(String 94 st) { // Print string 95 #ifdef DDT 96 Serial.print(st); 97 98 #endif 99 } 100void DDTt(String st) { // Print string 101 and new line 102 #ifdef DDT 103 Serial.println(st); 104 #endif 105 } 106void 107 DDTf(String st, float fi) { // Print descriptor and floating point value 108 and new line 109 #ifdef DDT 110 Serial.print(" "); 111 Serial.print(st); 112 113 Serial.print(" "); 114 Serial.println(fi,4); 115 #endif 116 } 117void 118 DDTsl(String st, String vt) { // 119 Print descriptor and string value and new line 120 #ifdef DDT 121 Serial.print(st); 122 123 Serial.println(vt); 124 #endif 125 } 126 127 128const char* ssid = STASSID; 129const 130 char* password = STAPSK; 131WiFiUDP udp; 132EasyNTPClient ntpClient(udp, "pool.ntp.org", 133 ((MyOffset * 60 * 60))); // initialize and set up offset 134 135unsigned 136 long t_unix_date; 137int MyYear; // 138 to receive the parsed date, time 139int MyMonth; 140int MyDay; 141int MyHour; 142int 143 MyMinute; 144int MySecond; 145int MyWeekDay; 146char MyDaysOfTheWeek[7][12] = {"Sunday", 147 "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; 148#define 149 SDA 4 // D2. 150#define SCL 5 // D1 151 152const int MasterPin = 2; // D4 153 // Pin to take interrupt on when asked for time and date 154bool 155 ESPvalue; // Value to read interrupt 156 status into 157bool Setflag = false; // 158 Single flag set to 'true' when interrupt occurs 159const int ConnectTries = 20; 160 // number of tries to wait for Wifi connection 161const 162 int ConnectWait = 500; // milliseconds between 163 tries 164const int WaitingTime = 2000; // milliseconds 165 to fully delay between network status tries 166const int WaitingCounter = 50; // 167 times to wait 168const int MasterChannel = 8; // 169 although it seems like the master, to us its the slave (its I2C address) 170byte 171 Master_Command = 0; 172const int MC_GetTime = 1; // 173 potential values for clock to ask for 174const int MC_GetNYT = 2; // 175 NYT RSS 176const int MC_GetACW = 3; // Current 177 Accuweather 178const int MC_GetNPR = 4; // 179 NPR 180const int MC_Bad = 0; 181const int MC_Null = 5; 182 183String line = " 184 "; 185 //150 186String NYTLine = line; 187String ACWLine = line; 188String NPRLine = 189 line; 190int NYTLength; // 191 length of returned headline 192int ACWLength; // 193 length of returned accuweather line 194int NPRLength; // 195 length of returned NPR line 196bool GotNYT = false; // 197 set true by successful "GET" 198bool GotACW = false; // 199 same - only need one for both currrent and forecast 200bool GotNPR = false; // 201 same 202int Status_Flag; // 203 result of wire transmission 204int Inptr; // 205 'from' string pointer 206int Outptr; // 207 'to' 208const int MaxBytes = 10; // 209 I2C has a 32 byte limitation, but I can't get past 10 or so 210int NYTCCounter; 211 // decrementing character 212 counter 213int ACWCCounter; // 214 decrementing character counter 215int NPRCCounter; // 216 decrementing character counter 217char Segment [MaxBytes]; // 218 segment header only in first segment: 219int Segment_Delay = 40; // 220 milliseconds to wait between segment sends 221const int SSH_Type = 0; // 222 segment header 'type' (will be MC_Command parroted back) 223const int SSH_Length 224 = 1; // length of data (not including 225 header) 226const int SSH_Contents = 2; // 227 where the data lives 228const int Repeat_Sends = 5; // 229 number of times to try to send segment 230 231 232WiFiClientSecure client; 233 234void 235 setup() { 236 Serial.begin(9600); 237 WiFi.disconnect(); // 238 Disconnect from any prior network 239 Wire.begin(SDA, SCL); // 240 join i2c bus with address defined (int sda, int scl); 241 pinMode(MasterPin, INPUT_PULLUP); 242 // initialize pin to listen to master on 243 244 // 245 Serial.print("connecting to "); 246 //Serial.println(ssid); 247 WiFi.mode(WIFI_STA); 248 249 WiFi.begin(ssid, password); 250 while (WiFi.status() != WL_CONNECTED) { 251 delay(500); 252 253 Serial.print("."); } 254 255 // Serial.println("Initially connected"); 256 257 // Serial.println(""); 258 // Serial.println("WiFi connected"); 259 // Serial.println("IP 260 address: "); 261 // Serial.println(WiFi.localIP()); 262 263 #ifndef test_mode 264 265 GetCommandByte(); // ask for 266 first byte 267 #endif 268 269// DDTt("asking for initial byte"); 270 attachInterrupt(digitalPinToInterrupt(MasterPin), 271 ISR, FALLING ); // set to interrupt when becomes LOW 272 273 } // end of setup 274 275 276void loop() { 277 278if (WiFi.status() != WL_CONNECTED){ // 279 if disconnected from network, restart 280// DDTt("Not connected at top of loop"); 281 282 ESP.restart();} 283#ifdef test_mode 284Setflag = true; // 285 if testing, we are always set 286#endif 287 288if (Setflag) 289 { // we got a wired interrupt 290 291#ifdef 292 test_mode // if testing a reader 293 294 Master_Command = MC_GetNPR; // THIS is the 295 reader to test - change to suit test 296#else 297 GetCommandByte(); // 298 ask Master for a command byte 299 DDTl("Command Byte = ",Master_Command); 300#endif 301 302 303 switch (Master_Command) { 304 case MC_Bad: 305// DDTs("Got a zero command"); 306 307 break; 308 case MC_GetTime: 309 if (!GetTheTime()) DDTt("GetThetime 310 failed"); 311 break; 312 case MC_GetNYT: 313 GotNYT = GetNYT(); // 314 do the fetch from RSS site and store success/fail 315 if (!GotNYT) {DDTt("GETNYT 316 failed");} 317 if (!SendNYT()) DDTt("SendNYT failed"); 318 break; 319 320 case MC_GetACW: 321 GotACW = GetACW(); // 322 do the fetch from RSS site and store success/fail 323 if (!GotACW) {DDTt("GETACW 324 failed");} 325 if(!SendACW()) DDTt("SendAccuweather failed"); 326 break; 327 328 case MC_GetNPR: 329 GotNPR = GetNPR(); // 330 do the fetch from RSS site and store success/fail 331 if (!GotNPR) {DDTt("GETNPR 332 failed");} 333 if(!SendNPR()) DDTt("SendNPR failed"); 334 break; 335 336 case MC_Null: 337 DDTt("Got idle startup command"); 338 break; 339 340 default: 341 DDTl("Never on CASE",Master_Command); 342 break; 343 } 344 // end of case switch 345 346 Setflag = false; // 347 and clear "got interrupt" flag 348} 349 #ifdef test_mode 350 delay(30000); //delay 351 between repeated reads 352 #endif 353} // end of Void Loop 354 355ICACHE_RAM_ATTR 356 void ISR(){ // Simple routine to set 'true' on interrupt 357 358 Setflag = true; 359// DDTt("Got interrupt from Master"); 360} // end of ISR 361 362void 363 GetCommandByte(){ // routine to ask for 364 one byte from master clock 365 Wire.requestFrom (MasterChannel, 1); 366 if (Wire.available() 367 == 1) {Master_Command = Wire.read(); 368// DDTv("Master Command received = ",Master_Command); 369 370 } else 371 {Master_Command = MC_Bad; 372 DDTv("Bad or no Command received 373 = ",Master_Command); 374 DDTv("and length was",Wire.available()); 375 } 376 377 378} // end of get Command byte 379 380 bool GetNYT(){ 381 // Use WiFiClientSecure 382 class to create TLS connection 383 384 const char* host = "rss.nytimes.com"; 385 386 const int httpsPort = 443; 387 388// Use web browser to view and copy 389// SHA1 390 fingerprint of the certificate 391 392//https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml 393//CB:29:78:50:52:F1:B9:1E:53:0C:BE:54:6C:11:DF:E6:29:94:D7:6E 394//const 395 char* fingerprint = "CB 29 78 50 52 F1 B9 1E 53 0C BE 54 6C 11 DF E6 29 94 D7 6E"; 396 397 398 if (!client.connect(host, httpsPort)) { 399 DDTt("connection failed"); 400 401 return false;} 402 403// if (!client.verify(fingerprint, host)) { // 404 it appears fingerprint not needed 405// DDTt("certificate doesn't match"); 406 407 // return false;} 408 409 String url = "/services/xml/rss/nyt/HomePage.xml"; 410// 411 Serial.print("requesting URL: "); 412// Serial.println(url); 413 414 client.print(String("GET 415 ") + url + 416 " HTTP/1.1\ \ 417" + 418 "Host: " 419 + host + "\ \ 420" + 421 "Connection: close\ \ 422\ \ 423"); 424// 425 Serial.println("request sent"); 426 while (client.connected()) { 427 line 428 = client.readStringUntil('\ 429'); 430 if (line == "\ ") { 431// Serial.println("headers 432 received"); 433 break; 434 } 435 } 436 line = client.readStringUntil('\ 437'); 438 439 if (line.startsWith("<?xml version")) { 440 // Serial.println("Successfull!"); 441 442 } else { 443 // Serial.println("Failed"); 444 return false; 445 } 446// Serial.println("reply 447 was:"); 448// Serial.println("=========="); 449// Serial.println(line); 450// 451 Serial.println("=========="); 452 SearchTag("<item>"); // 453 search for item tag 454 line = client.readStringUntil('\ 455'); // 456 read next line 457 RemoveTags(); 458 line.replace("'", "'"); // 459 another instance of HTML apostrophe sneaking in - replace it 460 line.replace("&", 461 "&"); // Replace '&' with just '&' 462 463 Serial.println(line); 464 NYTLength = line.length(); 465 DDTl("Length 466 of Line is ",NYTLength); 467 NYTCCounter = NYTLength; // 468 initialize the counter (send the terminator too) 469 line.setCharAt(NYTCCounter,'\ 470'); 471 // and ensure it IS a terminator 472 NYTLine 473 = line; // and copy the line for 474 send 475// Serial.println("closing connection"); 476 return true; 477 } 478 // end of GetNYT 479 480 bool SendNYT(){ 481 if (!GotNYT){NYTLine = "NY Times 482 Not Available"; // if didn't get it, can't send it 483 NYTCCounter 484 = NYTLine.length();} 485 // DDTl("SendNYT Called with data length of ",NYTCCounter); 486 487 Segment[SSH_Type] = MC_GetNYT; // first 488 byte is a flag (command mirrored) 489 Segment[SSH_Length] = NYTCCounter; // 490 second is number of data bytes past header 491 Inptr = 0; // 492 initialize 'get' pointer 493 Outptr = SSH_Contents; // 494 and the output pointer past header bytes 495 while (NYTCCounter > 0){ 496 Segment[Outptr] 497 = NYTLine.charAt(Inptr); // copy the character 498 if 499 (Segment[Outptr] == 226){ // if we hit the 500 dreaded HTML apostrophe [sequence is 226,128,153] 501 Segment[Outptr] = 39; 502 // set it to correct one 503 Inptr 504 = Inptr+2; // skip next two character 505 506 NYTCCounter = NYTCCounter-2; // increment 507 pointer, decrement count 508 } 509 // DDTv("Character",char(Segment[Outptr])); 510 511 // DDTl(" and value is ",int(Segment[Outptr])); 512 Inptr++; // 513 increment in pointer and output 514 Outptr++; 515 516 NYTCCounter--; // and 517 decrement remaining character 518 if (Outptr == MaxBytes) { 519 if 520 (!Send_Segment(MaxBytes)) return false; 521 Outptr = 0; // 522 and reset pointer 523// DDTl("Status of NYT transmission",Status_Flag); // 524 0 = success, 1 = data too long, 2 NACK on address, 3= NACK on data, 4= other error*/ 525 526 } // end of transmit segment 527 } // end of while 528 if (Outptr 529 != 0){ // if we have a partial 530 segment 531 if (!Send_Segment(Outptr)) return false; 532// DDTl("Status 533 of NYT stub transmission",Status_Flag); // 0 = success, 534 1 = data too long, 2 NACK on address, 3= NACK on data, 4= other error*/ 535 } 536 537 return true; 538 } // end of SendNYT 539 540 bool GetNPR(){ // 541 NPR News Feed 542 // Use WiFiClientSecure class to create TLS connection 543 544 545const char* host = "feeds.npr.org"; 546// https://feeds.npr.org/1001/rss.xml 547const 548 int httpsPort = 443; 549 550//const char* fingerprint = "b13ec36903f8bf4701d498261a0802ef63642bc3"; 551 552 553 if (!client.connect(host, httpsPort)) { 554 DDTt("connection failed"); 555 556 return false;} 557 558// if (!client.verify(fingerprint, host)) { // 559 does not appear to need fingerprint 560 // DDTt("certificate doesn't match"); 561 562 // return false;} 563 String url = "/1001/rss.xml"; 564 Serial.print("requesting 565 URL: "); 566 Serial.println(url); 567 568 client.print(String("GET ") + url 569 + 570 " HTTP/1.1\ \ 571" + 572 "Host: " + host + 573 "\ \ 574" + 575 "Connection: close\ \ 576\ \ 577"); 578// Serial.println("request 579 sent"); 580 while (client.connected()) { 581 line = client.readStringUntil('\ 582'); 583 584 if (line == "\ ") { 585 Serial.println("headers received"); 586 break; 587 } 588 } 589 line = client.readStringUntil('\ 590'); 591 if (line.startsWith("<?xml 592 version")) { 593 // Serial.println("Successfull!"); 594 } else { 595 Serial.println("Failed"); 596 597 return false; 598 } 599 // Serial.println("reply was:"); 600 // Serial.println("=========="); 601// 602 Serial.println(line); 603// Serial.println("=========="); 604 SearchTag("<item>"); 605 // search for item tag 606 line = client.readStringUntil('\ 607'); 608 // read next line 609 RemoveTags(); 610 line.replace("'", 611 "'"); // another instance of HTML apostrophe 612 sneaking in - replace it 613 line.replace("&", "&"); // 614 Replace '&' with just '&' 615 Serial.println(line); 616 NPRLength = line.length(); 617 618 DDTl("Length of Line is ",NPRLength); 619 NPRCCounter = NPRLength; // 620 initialize the counter (send the terminator too) 621 line.setCharAt(NPRCCounter,'\ 622'); 623 // and ensure it IS a terminator 624 NPRLine 625 = line; // and copy the line for 626 send 627 // Serial.println("closing connection"); 628 return true; 629 } 630 // end of GetNPR 631 632 bool SendNPR(){ 633 if (!GotNPR){NPRLine = "NPR Not 634 Available"; // if didn't get it, can't send it 635 NPRCCounter 636 = NPRLine.length();} 637 // DDTl("SendNPR Called with data length of ",NPRCCounter); 638 639 Segment[SSH_Type] = MC_GetNPR; // first 640 byte is a flag (command mirrored) 641 Segment[SSH_Length] = NPRCCounter; // 642 second is number of data bytes past header 643 Inptr = 0; // 644 initialize 'get' pointer 645 Outptr = SSH_Contents; // 646 and the output pointer past header bytes 647 while (NPRCCounter > 0){ 648 Segment[Outptr] 649 = NPRLine.charAt(Inptr); // copy the character 650 if 651 (Segment[Outptr] == 226){ // if we hit the 652 dreaded HTML apostrophe [sequence is 226,128,153] 653 Segment[Outptr] = 39; 654 // set it to correct one 655 Inptr 656 = Inptr+2; // skip next two character 657 658 NPRCCounter = NYTCCounter-2; // increment 659 pointer, decrement count 660 } 661 // DDTv("Character",char(Segment[Outptr])); 662 663 // DDTl(" and value is ",int(Segment[Outptr])); 664 Inptr++; // 665 increment in pointer and output 666 Outptr++; 667 668 NPRCCounter--; // and decrement 669 remaining character 670 if (Outptr == MaxBytes) { 671 if (!Send_Segment(MaxBytes)) 672 return false; 673 Outptr = 0; // 674 and reset pointer 675// DDTl("Status of NPR transmission",Status_Flag); // 676 0 = success, 1 = data too long, 2 NACK on address, 3= NACK on data, 4= other error*/ 677 678 } // end of transmit segment 679 } // end of while 680 if (Outptr 681 != 0){ // if we have a partial 682 segment 683 if (!Send_Segment(Outptr)) return false; 684// DDTl("Status 685 of NPR stub transmission",Status_Flag); // 0 = success, 686 1 = data too long, 2 NACK on address, 3= NACK on data, 4= other error*/ 687 } 688 689 return true; 690 } // end of SendNPR 691 692/* 693 void GetTheWeather() 694 { // vestigial code 695 for OpenWeatherMap get the weather... 696 697 //open weather map api key 698String 699 apiKey= "a0517cca5c02ebb3b2075b89d4121d96"; 700//the city you want the weather 701 for 702String location= "98040,us"; 703WiFiClient client; 704char server[] = 705 "api.openweathermap.org"; 706//api.openweathermap.org/data/2.5/weather?q=London,uk&APPID=a0517cca5c02ebb3b2075b89d4121d96 707 708//api.openweathermap.org/data/2.5/weather?zip=98040,us 709//api.openweathermap.org/data/2.5/weather?q=98040,us&APPID=a0517cca5c02ebb3b2075b89d4121d96&units=imperial&mode=xml 710 gets you xml and farenheit 711 Serial.println("\ 712Starting connection to server..."); 713 714 // if you get a connection, report back via serial: 715 if (client.connect(server, 716 80)) { 717 Serial.println("connected to server"); 718 // Make a HTTP request: 719 720 client.print("GET /data/2.5/weather?"); 721 client.print("q="+location); 722 723 client.print("&appid="+apiKey); 724 client.print("&cnt=3"); 725 client.println("&units=imperial"); 726 727 client.println("Host: api.openweathermap.org"); 728 client.println("Connection: 729 close"); 730 client.println(); 731 } else { 732 Serial.println("unable 733 to connect"); 734 } 735 delay(1000); 736 737 line = ""; 738 line = client.readStringUntil('\ 739'); 740 741 Serial.println(line); 742 743} // end of Get Weather 744*/ 745 746bool GetACW() 747 { // get accuweather 748 749 750 // Use WiFiClientSecure class to create TLS connection 751 752 const char* 753 host = "rss.accuweather.com"; 754 const int httpsPort = 443; 755 756// Use web 757 browser to view and copy 758// SHA1 fingerprint of the certificate 759 760//https://rss.accuweather.com/rss/liveweather_rss.asp?locCode=98040 761//7B:E1:6E:70:F8:2F:64:52:3B:1A:71:F4:42:43:4A:1A:11:06:AC:8E 762const 763 char* fingerprint = "7B E1 6E 70 F8 2F 64 52 3B 1A 71 F4 42 43 4A 1A 11 06 AC 8E"; 764 765 766 if (!client.connect(host, httpsPort)) { 767 DDTt("connection failed"); 768 769 return false;} 770 771 if (!client.verify(fingerprint, host)) { 772 DDTt("certificate 773 doesn't match"); 774 return false; 775 } 776 777// String url = "/rss/liveweather_rss.asp?locCode=98040"; 778 779 String url = "/rss/liveweather_rss.asp?locCode="; 780 url = url + MyZipCode; 781// 782 Serial.print("requesting URL: "); 783// Serial.println(url); 784 785 client.print(String("GET 786 ") + url + 787 " HTTP/1.1\ \ 788" + 789 "Host: " 790 + host + "\ \ 791" + 792 "Connection: close\ \ 793\ \ 794"); 795 // 796 Serial.println("request sent"); 797 while (client.connected()) { 798 String 799 line = client.readStringUntil('\ 800'); 801 if (line == "\ ") { 802 // Serial.println("headers 803 received"); 804 break; 805 } 806 } 807 line = client.readStringUntil('\ 808'); 809 810 if (line.startsWith("<?xml version")) { 811 // Serial.println("Successful!"); 812 813 } else { 814 DDTsl("XML Version test failed",line); 815 return false; 816 817 } 818// Serial.println("reply was:"); 819// Serial.println("=========="); 820// 821 Serial.println(line); 822// Serial.println("=========="); 823 824 825 SearchTag("<item>"); 826 // read until <item> found 827 line = client.readStringUntil('\ 828'); 829 // read next line 830 RemoveTags(); // 831 remove tags on line 832 Serial.println(line); // 833 should be today's conditions terse format 834 835 SearchTag("<description>"); // find today's longer 836 version of day forecast 837 RemoveTags(); // 838 and remove tags 839 line.replace(" °","~"); // 840 Edit out HTML degree in weather feed and insert tilde 841 Serial.println(line); 842 // should be today's conditions verbose 843 844 ACWLength = line.length(); 845// DDTl("Length of Weather Line is ",ACWLength); 846 847 ACWCCounter = ACWLength; // initialize 848 the counter (send the terminator too) 849 line.setCharAt(ACWCCounter,'\ 850'); // 851 and ensure it IS a terminator 852 ACWLine = line; // 853 and copy the line for send 854//[UNCOMMENT 855 TO GET TOMORROW FORECAST] 856 SearchTag("</item>"); // 857 read string from RSS site til out of current weather 858 SearchTag("<item>"); 859 860 SearchTag("<title>"); 861 RemoveTags(); 862 // get rid of the tags 863 Serial.println(line); 864 // should be forecast description (e.g., "2/27/2020 865 FORECAST") 866 SearchTag("<description>"); 867 RemoveTags(); // 868 clean it up 869 RemoveIMGThroughGIF(); 870 RemoveTags(); 871 872 Serial.println(line); // should be forecast 873 detail (e.g., ") 874 SearchTag("</item>"); 875 SearchTag("<item>"); 876 877 SearchTag("<title>"); 878 RemoveTags(); // 879 get rid of the tags 880 Serial.println(line); // 881 should be forecast description (e.g., "2/27/2020 FORECAST") 882 SearchTag("<description>"); 883 884 RemoveTags(); // clean it up 885 886 RemoveIMGThroughGIF(); 887 RemoveTags(); 888 889 Serial.println(line); // should be forecast 890 detail (e.g., ") 891 ACWLine = ACWLine + "$" + line; // 892 combine lines (Current and forecast) 893 ACWLength = ACWLine.length(); // 894 reset length to reflect combined line 895// DDTl("Length of Weather Forecast Line 896 is ",ACWFLength); 897 ACWCCounter = ACWLength; // 898 reset line length (send the terminator too) 899// line.setCharAt(ACWCCounter,'\ 900'); 901 // and ensure it IS a terminator 902 903 //Serial.println("closing 904 connection"); 905 return true; 906 } // end of GetAccuweather 907 908bool SendACW(){ 909 // send "Current 910 + "$" + "Forecast" 911 if (!GotACW){ACWLine = "Accuweather Not Available"; 912 // if didn't get it, can't send it 913 ACWCCounter 914 = ACWLine.length();} 915// DDTl("SendACW Called with data length of ",ACWCCounter+ACWFCCounter); 916 917 Segment[SSH_Type] = MC_GetACW; 918 Segment[SSH_Length] = ACWCCounter; 919 920 Inptr = 0; 921 Outptr = SSH_Contents; // 922 and the output pointer 923 while (ACWCCounter > 0){ 924 Segment[Outptr] 925 = ACWLine.charAt(Inptr); // copy the character 926 Inptr++; 927 // increment in pointer 928 and output 929 Outptr++; 930 931 ACWCCounter--; // and 932 decrement remaining character 933 if (Outptr == MaxBytes) { 934 if 935 (!Send_Segment(MaxBytes)) return false; 936 Outptr = 0; // 937 and reset pointer 938// DDTl("Status of ACW transmission",Status_Flag); // 939 0 = success, 1 = data too long, 2 NACK on address, 3= NACK on data, 4= other error*/ 940 941 } // end of transmit segment 942 } // end of while 943 if (Outptr 944 != 0){ // if we have a partial 945 segment 946 if (!Send_Segment(Outptr)) return false; 947// DDTl("Status 948 of ACW stub transmission",Status_Flag); // 0 = success, 949 1 = data too long, 2 NACK on address, 3= NACK on data, 4= other error*/ 950 } 951 952 return true; 953 } // end of SendACW 954 955bool GetTheTime(){ 956 // 957 we got a request AND we are connected 958 t_unix_date = ntpClient.getUnixTime(); 959 960 961 if(t_unix_date == 0) return false; 962 963 MyYear = year(t_unix_date); 964 MyMonth 965 = month(t_unix_date); 966 MyDay = day(t_unix_date); 967 MyHour = hour(t_unix_date); 968 969 MyMinute = minute(t_unix_date); 970 MySecond = second(t_unix_date); 971 MyWeekDay 972 = weekday(t_unix_date); // note: returns 1-7, not 0-6 973/* 974 975 Serial.print("Today's Date and Time: "); 976 Serial.print(MyDaysOfTheWeek[MyWeekDay-1]); 977 978 Serial.print(" "); 979 Serial.print(MyWeekDay); 980 Serial.print(" "); 981 982 Serial.print(MyMonth); 983 Serial.print("/"); 984 Serial.print(MyDay); 985 986 Serial.print("/"); 987 Serial.print(MyYear); 988 Serial.print(" "); 989 990 Serial.print(MyHour); 991 Serial.print(":"); 992 if (MyMinute < 10) { 993 994 Serial.print("0"); 995 } 996 Serial.print(MyMinute); 997 Serial.print(":"); 998 999 if (MySecond < 10) { 1000 Serial.print("0"); 1001 } 1002 Serial.print(MySecond); 1003 1004 Serial.println(" "); 1005*/ 1006 Segment[SSH_Type] = MC_GetTime; // 1007 load segment array up 1008 Segment[SSH_Length] = 7; // 1009 number of data bytes 1010 Segment[SSH_Contents] = MyYear-2019; // 1011 base year is 2019, so it fits in a byte 1012 Segment[SSH_Contents+1] = MyMonth; 1013 1014 Segment[SSH_Contents+2] = MyDay; 1015 Segment[SSH_Contents+3] = MyHour; 1016 Segment[SSH_Contents+4] 1017 = MyMinute; 1018 Segment[SSH_Contents+5] = MySecond; 1019 Segment[SSH_Contents+6] 1020 = MyWeekDay; 1021 Send_Segment(9); 1022 return true; 1023} 1024 1025 void SearchTag(String 1026 MyTag){ 1027 do { line = client.readStringUntil('\ 1028'); // read 1029 string from RSS site until this string is found 1030 } while (line.indexOf(MyTag) 1031 <0); 1032// Serial.print("Searchtag found "); 1033// Serial.print(MyTag); 1034// 1035 Serial.print(" in line "); 1036// Serial.println(line); 1037 } 1038 1039 void 1040 RemoveTags(){ 1041 line.remove(0,(line.indexOf(">"))+1); 1042 line.remove(line.indexOf("<"),line.indexOf(">")); 1043 1044 } // end of RemoveTags 1045 void RemoveIMGThroughGIF(){ 1046 line.remove(line.indexOf("img")-4,line.indexOf("gif")); 1047 1048 } // end of RIG 1049 1050bool Send_Segment(int bytestosend){ 1051 int trycounter 1052 = Repeat_Sends; 1053 do {Wire.beginTransmission(MasterChannel); // 1054 Send bytes result to Master 1055 Wire.write(Segment,bytestosend); // 1056// 1057 DDTl("segment transmission sent",bytestosend); 1058 Status_Flag = Wire.endTransmission(true); 1059 1060 delay(Segment_Delay);} while ((Status_Flag != 0) && (trycounter-- >0)); // 1061 keep trying until good status or counter gone 1062 if (trycounter > 0) {return 1063 true;} else {return false;} 1064} 1065
Downloadable files
Back of clock
Back of clock
Clock connections and assembly
Each connection is described.
Clock connections and assembly
Front
Front
Back of clock
Back of clock
Clock connections and assembly
Each connection is described.
Clock connections and assembly
Front
Front
Documentation
Sound files for FX sound board
Loaded onto Arduino FX sounboard (OGG files)
Sound files for FX sound board
Sound files for FX sound board
Loaded onto Arduino FX sounboard (OGG files)
Sound files for FX sound board
Sound files
Sound files
Comments
Only logged in users can leave comments