/ EInk_Weather_Station / adafruit_epd_weather / adafruit_epd_weather.ino
adafruit_epd_weather.ino
  1  // SPDX-FileCopyrightText: 2019 Dan Cogliano for Adafruit Industries
  2  //
  3  // SPDX-License-Identifier: MIT
  4  
  5  #include <time.h>
  6  #include <Adafruit_GFX.h>    // Core graphics library
  7  #include <Adafruit_ThinkInk.h>
  8  #include <Adafruit_NeoPixel.h>
  9  #include <ArduinoJson.h>        //https://github.com/bblanchon/ArduinoJson
 10  #include <SPI.h>
 11  #include <WiFiNINA.h>
 12  
 13  #include "secrets.h"
 14  #include "OpenWeatherMap.h"
 15  
 16  #include "Fonts/meteocons48pt7b.h"
 17  #include "Fonts/meteocons24pt7b.h"
 18  #include "Fonts/meteocons20pt7b.h"
 19  #include "Fonts/meteocons16pt7b.h"
 20  
 21  #include "Fonts/moon_phases20pt7b.h"
 22  #include "Fonts/moon_phases36pt7b.h"
 23  
 24  #include <Fonts/FreeSans9pt7b.h>
 25  #include <Fonts/FreeSans12pt7b.h>
 26  #include <Fonts/FreeSans18pt7b.h>
 27  #include <Fonts/FreeSansBold12pt7b.h>
 28  #include <Fonts/FreeSansBold24pt7b.h>
 29  
 30  #define SRAM_CS     8
 31  #define EPD_CS      10
 32  #define EPD_DC      9  
 33  #define EPD_RESET -1
 34  #define EPD_BUSY -1
 35  
 36  #define NEOPIXELPIN   40
 37  
 38  // This is for the 2.7" tricolor EPD
 39  ThinkInk_270_Tricolor_C44 gfx(EPD_DC, EPD_RESET, EPD_CS, SRAM_CS, EPD_BUSY);
 40  
 41  AirliftOpenWeatherMap owclient(&Serial);
 42  OpenWeatherMapCurrentData owcdata;
 43  OpenWeatherMapForecastData owfdata[3];
 44  
 45  Adafruit_NeoPixel neopixel = Adafruit_NeoPixel(1, NEOPIXELPIN, NEO_GRB + NEO_KHZ800);
 46  
 47    const char *moonphasenames[29] = {
 48      "New Moon",
 49      "Waxing Crescent",
 50      "Waxing Crescent",
 51      "Waxing Crescent",
 52      "Waxing Crescent",
 53      "Waxing Crescent",
 54      "Waxing Crescent",
 55      "Quarter",
 56      "Waxing Gibbous",
 57      "Waxing Gibbous",
 58      "Waxing Gibbous",
 59      "Waxing Gibbous",
 60      "Waxing Gibbous",
 61      "Waxing Gibbous",
 62      "Full Moon",
 63      "Waning Gibbous",
 64      "Waning Gibbous",
 65      "Waning Gibbous",
 66      "Waning Gibbous",
 67      "Waning Gibbous",
 68      "Waning Gibbous",
 69      "Last Quarter",
 70      "Waning Crescent",
 71      "Waning Crescent",
 72      "Waning Crescent",
 73      "Waning Crescent",
 74      "Waning Crescent",
 75      "Waning Crescent",
 76      "Waning Crescent"
 77  };
 78  
 79  int8_t readButtons(void) {
 80    uint16_t reading = analogRead(A3);
 81    //Serial.println(reading);
 82  
 83    if (reading > 600) {
 84      return 0; // no buttons pressed
 85    }
 86    if (reading > 400) {
 87      return 4; // button D pressed
 88    }
 89    if (reading > 250) {
 90      return 3; // button C pressed
 91    }
 92    if (reading > 125) {
 93      return 2; // button B pressed
 94    }
 95    return 1; // Button A pressed
 96  }
 97  
 98  bool wifi_connect(){
 99    
100    Serial.print("Connecting to WiFi... ");
101  
102    WiFi.setPins(SPIWIFI_SS, SPIWIFI_ACK, ESP32_RESETN, ESP32_GPIO0, &SPIWIFI);
103  
104    // check for the WiFi module:
105    if(WiFi.status() == WL_NO_MODULE) {
106      Serial.println("Communication with WiFi module failed!");
107      displayError("Communication with WiFi module failed!");
108      while(true);
109    }
110  
111    String fv = WiFi.firmwareVersion();
112    if (fv < "1.0.0") {
113      Serial.println("Please upgrade the firmware");
114    }
115  
116    neopixel.setPixelColor(0, neopixel.Color(0, 0, 255));
117    neopixel.show(); 
118    if(WiFi.begin(WIFI_SSID, WIFI_PASSWORD) == WL_CONNECT_FAILED)
119    {
120      Serial.println("WiFi connection failed!");
121      displayError("WiFi connection failed!");
122      return false;
123    }
124  
125    int wifitimeout = 15;
126    int wifistatus; 
127    while ((wifistatus = WiFi.status()) != WL_CONNECTED && wifitimeout > 0) {
128      delay(1000);
129      Serial.print(".");
130      wifitimeout--;
131    }
132    if(wifitimeout == 0)
133    {
134      Serial.println("WiFi connection timeout with error " + String(wifistatus));
135      displayError("WiFi connection timeout with error " + String(wifistatus));
136      neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
137      neopixel.show(); 
138      return false;
139    }
140    neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
141    neopixel.show(); 
142    Serial.println("Connected");
143    return true;
144  }
145  
146  void wget(String &url, int port, char *buff)
147  {
148    int pos1 = url.indexOf("/",0);
149    int pos2 = url.indexOf("/",8);
150    String host = url.substring(pos1+2,pos2);
151    String path = url.substring(pos2);
152    Serial.println("to wget(" + host + "," + path + "," + port + ")");
153    wget(host, path, port, buff);
154  }
155  
156  void wget(String &host, String &path, int port, char *buff)
157  {
158    //WiFiSSLClient client;
159    WiFiClient client;
160  
161    neopixel.setPixelColor(0, neopixel.Color(0, 0, 255));
162    neopixel.show();
163    client.stop();
164    if (client.connect(host.c_str(), port)) {
165      Serial.println("connected to server");
166      // Make a HTTP request:
167      client.println(String("GET ") + path + String(" HTTP/1.0"));
168      client.println("Host: " + host);
169      client.println("Connection: close");
170      client.println();
171  
172      uint32_t bytes = 0;
173      int capturepos = 0;
174      bool capture = false;
175      int linelength = 0;
176      char lastc = '\0';
177      while(true) 
178      {
179        while (client.available()) {
180          char c = client.read();
181          //Serial.print(c);
182          if((c == '\n') && (lastc == '\r'))
183          {
184            if(linelength == 0)
185            {
186              capture = true;
187            }
188            linelength = 0;
189          }
190          else if(capture)
191          {
192            buff[capturepos++] = c;
193            //Serial.write(c);
194          }
195          else
196          {
197            if((c != '\n') && (c != '\r'))
198              linelength++;
199          }
200          lastc = c;
201          bytes++;
202        }
203      
204        // if the server's disconnected, stop the client:
205        if (!client.connected()) {
206          //Serial.println();
207          Serial.println("disconnecting from server.");
208          client.stop();
209          buff[capturepos] = '\0';
210          Serial.println("captured " + String(capturepos) + " bytes");
211          break;
212        }
213      }
214    }
215    else
216    {
217      Serial.println("problem connecting to " + host + ":" + String(port));
218      buff[0] = '\0';
219    }
220    neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
221    neopixel.show(); 
222  }
223  
224  int getStringLength(String s)
225  {
226    int16_t  x = 0, y = 0;
227    uint16_t w, h;
228    gfx.getTextBounds(s, 0, 0, &x, &y, &w, &h);
229    return w + x;
230  }
231  
232  /*
233  return value is percent of moon cycle ( from 0.0 to 0.999999), i.e.:
234  
235  0.0: New Moon
236  0.125: Waxing Crescent Moon
237  0.25: Quarter Moon
238  0.375: Waxing Gibbous Moon
239  0.5: Full Moon
240  0.625: Waning Gibbous Moon
241  0.75: Last Quarter Moon
242  0.875: Waning Crescent Moon
243  
244  */
245  float getMoonPhase(time_t tdate)
246  {
247  
248    time_t newmoonref = 1263539460; //known new moon date (2010-01-15 07:11)
249    // moon phase is 29.5305882 days, which is 2551442.82048 seconds
250    float phase = abs( tdate - newmoonref) / (double)2551442.82048;
251    phase -= (int)phase; // leave only the remainder
252    if(newmoonref > tdate)
253    phase = 1 - phase;
254    return phase;
255  }
256  
257  void displayError(String str)
258  {
259      // show error on display
260      neopixel.setPixelColor(0, neopixel.Color(255, 0, 0));
261      neopixel.show(); 
262  
263      Serial.println(str);
264  
265      gfx.setTextColor(EPD_BLACK);
266      gfx.powerUp();
267      gfx.clearBuffer();
268      gfx.setTextWrap(true);
269      gfx.setCursor(10,60);
270      gfx.setFont(&FreeSans12pt7b);
271      gfx.print(str);
272      gfx.display();
273      neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
274      neopixel.show();
275  }
276  
277  void displayHeading(OpenWeatherMapCurrentData &owcdata)
278  {
279  
280    time_t local = owcdata.observationTime + owcdata.timezone;
281    struct tm *timeinfo = gmtime(&local);
282    char datestr[80];
283    // date
284    //strftime(datestr,80,"%a, %d %b %Y",timeinfo);
285    strftime(datestr,80,"%a, %b %d",timeinfo);
286    gfx.setFont(&FreeSans18pt7b);
287    gfx.setCursor((gfx.width()-getStringLength(datestr))/2,30);
288    gfx.print(datestr);
289    
290    // city
291    strftime(datestr,80,"%A",timeinfo);
292    gfx.setFont(&FreeSansBold12pt7b);
293    gfx.setCursor((gfx.width()-getStringLength(owcdata.cityName))/2,60);
294    gfx.print(owcdata.cityName);
295  }
296  
297  void displayForecastDays(OpenWeatherMapCurrentData &owcdata, OpenWeatherMapForecastData owfdata[], int count = 3)
298  {
299    for(int i=0; i < count; i++)
300    {
301      // day
302  
303      time_t local = owfdata[i].observationTime + owcdata.timezone;
304      struct tm *timeinfo = gmtime(&local);
305      char strbuff[80];
306      strftime(strbuff,80,"%I",timeinfo);
307      String datestr = String(atoi(strbuff));
308      strftime(strbuff,80,"%p",timeinfo);
309      // convert AM/PM to lowercase
310      strbuff[0] = tolower(strbuff[0]);
311      strbuff[1] = tolower(strbuff[1]);
312      datestr = datestr + " " + String(strbuff);
313      gfx.setFont(&FreeSans9pt7b);
314      gfx.setCursor(i*gfx.width()/3 + (gfx.width()/3-getStringLength(datestr))/2,94);
315      gfx.print(datestr);
316      
317      // weather icon
318      String wicon = owclient.getMeteoconIcon(owfdata[i].icon);
319      gfx.setFont(&meteocons20pt7b);
320      gfx.setCursor(i*gfx.width()/3 + (gfx.width()/3-getStringLength(wicon))/2,134);
321      gfx.print(wicon);
322    
323      // weather main description
324      gfx.setFont(&FreeSans9pt7b);
325      gfx.setCursor(i*gfx.width()/3 + (gfx.width()/3-getStringLength(owfdata[i].main))/2,154);
326      gfx.print(owfdata[i].main);
327  
328      // temperature
329      int itemp = (int)(owfdata[i].temp + .5);
330      int color = EPD_BLACK;
331      if((OWM_METRIC && itemp >= METRIC_HOT)|| (!OWM_METRIC && itemp >= ENGLISH_HOT))
332        color = EPD_RED;
333      gfx.setTextColor(color);
334      gfx.setFont(&FreeSans9pt7b);
335      gfx.setCursor(i*gfx.width()/3 + (gfx.width()/3-getStringLength(String(itemp)))/2,172);
336      gfx.print(itemp);
337      gfx.drawCircle(i*gfx.width()/3 + (gfx.width()/3-getStringLength(String(itemp)))/2 + getStringLength(String(itemp)) + 6,163,3,color);
338      gfx.drawCircle(i*gfx.width()/3 + (gfx.width()/3-getStringLength(String(itemp)))/2 + getStringLength(String(itemp)) + 6,163,2,color); 
339      gfx.setTextColor(EPD_BLACK);   
340    }  
341  }
342  
343  void displayForecast(OpenWeatherMapCurrentData &owcdata, OpenWeatherMapForecastData owfdata[], int count = 3)
344  {
345    gfx.powerUp();
346    gfx.clearBuffer();
347    neopixel.setPixelColor(0, neopixel.Color(0, 255, 0));
348    neopixel.show();  
349  
350    gfx.setTextColor(EPD_BLACK);
351    displayHeading(owcdata);
352  
353    displayForecastDays(owcdata, owfdata, count);
354    gfx.display();
355    gfx.powerDown();
356    neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
357    neopixel.show(); 
358  }
359  
360  void displayAllWeather(OpenWeatherMapCurrentData &owcdata, OpenWeatherMapForecastData owfdata[], int count = 3)
361  {
362    gfx.powerUp();
363    gfx.clearBuffer();
364    neopixel.setPixelColor(0, neopixel.Color(0, 255, 0));
365    neopixel.show();  
366  
367    gfx.setTextColor(EPD_BLACK);
368  
369    // date string
370    time_t local = owcdata.observationTime + owcdata.timezone;
371    struct tm *timeinfo = gmtime(&local);
372    char datestr[80];
373    // date
374    //strftime(datestr,80,"%a, %d %b %Y",timeinfo);
375    strftime(datestr,80,"%a, %b %d %Y",timeinfo);
376    gfx.setFont(&FreeSans9pt7b);
377    gfx.setCursor((gfx.width()-getStringLength(datestr))/2,14);
378    gfx.print(datestr);
379    
380    // weather icon
381    String wicon = owclient.getMeteoconIcon(owcdata.icon);
382    gfx.setFont(&meteocons24pt7b);
383    gfx.setCursor((gfx.width()/3-getStringLength(wicon))/2,56);
384    gfx.print(wicon);
385  
386    // weather main description
387    gfx.setFont(&FreeSans9pt7b);
388    gfx.setCursor((gfx.width()/3-getStringLength(owcdata.main))/2,72);
389    gfx.print(owcdata.main);
390  
391    // temperature
392    gfx.setFont(&FreeSansBold24pt7b);
393    int itemp = owcdata.temp + .5;
394    int color = EPD_BLACK;
395    if((OWM_METRIC && (int)itemp >= METRIC_HOT)|| (!OWM_METRIC && (int)itemp >= ENGLISH_HOT))
396      color = EPD_RED;
397    gfx.setTextColor(color);
398    gfx.setCursor(gfx.width()/3 + (gfx.width()/3-getStringLength(String(itemp)))/2,58);
399    gfx.print(itemp);
400    gfx.setTextColor(EPD_BLACK);
401  
402    // draw temperature degree as a circle (not available as font character
403    gfx.drawCircle(gfx.width()/3 + (gfx.width()/3 + getStringLength(String(itemp)))/2 + 8, 58-30,4,color);
404    gfx.drawCircle(gfx.width()/3 + (gfx.width()/3 + getStringLength(String(itemp)))/2 + 8, 58-30,3,color);
405  
406    // draw moon
407    // draw Moon Phase
408    float moonphase = getMoonPhase(owcdata.observationTime);
409    int moonage = 29.5305882 * moonphase;
410    //Serial.println("moon age: " + String(moonage));
411    // convert to appropriate icon
412    String moonstr = String((char)((int)'A' + (int)(moonage*25./30)));
413    gfx.setFont(&moon_phases20pt7b);
414    // font lines look a little thin at this size, drawing it a few times to thicken the lines
415    gfx.setCursor(2*gfx.width()/3 + (gfx.width()/3-getStringLength(moonstr))/2,56);
416    gfx.print(moonstr);  
417    gfx.setCursor(2*gfx.width()/3 + (gfx.width()/3-getStringLength(moonstr))/2+1,56);
418    gfx.print(moonstr);  
419    gfx.setCursor(2*gfx.width()/3 + (gfx.width()/3-getStringLength(moonstr))/2,56-1);
420    gfx.print(moonstr);  
421  
422    // draw moon phase name
423    int currentphase = moonphase * 28. + .5;
424    gfx.setFont(); // system font (smallest available)
425    gfx.setCursor(2*gfx.width()/3 + max(0,(gfx.width()/3 - getStringLength(moonphasenames[currentphase]))/2),62);
426    gfx.print(moonphasenames[currentphase]);
427  
428  
429    displayForecastDays(owcdata, owfdata, count);
430    gfx.display();
431    gfx.powerDown();
432    neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
433    neopixel.show(); 
434    
435  }
436  
437  void displayCurrentConditions(OpenWeatherMapCurrentData &owcdata)
438  {
439    gfx.powerUp();
440    gfx.clearBuffer();
441    neopixel.setPixelColor(0, neopixel.Color(0, 255, 0));
442    neopixel.show();  
443  
444    gfx.setTextColor(EPD_BLACK);
445    displayHeading(owcdata);
446  
447    // weather icon
448    String wicon = owclient.getMeteoconIcon(owcdata.icon);
449    gfx.setFont(&meteocons48pt7b);
450    gfx.setCursor((gfx.width()/2-getStringLength(wicon))/2,156);
451    gfx.print(wicon);
452  
453    // weather main description
454    gfx.setFont(&FreeSans9pt7b);
455    gfx.setCursor(gfx.width()/2 + (gfx.width()/2-getStringLength(owcdata.main))/2,160);
456    gfx.print(owcdata.main);
457  
458    // temperature
459    gfx.setFont(&FreeSansBold24pt7b);
460    int itemp = owcdata.temp + .5;
461    int color = EPD_BLACK;
462    if((OWM_METRIC && (int)itemp >= METRIC_HOT)|| (!OWM_METRIC && (int)itemp >= ENGLISH_HOT))
463      color = EPD_RED;
464    gfx.setTextColor(color);
465    gfx.setCursor(gfx.width()/2 + (gfx.width()/2-getStringLength(String(itemp)))/2,130);
466    gfx.print(itemp);
467    gfx.setTextColor(EPD_BLACK);
468    
469    // draw temperature degree as a circle (not available as font character
470    gfx.drawCircle(gfx.width()/2 + (gfx.width()/2 + getStringLength(String(itemp)))/2 + 10, 130-26,4,color);
471    gfx.drawCircle(gfx.width()/2 + (gfx.width()/2 + getStringLength(String(itemp)))/2 + 10, 130-26,3,color);
472    
473    gfx.display();
474    gfx.powerDown();
475    neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
476    neopixel.show(); 
477  }
478  
479  void displaySunMoon(OpenWeatherMapCurrentData &owcdata)
480  {
481    
482    gfx.powerUp();
483    gfx.clearBuffer();
484    neopixel.setPixelColor(0, neopixel.Color(0, 255, 0));
485    neopixel.show();  
486  
487    gfx.setTextColor(EPD_BLACK);
488    displayHeading(owcdata);
489  
490    // draw Moon Phase
491    float moonphase = getMoonPhase(owcdata.observationTime);
492    int moonage = 29.5305882 * moonphase;
493    // convert to appropriate icon
494    String moonstr = String((char)((int)'A' + (int)(moonage*25./30)));
495    gfx.setFont(&moon_phases36pt7b);
496    gfx.setCursor((gfx.width()/3-getStringLength(moonstr))/2,140);
497    gfx.print(moonstr);
498  
499    // draw moon phase name
500    int currentphase = moonphase * 28. + .5;
501    gfx.setFont(&FreeSans9pt7b);
502    gfx.setCursor(gfx.width()/3 + max(0,(gfx.width()*2/3 - getStringLength(moonphasenames[currentphase]))/2),110);
503    gfx.print(moonphasenames[currentphase]);
504  
505    // draw sunrise/sunset
506  
507    // sunrise/sunset times
508    // sunrise
509  
510    time_t local = owcdata.sunrise + owcdata.timezone + 30;  // round to nearest minute
511    struct tm *timeinfo = gmtime(&local);
512    char strbuff[80];
513    strftime(strbuff,80,"%I",timeinfo);
514    String datestr = String(atoi(strbuff));
515    strftime(strbuff,80,":%M %p",timeinfo);
516    datestr = datestr + String(strbuff) + " - ";
517    // sunset
518    local = owcdata.sunset + owcdata.timezone + 30; // round to nearest minute
519    timeinfo = gmtime(&local);
520    strftime(strbuff,80,"%I",timeinfo);
521    datestr = datestr + String(atoi(strbuff));
522    strftime(strbuff,80,":%M %p",timeinfo);
523    datestr = datestr + String(strbuff);
524  
525    gfx.setFont(&FreeSans9pt7b);
526    int datestrlen = getStringLength(datestr);
527    int xpos = (gfx.width() - datestrlen)/2;
528    gfx.setCursor(xpos,166);
529    gfx.print(datestr);
530  
531    // draw sunrise icon
532    // sun icon is "B"
533    String wicon = "B";
534    gfx.setFont(&meteocons16pt7b);
535    gfx.setCursor(xpos - getStringLength(wicon) - 12,174);
536    gfx.print(wicon);
537  
538    // draw sunset icon
539    // sunset icon is "A"
540    wicon = "A";
541    gfx.setCursor(xpos + datestrlen + 12,174);
542    gfx.print(wicon);
543  
544    gfx.display();
545    gfx.powerDown();
546    neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
547    neopixel.show(); 
548  }
549  
550  void setup() {
551    neopixel.begin();
552    neopixel.show();
553    
554    gfx.begin();
555    Serial.println("ePaper display initialized");
556    gfx.setRotation(2);
557    gfx.setTextWrap(false);
558  
559  }
560  
561  void loop() {
562    char data[4000];
563    static uint32_t timer = millis();
564    static uint8_t lastbutton = 1;
565    static bool firsttime = true;
566  
567    int button = readButtons();
568    
569    // update weather data at specified interval or when button 4 is pressed
570    if((millis() >= (timer + 1000*60*UPDATE_INTERVAL)) || (button == 4) || firsttime)
571    {
572      Serial.println("getting weather data");
573      firsttime = false;
574      timer = millis();
575      int retry = 6;
576      while(!wifi_connect())
577      {
578        delay(5000);
579        retry--;
580        if(retry < 0)
581        {
582          displayError("Can not connect to WiFi, press reset to restart");
583          while(1);      
584        }
585      }
586      String urlc = owclient.buildUrlCurrent(OWM_KEY,OWM_LOCATION);
587      Serial.println(urlc);
588      retry = 6;
589      do
590      {
591        retry--;
592        wget(urlc,80,data);
593        if(strlen(data) == 0 && retry < 0)
594        {
595          displayError("Can not get weather data, press reset to restart");
596          while(1);      
597        }
598      }
599      while(strlen(data) == 0);
600      Serial.println("data retrieved:");
601      Serial.println(data);
602      retry = 6;
603      while(!owclient.updateCurrent(owcdata,data))
604      {
605        retry--;
606        if(retry < 0)
607        {
608          displayError(owclient.getError());
609          while(1);
610        }
611        delay(5000);
612      }
613    
614      String urlf = owclient.buildUrlForecast(OWM_KEY,OWM_LOCATION);
615      Serial.println(urlf);
616      wget(urlf,80,data);
617      Serial.println("data retrieved:");
618      Serial.println(data);
619      if(!owclient.updateForecast(owfdata[0],data,0))
620      {
621        displayError(owclient.getError());
622        while(1);
623      }
624      if(!owclient.updateForecast(owfdata[1],data,2))
625      {
626        displayError(owclient.getError());
627        while(1);
628      }
629      if(!owclient.updateForecast(owfdata[2],data,4))
630      {
631        displayError(owclient.getError());
632        while(1);
633      }
634  
635      switch(lastbutton)
636      {
637        case 1:        
638          displayAllWeather(owcdata,owfdata,3);
639          break;
640        case 2:
641          displayCurrentConditions(owcdata);
642          break;
643        case 3:
644          displaySunMoon(owcdata);
645          break;
646      }
647    }
648  
649    if (button == 0) {
650      return;
651    }
652  
653    Serial.print("Button "); Serial.print(button); Serial.println(" pressed");
654  
655    if (button == 1) {
656      displayAllWeather(owcdata,owfdata,3);
657      lastbutton = button;
658    }
659    if (button == 2) {
660      //displayForecast(owcdata,owfdata,3);
661      displayCurrentConditions(owcdata);
662      lastbutton = button;
663    }
664    if (button == 3) {
665      displaySunMoon(owcdata);
666      lastbutton = button;
667    }
668    
669    // wait until button is released
670    while (readButtons()) {
671      delay(10);
672    }
673  
674  }