<commit_before>////////////////////////////////////////////////////////////////////////
//    Weather Forecast Display                                       //
//           <NAME>                                              //
//            12/29/16                                               //
//     Based on the following:                                       //
// http://educ8s.tv/art-deco-weather-forecast-display/               //
//                                                                   //
//     See the following for a description on JSON decoding:         //                                                    //
// https://www.arduino.cc/en/Tutorial.WiFi101WeatherAudioNotifier    //
//                                                                   //
///////////////////////////////////////////////////////////////////////

#include "WeatherDisplay.h"
#include <ESP8266WiFi.h>
#include <ArduinoJson.h>
#include <Adafruit_ST7735.h>
#include <Adafruit_GFX.h>

WiFiClient client;
String serverName = "api.openweathermap.org"; // Remote server we will connect to
String result;
bool night = false;
bool toggleDisplay = HIGH;
bool pastToggleDisplay = !toggleDisplay;

Adafruit_ST7735 tft(TFT_CS, TFT_DC, TFT_RST);

extern unsigned char cloud[];
extern unsigned char thunder[];
extern unsigned char wind[];
/* Fill in these four variables within NetworkConnection.c
   with your ssid, password, APIKey and cityID */
extern const char* ssid; // SSID of local network
extern const char* password; // Password on network
extern const char* APIKey; // openweathermap.org API key
extern const char* cityID; // openweathermap.org city

void setup() {
#ifdef DEBUG
  Serial.begin(9600);
#endif
  pinMode(TOGGLEDISPLAY_SW, INPUT);
  pinMode(ALIVE_LED, OUTPUT);

  tft.initR(INITR_BLACKTAB);   // initialize a ST7735S chip, black tab
  tft.fillScreen(BLACK);

#ifdef DEBUG
  Serial.println("Connecting");
#endif
  tft.setTextColor(YELLOW);
  tft.setTextSize(1);
  tft.setCursor(30, 5);
  tft.print("Version ");
  tft.print(VERSION);
  tft.setCursor(30, 15);
  tft.print(CREDIT);
  tft.setCursor(30, 80);
  tft.setTextColor(WHITE);
  tft.print("Connecting...");

  int startCounter = 0;

  WiFi.mode(WIFI_STA);
  WiFi.begin (ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
#ifdef DEBUG
    Serial.print(".");
#endif
    startCounter++;
    tft.setTextSize(1);
    tft.setTextColor(YELLOW);
    if (startCounter <= 1) tft.setCursor(20, 100);
    tft.print(startCounter);
    if (startCounter >= 10) {
#ifdef DEBUG
      Serial.println("\nTimed out. Starting again...");
#endif
      return;
    }
    delay(500);
  }
#ifdef DEBUG
  Serial.println("WiFi connected");
#endif
} // setup

void loop() {
  static int counter = GETDATACOUNT;
  bool flasher;

  toggleDisplay = digitalRead(TOGGLEDISPLAY_SW);
  if (toggleDisplay != pastToggleDisplay) {
    pastToggleDisplay = toggleDisplay;
    counter = GETDATACOUNT;
  }

  if (counter >= GETDATACOUNT) //Get new data
  {
    counter = 0;
    getWeatherData();

  }
  else {
    counter++;
    flasher = (counter % 2 == 0); // 0 or 1
    // Flash the alive LED so we know the system has not crashed
    analogWrite(ALIVE_LED, flasher * ALIVEBRIGHTNESS);
    // Seed random number generator
    randomSeed(counter + flasher);
    delay(1000);
#ifdef DEBUG
    Serial.print("counter = ");
    Serial.println(counter);
#endif
  }
}

void getWeatherData() //client function to send/receive GET request data.
{
#ifdef DEBUG
  Serial.println("Getting Weather Data");
#endif

  String APIKEY(APIKey);
  String CityID(cityID);
  if (client.connect(serverName.c_str(), PORT)) { // Starts client connection, checks for connection
    client.println("GET /data/2.5/forecast?id=" + CityID + "&units=imperial&cnt=1&APPID=" + APIKEY);
    client.println("Host: " + serverName);
    client.println("User-Agent: ArduinoWiFi/1.1");
    client.println("Connection: close");
    client.println();
  }
  else {
#ifdef DEBUG
    Serial.println("connection failed"); // Error message if no client connect
    Serial.println();
#endif
    return;
  }

  while (client.connected() && !client.available()) delay(1); //waits for data

#ifdef DEBUG
  Serial.println("Waiting for data");
#endif

  while (client.connected() || client.available()) { //connected or data available
    char c = client.read(); // Gets byte from WIFI buffer
    result = result + c;
  }

  client.stop(); //stop client
  result.replace('[', ' ');
  result.replace(']', ' ');
#ifdef DEBUG
  Serial.println(result);
#endif

  char jsonArray [result.length() + 1];
  result.toCharArray(jsonArray, sizeof(jsonArray));
  jsonArray[result.length() + 1] = '\0';

  StaticJsonBuffer<1024> json_buf;
  JsonObject &root = json_buf.parseObject(jsonArray);
  if (!root.success())
  {
#ifdef DEBUG
    Serial.println("parseObject() failed");
#endif
    return;
  }

  String location = root["city"]["name"];
  String temperature = root["list"]["main"]["temp"];
  String humidity = root["list"]["main"]["humidity"];
  String weather = root["list"]["weather"]["main"];
  String description = root["list"]["weather"]["description"];
  String idString = root["list"]["weather"]["id"];
  String timeS = root["list"]["dt_txt"];
  String latitude = root["city"]["coord"]["lat"];
  String longitude = root["city"]["coord"]["lon"];
  String windSpeed = root["list"]["wind"]["speed"];
  String windDirection = root["list"]["wind"]["deg"];

  bool DST = false;
  int riseI, setI, monthI, dayI, yearI;
  String timeUTC = timeS;
  timeUTC = timeUTC.substring(timeUTC.length() - 8, timeUTC.length()); // Strip out the time (XX:XX:XX)
  timeS = convertGMTTimeToLocal(timeS, latitude, longitude,
                                &riseI, &setI, &monthI, &dayI, &yearI, &DST);
  String dateS = String(monthI) + '/' + String(dayI) + '/' + String(yearI);

  int length = temperature.length();
  if (length == 5)
  {
    temperature.remove(length - 1);
  }
#ifdef DEBUG
  Serial.println();
  Serial.println(dateS);
  Serial.println("Today is " + getDayOfWeek(dayOfWeek(yearI, monthI, dayI)));
  Serial.println((DST) ? "It's DST" : "It's ST");
  Serial.println((night) ? "It is Night time" : "It is Day time");
  Serial.print("City ");
  Serial.println(location);
  Serial.print("Weather Icon ID ");
  Serial.println(idString);
  Serial.print("Weather ");
  Serial.println(weather);
  Serial.print("Description ");
  Serial.println(description);
  Serial.print("Temperature ");
  Serial.println(temperature + "f");
  Serial.print("Humidity ");
  Serial.println(humidity + "% RH");
  Serial.print("Wind speed  ");
  Serial.println(windSpeed + " MPH");
  Serial.print("Wind direction  ");
  Serial.println(windDirection + " Deg");
  Serial.print("Latitude ");
  Serial.println(latitude + " Deg");
  Serial.print("Longitude ");
  Serial.println(longitude + " Deg");
  Serial.print("Sunrise ");
  Serial.print(riseI);
  Serial.println("am");
  Serial.print("Sunset ");
  Serial.print(setI);
  Serial.println("pm");
  Serial.print("Time last updated ");
  Serial.println(timeS);
  Serial.print("UTC ");
  Serial.println(timeUTC);
#endif // #ifdef DEBUG

  clearScreen();

  if (toggleDisplay) {
    // Display the aux weather
    printAuxData(location,
                 temperature,
                 humidity,
                 description,
                 windSpeed,
                 windDirection,
                 timeS,
                 riseI,
                 setI,
                 idString.toInt(),
                 dateS,
                 timeUTC,
                 dayI,
                 monthI,
                 yearI,
                 DST);
  }
  else {
    // Display the main weather
    printMainData(humidity,
                  temperature,
                  timeS,
                  idString.toInt(),
                  dateS);
  }
} // void getWeatherData()

// Displays the aux weather
void printAuxData(String location,
                  String temperature,
                  String humidity,
                  String description,
                  String windSpeed,
                  String windDirection,
                  String time,
                  int rise,
                  int set,
                  int weatherID,
                  String dateS,
                  String timeUTC,
                  int dayI,
                  int monthI,
                  int yearI,
                  bool DST)
{
  static long lastRandomNumber;
  long randomColor;
  long anotherRandomColor;
  unsigned int textColor[] = {
    BLUE,
    RED,
    GREEN,
    CYAN,
    MAGENTA,
    YELLOW,
    WHITE
  };

  // Get random number
  byte maxRandomNumber = sizeof(textColor) / sizeof(unsigned int);
  randomColor = random(maxRandomNumber);
  // If random number repeats, try again
  while (randomColor == lastRandomNumber) {
    randomColor = random(maxRandomNumber);
  }
  lastRandomNumber = randomColor;
  anotherRandomColor = random(maxRandomNumber);
  // If random number repeats first random number, try again
  while (anotherRandomColor == randomColor) {
    anotherRandomColor = random(maxRandomNumber);
  }
  tft.setTextColor(textColor[randomColor]);
  tft.setTextSize(1);
  tft.setCursor(0, 0);
  tft.print("Location: ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.println(location);
  tft.setTextColor(textColor[randomColor]);
  tft.print("Temperature: ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.print(temperature.toInt());
  tft.println(" F");
  tft.setTextColor(textColor[randomColor]);
  tft.print("Humidity: ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.print(humidity.toInt());
  tft.println("% RH");
  tft.setTextColor(textColor[randomColor]);
  tft.print("Weather ID: ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.println(weatherID);
  tft.setTextColor(textColor[randomColor]);
  tft.print("Sky: ");
  tft.setTextColor(textColor[anotherRandomColor]);
  if ((sizeof("Sky: ") + description.length()) >= 21 /*Length of line*/) description.replace(' ', '\n');
  tft.println(description);
  tft.setTextColor(textColor[randomColor]);
  tft.print("Wind Spd: ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.print(windSpeed.toInt());
  tft.println(" Knots");
  tft.setTextColor(textColor[randomColor]);
  tft.print("Wind Dir: ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.print(windDirection.toInt());
  tft.println(" Degs");
  tft.setTextColor(textColor[randomColor]);
  tft.print("Time LU: ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.println(time);
  tft.setTextColor(textColor[randomColor]);
  tft.print("UTC: ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.println(timeUTC);
  tft.setTextColor(textColor[randomColor]);
  tft.print("Date: ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.println(dateS);
  tft.setTextColor(textColor[randomColor]);
  tft.print("Sunrise: ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.print(rise);
  tft.println(":00am");
  tft.setTextColor(textColor[randomColor]);
  tft.print("Sunset: ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.print(set);
  tft.println(":00pm");
  tft.setTextColor(textColor[randomColor]);
  tft.print("It's ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.println((DST) ? "DST" : "ST");
  tft.setTextColor(textColor[randomColor]);
  tft.print("Today is ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.println(getDayOfWeek(dayOfWeek(yearI, monthI, dayI)));
  tft.setTextColor(textColor[randomColor]);
  tft.print("It is ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.print((night) ? "Night " : "Day ");
  tft.setTextColor(textColor[randomColor]);
  tft.println("time");
}

// Displays the main weather
void printMainData(String humidityString,
                   String temperature,
                   String time,
                   int weatherID,
                   String dateS)
{
  tft.setTextColor(YELLOW);
  tft.setTextSize(1);
  tft.setCursor(17, 5);
  tft.print("Updated " + time);
  tft.setTextColor(WHITE);
  tft.setTextSize(2);
  tft.setCursor(27, 20);
  tft.print(temperature);
  tft.setTextSize(1);
  tft.print("o");
  tft.setTextSize(2);
  tft.print(" F");

  printWeatherIcon(weatherID);

  tft.setTextColor(WHITE);
  tft.setTextSize(2);
  tft.setCursor(27, 132);
  tft.print(humidityString);
  tft.print("% RH");
  tft.setTextColor(YELLOW);
  tft.setTextSize(1);
  tft.setCursor(40, 150);
  tft.print(dateS);
}

void printWeatherIcon(int id)
{
  switch (id)
  {
    case 800: drawClearWeather(); break;
    case 801: drawFewClouds(); break;
    case 802: drawFewClouds(); break;
    case 803: drawFewClouds(); break;
    case 804: drawCloud(); break;

    case 200: drawThunderstorm(); break;
    case 201: drawThunderstorm(); break;
    case 202: drawThunderstorm(); break;
    case 210: drawThunderstorm(); break;
    case 211: drawThunderstorm(); break;
    case 212: drawThunderstorm(); break;
    case 221: drawThunderstorm(); break;
    case 230: drawThunderstorm(); break;
    case 231: drawThunderstorm(); break;
    case 232: drawThunderstorm(); break;

    case 300: drawLightRain(); break;
    case 301: drawLightRain(); break;
    case 302: drawLightRain(); break;
    case 310: drawLightRain(); break;
    case 311: drawLightRain(); break;
    case 312: drawLightRain(); break;
    case 313: drawLightRain(); break;
    case 314: drawLightRain(); break;
    case 321: drawLightRain(); break;

    case 500: drawLightRainWithSunOrMoon(); break;
    case 501: drawModerateRain(); break;
    case 502: drawLightRainWithSunOrMoon(); break;
    case 503: drawLightRainWithSunOrMoon(); break;
    case 504: drawLightRainWithSunOrMoon(); break;
    case 511: drawLightRain(); break;
    case 520: drawModerateRain(); break;
    case 521: drawModerateRain(); break;
    case 522: drawHeavyRain(); break;
    case 531: drawHeavyRain(); break;

    case 600: drawLightSnowfall(); break;
    case 601: drawModerateSnowfall(); break;
    case 602: drawHeavySnowfall(); break;
    case 611: drawLightSnowfall(); break;
    case 612: drawLightSnowfall(); break;
    case 615: drawLightSnowfall(); break;
    case 616: drawLightSnowfall(); break;
    case 620: drawLightSnowfall(); break;
    case 621: drawModerateSnowfall(); break;
    case 622: drawHeavySnowfall(); break;

    case 701: drawFog(); break;
    case 711: drawFog(); break;
    case 721: drawFog(); break;
    case 731: drawFog(); break;
    case 741: drawFog(); break;
    case 751: drawFog(); break;
    case 761: drawFog(); break;
    case 762: drawFog(); break;
    case 771: drawFog(); break;
    case 781: drawFog(); break;

    default: break;
  }
}

String convertGMTTimeToLocal(String timeS, String latitude, String longitude,
                             int* riseI, int* setI, int* monthI, int* dayI, int* yearI, bool* DST)
{
  double rise, set;

  int length = timeS.length();
  String yearS = timeS.substring(0, 4);
  int year = yearS.toInt();
  String monthS = timeS.substring(5, 7);
  int month = monthS.toInt();
  String dayS = timeS.substring(8, 10);
  int day = dayS.toInt();

  timeS = timeS.substring(length - 8, length - 6); // Strip out the hours
  int time = timeS.toInt();

#ifdef ERROROFFSET
  // For some reason, openweathermap.org time is off by some (???)
  // hours from UTC. Need to compensate
  int TimeErrorOffset = -3;
  if (((time > 18) && (time < 21)) ||
      (time == 12)) {
    time += (TimeErrorOffset + 2);
  }
  else if (time == 0) {
    time += TimeErrorOffset;
    convertDate((double)longitude.toFloat(), &year, &month, &day);
  }
  else {
    time += TimeErrorOffset;
  }
#endif

  // Convert UTC time & date to local time & date
  *DST = convertToLocal(&time, &year, &month,
                        &day, (double)longitude.toFloat(), true); // true means date and DST conversion

  *monthI = month;
  *dayI = day;
  *yearI = year;

  // Get sunrise and sunset time in UTC
  int rs = sun_rise_set(year, month, day,
                        (double)longitude.toFloat(),
                        (double)latitude.toFloat(),
                        &rise, &set);
  *riseI = (int)round(rise);
  *setI = (int)round(set);

  // Convert UTC sunrise time to local sunrise time
  (void) convertToLocal(riseI, &year, &month,
                        &day, (double)longitude.toFloat(), false); // false means no date or DST conversion

  // Convert UTC sunset time to local sunset time
  (void) convertToLocal(setI, &year, &month,
                        &day, (double)longitude.toFloat(), false); // false means no date or DST conversion

  // Determine night or day
  night = (time > *setI ||  time < *riseI);

  if (*riseI >= 12) { // Convert to 12 hour format
    if (*riseI > 12) *riseI -= 12;
  }
  if (*setI >= 12) { // Convert to 12 hour format
    if (*setI > 12) *setI -= 12;
  }

  char AMPM[] = "am";
  if (time >= 12) { // Convert to 12 hour format
    if (time > 12) time -= 12;
    strcpy(AMPM, "pm");
  }

  timeS = String(time) + ":00" + AMPM;

  return timeS;
}

void clearScreen()
{
  tft.fillScreen(BLACK);
}

void drawClearWeather()
{
  if (night)
  {
    drawTheMoon();
  } else
  {
    drawTheSun();
  }
}

void drawFewClouds()
{
  if (night)
  {
    drawCloudAndTheMoon();
  } else
  {
    drawCloudWithSun();
  }
}

void drawTheSun()
{
  tft.fillCircle(64, 80, 26, YELLOW);
}

void drawTheFullMoon()
{
  tft.fillCircle(64, 80, 26, GREY);
}

void drawTheMoon()
{
  tft.fillCircle(64, 80, 26, GREY);
  tft.fillCircle(75, 73, 26, BLACK);
}

void drawCloud()
{
  tft.drawBitmap(0, 35, cloud, 128, 90, GREY);
}

void drawCloudWithSun()
{
  tft.fillCircle(73, 70, 20, YELLOW);
  tft.drawBitmap(0, 36, cloud, 128, 90, BLACK);
  tft.drawBitmap(0, 40, cloud, 128, 90, GREY);
}

void drawLightRainWithSunOrMoon()
{
  if (night)
  {
    drawCloudTheMoonAndRain();
  } else
  {
    drawCloudSunAndRain();
  }
}

void drawLightRain()
{
  tft.drawBitmap(0, 35, cloud, 128, 90, GREY);
  tft.fillRoundRect(50, 105, 3, 13, 1, BLUE);
  tft.fillRoundRect(65, 105, 3, 13, 1, BLUE);
  tft.fillRoundRect(80, 105, 3, 13, 1, BLUE);
}

void drawModerateRain()
{
  tft.drawBitmap(0, 35, cloud, 128, 90, GREY);
  tft.fillRoundRect(50, 105, 3, 15, 1, BLUE);
  tft.fillRoundRect(57, 102, 3, 15, 1, BLUE);
  tft.fillRoundRect(65, 105, 3, 15, 1, BLUE);
  tft.fillRoundRect(72, 102, 3, 15, 1, BLUE);
  tft.fillRoundRect(80, 105, 3, 15, 1, BLUE);
}

void drawHeavyRain()
{
  tft.drawBitmap(0, 35, cloud, 128, 90, GREY);
  tft.fillRoundRect(43, 102, 3, 15, 1, BLUE);
  tft.fillRoundRect(50, 105, 3, 15, 1, BLUE);
  tft.fillRoundRect(57, 102, 3, 15, 1, BLUE);
  tft.fillRoundRect(65, 105, 3, 15, 1, BLUE);
  tft.fillRoundRect(72, 102, 3, 15, 1, BLUE);
  tft.fillRoundRect(80, 105, 3, 15, 1, BLUE);
  tft.fillRoundRect(87, 102, 3, 15, 1, BLUE);
}

void drawThunderstorm()
{
  tft.drawBitmap(0, 40, thunder, 128, 90, YELLOW);
  tft.drawBitmap(0, 35, cloud, 128, 90, GREY);
  tft.fillRoundRect(48, 102, 3, 15, 1, BLUE);
  tft.fillRoundRect(55, 102, 3, 15, 1, BLUE);
  tft.fillRoundRect(74, 102, 3, 15, 1, BLUE);
  tft.fillRoundRect(82, 102, 3, 15, 1, BLUE);
}

void drawLightSnowfall()
{
  tft.drawBitmap(0, 30, cloud, 128, 90, GREY);
  tft.fillCircle(50, 100, 3, GREY);
  tft.fillCircle(65, 103, 3, GREY);
  tft.fillCircle(82, 100, 3, GREY);
}

void drawModerateSnowfall()
{
  tft.drawBitmap(0, 35, cloud, 128, 90, GREY);
  tft.fillCircle(50, 105, 3, GREY);
  tft.fillCircle(50, 115, 3, GREY);
  tft.fillCircle(65, 108, 3, GREY);
  tft.fillCircle(65, 118, 3, GREY);
  tft.fillCircle(82, 105, 3, GREY);
  tft.fillCircle(82, 115, 3, GREY);
}

void drawHeavySnowfall()
{
  tft.drawBitmap(0, 35, cloud, 128, 90, GREY);
  tft.fillCircle(40, 105, 3, GREY);
  tft.fillCircle(52, 105, 3, GREY);
  tft.fillCircle(52, 115, 3, GREY);
  tft.fillCircle(65, 108, 3, GREY);
  tft.fillCircle(65, 118, 3, GREY);
  tft.fillCircle(80, 105, 3, GREY);
  tft.fillCircle(80, 115, 3, GREY);
  tft.fillCircle(92, 105, 3, GREY);
}

void drawCloudSunAndRain()
{
  tft.fillCircle(73, 70, 20, YELLOW);
  tft.drawBitmap(0, 32, cloud, 128, 90, BLACK);
  tft.drawBitmap(0, 35, cloud, 128, 90, GREY);
  tft.fillRoundRect(50, 105, 3, 13, 1, BLUE);
  tft.fillRoundRect(65, 105, 3, 13, 1, BLUE);
  tft.fillRoundRect(80, 105, 3, 13, 1, BLUE);
}

void drawCloudAndTheMoon()
{
  tft.fillCircle(94, 60, 18, GREY);
  tft.fillCircle(105, 53, 18, BLACK);
  tft.drawBitmap(0, 32, cloud, 128, 90, BLACK);
  tft.drawBitmap(0, 35, cloud, 128, 90, GREY);
}

void drawCloudTheMoonAndRain()
{
  tft.fillCircle(94, 60, 18, GREY);
  tft.fillCircle(105, 53, 18, BLACK);
  tft.drawBitmap(0, 32, cloud, 128, 90, BLACK);
  tft.drawBitmap(0, 35, cloud, 128, 90, GREY);
  tft.fillRoundRect(50, 105, 3, 11, 1, BLUE);
  tft.fillRoundRect(65, 105, 3, 11, 1, BLUE);
  tft.fillRoundRect(80, 105, 3, 11, 1, BLUE);
}

void drawWind()
{
  tft.drawBitmap(0, 35, wind, 128, 90, GREY);
}

void drawFog()
{
  tft.fillRoundRect(45, 60, 40, 4, 1, GREY);
  tft.fillRoundRect(40, 70, 50, 4, 1, GREY);
  tft.fillRoundRect(35, 80, 60, 4, 1, GREY);
  tft.fillRoundRect(40, 90, 50, 4, 1, GREY);
  tft.fillRoundRect(45, 100, 40, 4, 1, GREY);
}

void clearIcon()
{
  tft.fillRect(0, 40, 128, 100, BLACK);
}

/* Helper functions for determing local time & date (from UTC)

  The following three functions ripped off from Electrical Engineering Stack Exchange
  http://electronics.stackexchange.com/questions/66285/how-to-calculate-day-of-the-week-for-rtc

   Returns the number of days to the start of the specified year, taking leap
   years into account, but not the shift from the Julian calendar to the
   Gregorian calendar. Instead, it is as though the Gregorian calendar is
   extrapolated back in time to a hypothetical "year zero".
*/
uint16_t leap(uint16_t year)
{
  return year * 365 + (year / 4) - (year / 100) + (year / 400);
}
/* Returns a number representing the number of days since March 1 in the
   hypothetical year 0, not counting the change from the Julian calendar
   to the Gregorian calendar that occurred in the 16th century. This
   algorithm is loosely based on a function known as "Zeller's Congruence".
   This number MOD 7 gives the day of week, where 0 = Monday and 6 = Sunday.
*/
uint16_t zeller(uint16_t year, uint8_t month, uint8_t day)
{
  year += ((month + 9) / 12) - 1;
  month = (month + 9) % 12;
  return leap(year) + month * 30 + ((6 * month + 5) / 10) + day + 1;
}

// Returns the day of week for a given date.
uint8_t dayOfWeek(uint16_t year, uint8_t month, uint8_t day)
{
  return (zeller(year, month, day) % 7) + 1;
}

/* Ripped off from Stackoverflow
  http://stackoverflow.com/questions/5590429/calculating-daylight-saving-time-from-only-date

  Check to see if it's Daylight Savings Time (DST)
*/
bool IsDST(uint8_t day, uint8_t month , uint8_t DOW)
{
  // Make Day of Week (DOW) match with what Stackoverflow suggests
  // for DOW (Sunday = 0 to Saturday = 6)
  switch (DOW)
  {
    case 6:  DOW = 0; break; // Sun
    case 7:  DOW = 1; break; // Mon
    case 1:  DOW = 2; break; // Tue
    case 2:  DOW = 3; break; // Wed
    case 3:  DOW = 4; break; // Thu
    case 4:  DOW = 5; break; // Fri
    case 5:  DOW = 6; break; // Sat
    default: break;
  }
  // January, February, and December are out
  if (month < 3 || month > 11) {
    return false;
  }
  // April to October are in
  if (month > 3 && month < 11) {
    return true;
  }
  int8_t previousSunday = (int8_t)(day - DOW);
  // In march, we are DST if our previous Sunday was on or after the 8th
  if (month == 3) {
    return previousSunday >= 8;
  }
  // In November we must be before the first Sunday to be DST
  // That means the previous Sunday must be before the 1st
  return previousSunday <= 0;
}

/* Convert UTC time and date to local time and date
   Difference between UTC time/date (at Greenwich) and local time/date is 15 minutes
   per 1 degree of longitude. See the following:
   http://www.edaboard.com/thread101516.html
*/
bool convertToLocal(int* hour, int* year, int* month,
                    int* day, double lon, bool convertDate) {

  uint8_t DaysAMonth[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

  // Get Day of Week (DOW)
  uint8_t DOW = dayOfWeek(*year, *month, *day);
  // Get Daylight Saving Time (DST) or Standard Time (ST)
  bool DST = IsDST(*day, *month, DOW);
  // Compute local time (hours)
  int8_t UTCOffset = (int8_t)round((lon / 15.0d)); // UTC offset
  if (UTCOffset < 0) {
    // West of Greenwich, subtract
    UTCOffset = abs(UTCOffset); // Make offset positive
    if (DST && convertDate) --UTCOffset; // Compensate for DST
    if (*hour <= UTCOffset) *hour += 24;
    *hour -= UTCOffset; // Subtract offset
  }
  else {
    // East of Greenwich, add
    if (DST && convertDate) --UTCOffset; // Compensate for DST
    if (*hour <= UTCOffset) *hour += 24;
    *hour += UTCOffset; // Add offset
  }
  // Convert date if convertDate flag is set
  // Portions of the following code (with some modifications) ripped off from Instructables
  // http://www.instructables.com/id/GPS-time-UTC-to-local-time-conversion-using-Arduin/?ALLSTEPS
  if (convertDate) {
    if ((24 - *hour) <= UTCOffset) { // A new UTC day started
      if (*year % 4 == 0) DaysAMonth[1] = 29; //leap year check (the simple method)
      if (*hour < 24) {
        *day -= 1;
        if (*day < 1) {
          if (*month == 1) {
            *month = 12;
            *year -= 1;
          } // if (*month == 1)
          else {
            *month -= 1;
          }
          *day = DaysAMonth[*month - 1];
        } // if (*day < 1)
      } // if (*hour < 24)
      else if (*hour >= 24) {
        *day += 1;
        if (*day > DaysAMonth[*month - 1]) {
          *day = 1;
          *month += 1;
          if (*month > 12) *year += 1;
        } // if (*day > DaysAMonth[*month - 1])
      } // if (*hour >= 24)
    } // if ((24 - *hour) <= UTCOffset)
  } // if (convertDate)
  return (DST);
}

void convertDate(double lon, int* year, int* month, int* day) {

  uint8_t DaysAMonth[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

  if (*year % 4 == 0) DaysAMonth[1] = 29; //leap year check (the simple method)
  if (round((lon / 15.0d)) < 0.0d) { // East of Greenwich, subtract
    *day -= 1;
    if (*day < 1) {
      if (*month == 1) {
        *month = 12;
        *year -= 1;
      } // if (*month == 1)
      else {
        *month -= 1;
      }
      *day = DaysAMonth[*month - 1];
    } // if (*day < 1)
  } // if (round((lon / 15.0d)) < 0.0d)
  else { // West of Greenwich, add
    *day += 1;
    if (*day > DaysAMonth[*month - 1]) {
      *day = 1;
      *month += 1;
      if (*month > 12) *year += 1;
    } // if (*day > DaysAMonth[*month - 1])
  } // if (round((lon / 15.0d)) < 0.0d)
}

// Get the day of the week
String getDayOfWeek(uint8_t day)
{
  String DOW;
  if (day == 7) day = 0;
  switch (day)
  {
    case 0:  DOW = "Monday"; break;
    case 1:  DOW = "Tuesday"; break;
    case 2:  DOW = "Wensday"; break;
    case 3:  DOW = "Thursday"; break;
    case 4:  DOW = "Friday"; break;
    case 5:  DOW = "Saturday" ; break;
    case 6:  DOW = "Sunday"; break;
    default: DOW = "Error" ;
  }
  return (DOW);
}

/*
 Computes Sun rise/set times, start/end of twilight, and
 the length of the day at any date and latitude<commit_msg>v7.0 Restart nodeMCU if we lose WiFi connection<commit_after>////////////////////////////////////////////////////////////////////////
//    Weather Forecast Display                                       //
//           <NAME>                                              //
//            12/29/16                                               //
//     Based on the following:                                       //
// http://educ8s.tv/art-deco-weather-forecast-display/               //
//                                                                   //
//     See the following for a description on JSON decoding:         //                                                    //
// https://www.arduino.cc/en/Tutorial.WiFi101WeatherAudioNotifier    //
//                                                                   //
///////////////////////////////////////////////////////////////////////

#include "WeatherDisplay.h"
#include <ESP8266WiFi.h>
#include <ArduinoJson.h>
#include <Adafruit_ST7735.h>
#include <Adafruit_GFX.h>

WiFiClient client;
String serverName = "api.openweathermap.org"; // Remote server we will connect to
String result;
bool night = false;
bool toggleDisplay = HIGH;
bool pastToggleDisplay = !toggleDisplay;

Adafruit_ST7735 tft(TFT_CS, TFT_DC, TFT_RST);

extern unsigned char cloud[];
extern unsigned char thunder[];
extern unsigned char wind[];
/* Fill in these four variables within NetworkConnection.c
   with your ssid, password, APIKey and cityID */
extern const char* ssid; // SSID of local network
extern const char* password; // Password on network
extern const char* APIKey; // openweathermap.org API key
extern const char* cityID; // openweathermap.org city

void setup() {
#ifdef DEBUG
  Serial.begin(9600);
#endif
  pinMode(TOGGLEDISPLAY_SW, INPUT);
  pinMode(ALIVE_LED, OUTPUT);

  tft.initR(INITR_BLACKTAB);   // initialize a ST7735S chip, black tab
  tft.fillScreen(BLACK);

#ifdef DEBUG
  Serial.println("Connecting");
#endif
  tft.setTextColor(YELLOW);
  tft.setTextSize(1);
  tft.setCursor(30, 5);
  tft.print("Version ");
  tft.print(VERSION);
  tft.setCursor(30, 15);
  tft.print(CREDIT);
  tft.setCursor(30, 80);
  tft.setTextColor(WHITE);
  tft.print("Connecting...");

  int startCounter = 0;

  WiFi.mode(WIFI_STA);
  WiFi.begin (ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
#ifdef DEBUG
    Serial.print(".");
#endif
    startCounter++;
    tft.setTextSize(1);
    tft.setTextColor(YELLOW);
    if (startCounter <= 1) tft.setCursor(20, 100);
    tft.print(startCounter);
    if (startCounter >= 10) {
#ifdef DEBUG
      Serial.println("\nTimed out. Starting again...");
#endif
      return;
    }
    delay(500);
  }
#ifdef DEBUG
  Serial.println("WiFi connected");
#endif
} // setup

void loop() {
  static int counter = GETDATACOUNT;
  bool flasher;

  toggleDisplay = digitalRead(TOGGLEDISPLAY_SW);
  if (toggleDisplay != pastToggleDisplay) {
    pastToggleDisplay = toggleDisplay;
    counter = GETDATACOUNT;
  }

  if (counter >= GETDATACOUNT) //Get new data
  {
    // If we lost WiFi connection (it sometimes happens), restart the nodeMCU
    if (WiFi.status() != WL_CONNECTED) {
      ESP.restart();
    }
    else {
      counter = 0;
      getWeatherData();
    }
  }
  else {
    counter++;
    flasher = (counter % 2 == 0); // 0 or 1
    // Flash the alive LED so we know the system has not crashed
    analogWrite(ALIVE_LED, flasher * ALIVEBRIGHTNESS);
    // Seed random number generator
    randomSeed(counter + flasher);
    delay(1000);
#ifdef DEBUG
    Serial.print("counter = ");
    Serial.println(counter);
#endif
  }
}

void getWeatherData() //client function to send/receive GET request data.
{
#ifdef DEBUG
  Serial.println("Getting Weather Data");
#endif

  String APIKEY(APIKey);
  String CityID(cityID);
  if (client.connect(serverName.c_str(), PORT)) { // Starts client connection, checks for connection
    client.println("GET /data/2.5/forecast?id=" + CityID + "&units=imperial&cnt=1&APPID=" + APIKEY);
    client.println("Host: " + serverName);
    client.println("User-Agent: ArduinoWiFi/1.1");
    client.println("Connection: close");
    client.println();
  }
  else {
#ifdef DEBUG
    Serial.println("connection failed"); // Error message if no client connect
    Serial.println();
#endif
    return;
  }

  while (client.connected() && !client.available()) delay(1); //waits for data

#ifdef DEBUG
  Serial.println("Waiting for data");
#endif

  while (client.connected() || client.available()) { //connected or data available
    char c = client.read(); // Gets byte from WIFI buffer
    result = result + c;
  }

  client.stop(); //stop client
  result.replace('[', ' ');
  result.replace(']', ' ');
#ifdef DEBUG
  Serial.println(result);
#endif

  char jsonArray [result.length() + 1];
  result.toCharArray(jsonArray, sizeof(jsonArray));
  jsonArray[result.length() + 1] = '\0';

  StaticJsonBuffer<1024> json_buf;
  JsonObject &root = json_buf.parseObject(jsonArray);
  if (!root.success())
  {
#ifdef DEBUG
    Serial.println("parseObject() failed");
#endif
    return;
  }

  String location = root["city"]["name"];
  String temperature = root["list"]["main"]["temp"];
  String humidity = root["list"]["main"]["humidity"];
  String weather = root["list"]["weather"]["main"];
  String description = root["list"]["weather"]["description"];
  String idString = root["list"]["weather"]["id"];
  String timeS = root["list"]["dt_txt"];
  String latitude = root["city"]["coord"]["lat"];
  String longitude = root["city"]["coord"]["lon"];
  String windSpeed = root["list"]["wind"]["speed"];
  String windDirection = root["list"]["wind"]["deg"];

  bool DST = false;
  int riseI, setI, monthI, dayI, yearI;
  String timeUTC = timeS;
  timeUTC = timeUTC.substring(timeUTC.length() - 8, timeUTC.length()); // Strip out the time (XX:XX:XX)
  timeS = convertGMTTimeToLocal(timeS, latitude, longitude,
                                &riseI, &setI, &monthI, &dayI, &yearI, &DST);
  String dateS = String(monthI) + '/' + String(dayI) + '/' + String(yearI);

  int length = temperature.length();
  if (length == 5)
  {
    temperature.remove(length - 1);
  }
#ifdef DEBUG
  Serial.println();
  Serial.println(dateS);
  Serial.println("Today is " + getDayOfWeek(dayOfWeek(yearI, monthI, dayI)));
  Serial.println((DST) ? "It's DST" : "It's ST");
  Serial.println((night) ? "It is Night time" : "It is Day time");
  Serial.print("City ");
  Serial.println(location);
  Serial.print("Weather Icon ID ");
  Serial.println(idString);
  Serial.print("Weather ");
  Serial.println(weather);
  Serial.print("Description ");
  Serial.println(description);
  Serial.print("Temperature ");
  Serial.println(temperature + "f");
  Serial.print("Humidity ");
  Serial.println(humidity + "% RH");
  Serial.print("Wind speed  ");
  Serial.println(windSpeed + " MPH");
  Serial.print("Wind direction  ");
  Serial.println(windDirection + " Deg");
  Serial.print("Latitude ");
  Serial.println(latitude + " Deg");
  Serial.print("Longitude ");
  Serial.println(longitude + " Deg");
  Serial.print("Sunrise ");
  Serial.print(riseI);
  Serial.println("am");
  Serial.print("Sunset ");
  Serial.print(setI);
  Serial.println("pm");
  Serial.print("Time last updated ");
  Serial.println(timeS);
  Serial.print("UTC ");
  Serial.println(timeUTC);
#endif // #ifdef DEBUG

  clearScreen();

  if (toggleDisplay) {
    // Display the aux weather
    printAuxData(location,
                 temperature,
                 humidity,
                 description,
                 windSpeed,
                 windDirection,
                 timeS,
                 riseI,
                 setI,
                 idString.toInt(),
                 dateS,
                 timeUTC,
                 dayI,
                 monthI,
                 yearI,
                 DST);
  }
  else {
    // Display the main weather
    printMainData(humidity,
                  temperature,
                  timeS,
                  idString.toInt(),
                  dateS);
  }
} // void getWeatherData()

// Displays the aux weather
void printAuxData(String location,
                  String temperature,
                  String humidity,
                  String description,
                  String windSpeed,
                  String windDirection,
                  String time,
                  int rise,
                  int set,
                  int weatherID,
                  String dateS,
                  String timeUTC,
                  int dayI,
                  int monthI,
                  int yearI,
                  bool DST)
{
  static long lastRandomNumber;
  long randomColor;
  long anotherRandomColor;
  unsigned int textColor[] = {
    BLUE,
    RED,
    GREEN,
    CYAN,
    MAGENTA,
    YELLOW,
    WHITE
  };

  // Get random number
  byte maxRandomNumber = sizeof(textColor) / sizeof(unsigned int);
  randomColor = random(maxRandomNumber);
  // If random number repeats, try again
  while (randomColor == lastRandomNumber) {
    randomColor = random(maxRandomNumber);
  }
  lastRandomNumber = randomColor;
  anotherRandomColor = random(maxRandomNumber);
  // If random number repeats first random number, try again
  while (anotherRandomColor == randomColor) {
    anotherRandomColor = random(maxRandomNumber);
  }
  tft.setTextColor(textColor[randomColor]);
  tft.setTextSize(1);
  tft.setCursor(0, 0);
  tft.print("Location: ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.println(location);
  tft.setTextColor(textColor[randomColor]);
  tft.print("Temperature: ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.print(temperature.toInt());
  tft.println(" F");
  tft.setTextColor(textColor[randomColor]);
  tft.print("Humidity: ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.print(humidity.toInt());
  tft.println("% RH");
  tft.setTextColor(textColor[randomColor]);
  tft.print("Weather ID: ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.println(weatherID);
  tft.setTextColor(textColor[randomColor]);
  tft.print("Sky: ");
  tft.setTextColor(textColor[anotherRandomColor]);
  if ((sizeof("Sky: ") + description.length()) >= 21 /*Length of line*/) description.replace(' ', '\n');
  tft.println(description);
  tft.setTextColor(textColor[randomColor]);
  tft.print("Wind Spd: ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.print(windSpeed.toInt());
  tft.println(" Knots");
  tft.setTextColor(textColor[randomColor]);
  tft.print("Wind Dir: ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.print(windDirection.toInt());
  tft.println(" Degs");
  tft.setTextColor(textColor[randomColor]);
  tft.print("Time LU: ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.println(time);
  tft.setTextColor(textColor[randomColor]);
  tft.print("UTC: ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.println(timeUTC);
  tft.setTextColor(textColor[randomColor]);
  tft.print("Date: ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.println(dateS);
  tft.setTextColor(textColor[randomColor]);
  tft.print("Sunrise: ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.print(rise);
  tft.println(":00am");
  tft.setTextColor(textColor[randomColor]);
  tft.print("Sunset: ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.print(set);
  tft.println(":00pm");
  tft.setTextColor(textColor[randomColor]);
  tft.print("It's ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.println((DST) ? "DST" : "ST");
  tft.setTextColor(textColor[randomColor]);
  tft.print("Today is ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.println(getDayOfWeek(dayOfWeek(yearI, monthI, dayI)));
  tft.setTextColor(textColor[randomColor]);
  tft.print("It is ");
  tft.setTextColor(textColor[anotherRandomColor]);
  tft.print((night) ? "Night " : "Day ");
  tft.setTextColor(textColor[randomColor]);
  tft.println("time");
}

// Displays the main weather
void printMainData(String humidityString,
                   String temperature,
                   String time,
                   int weatherID,
                   String dateS)
{
  tft.setTextColor(YELLOW);
  tft.setTextSize(1);
  tft.setCursor(17, 5);
  tft.print("Updated " + time);
  tft.setTextColor(WHITE);
  tft.setTextSize(2);
  tft.setCursor(27, 20);
  tft.print(temperature);
  tft.setTextSize(1);
  tft.print("o");
  tft.setTextSize(2);
  tft.print(" F");

  printWeatherIcon(weatherID);

  tft.setTextColor(WHITE);
  tft.setTextSize(2);
  tft.setCursor(27, 132);
  tft.print(humidityString);
  tft.print("% RH");
  tft.setTextColor(YELLOW);
  tft.setTextSize(1);
  tft.setCursor(40, 150);
  tft.print(dateS);
}

void printWeatherIcon(int id)
{
  switch (id)
  {
    case 800: drawClearWeather(); break;
    case 801: drawFewClouds(); break;
    case 802: drawFewClouds(); break;
    case 803: drawFewClouds(); break;
    case 804: drawCloud(); break;

    case 200: drawThunderstorm(); break;
    case 201: drawThunderstorm(); break;
    case 202: drawThunderstorm(); break;
    case 210: drawThunderstorm(); break;
    case 211: drawThunderstorm(); break;
    case 212: drawThunderstorm(); break;
    case 221: drawThunderstorm(); break;
    case 230: drawThunderstorm(); break;
    case 231: drawThunderstorm(); break;
    case 232: drawThunderstorm(); break;

    case 300: drawLightRain(); break;
    case 301: drawLightRain(); break;
    case 302: drawLightRain(); break;
    case 310: drawLightRain(); break;
    case 311: drawLightRain(); break;
    case 312: drawLightRain(); break;
    case 313: drawLightRain(); break;
    case 314: drawLightRain(); break;
    case 321: drawLightRain(); break;

    case 500: drawLightRainWithSunOrMoon(); break;
    case 501: drawModerateRain(); break;
    case 502: drawLightRainWithSunOrMoon(); break;
    case 503: drawLightRainWithSunOrMoon(); break;
    case 504: drawLightRainWithSunOrMoon(); break;
    case 511: drawLightRain(); break;
    case 520: drawModerateRain(); break;
    case 521: drawModerateRain(); break;
    case 522: drawHeavyRain(); break;
    case 531: drawHeavyRain(); break;

    case 600: drawLightSnowfall(); break;
    case 601: drawModerateSnowfall(); break;
    case 602: drawHeavySnowfall(); break;
    case 611: drawLightSnowfall(); break;
    case 612: drawLightSnowfall(); break;
    case 615: drawLightSnowfall(); break;
    case 616: drawLightSnowfall(); break;
    case 620: drawLightSnowfall(); break;
    case 621: drawModerateSnowfall(); break;
    case 622: drawHeavySnowfall(); break;

    case 701: drawFog(); break;
    case 711: drawFog(); break;
    case 721: drawFog(); break;
    case 731: drawFog(); break;
    case 741: drawFog(); break;
    case 751: drawFog(); break;
    case 761: drawFog(); break;
    case 762: drawFog(); break;
    case 771: drawFog(); break;
    case 781: drawFog(); break;

    default: break;
  }
}

String convertGMTTimeToLocal(String timeS, String latitude, String longitude,
                             int* riseI, int* setI, int* monthI, int* dayI, int* yearI, bool* DST)
{
  double rise, set;

  int length = timeS.length();
  String yearS = timeS.substring(0, 4);
  int year = yearS.toInt();
  String monthS = timeS.substring(5, 7);
  int month = monthS.toInt();
  String dayS = timeS.substring(8, 10);
  int day = dayS.toInt();

  timeS = timeS.substring(length - 8, length - 6); // Strip out the hours
  int time = timeS.toInt();

#ifdef ERROROFFSET
  // For some reason, openweathermap.org time is off by some (???)
  // hours from UTC. Need to compensate
  int TimeErrorOffset = -3;
  if (((time > 18) && (time < 21)) ||
      (time == 12)) {
    time += (TimeErrorOffset + 2);
  }
  else if (time == 0) {
    time += TimeErrorOffset;
    convertDate((double)longitude.toFloat(), &year, &month, &day);
  }
  else {
    time += TimeErrorOffset;
  }
#endif

  // Convert UTC time & date to local time & date
  *DST = convertToLocal(&time, &year, &month,
                        &day, (double)longitude.toFloat(), true); // true means date and DST conversion

  *monthI = month;
  *dayI = day;
  *yearI = year;

  // Get sunrise and sunset time in UTC
  int rs = sun_rise_set(year, month, day,
                        (double)longitude.toFloat(),
                        (double)latitude.toFloat(),
                        &rise, &set);
  *riseI = (int)round(rise);
  *setI = (int)round(set);

  // Convert UTC sunrise time to local sunrise time
  (void) convertToLocal(riseI, &year, &month,
                        &day, (double)longitude.toFloat(), false); // false means no date or DST conversion

  // Convert UTC sunset time to local sunset time
  (void) convertToLocal(setI, &year, &month,
                        &day, (double)longitude.toFloat(), false); // false means no date or DST conversion

  // Determine night or day
  night = (time > *setI ||  time < *riseI);

  if (*riseI >= 12) { // Convert to 12 hour format
    if (*riseI > 12) *riseI -= 12;
  }
  if (*setI >= 12) { // Convert to 12 hour format
    if (*setI > 12) *setI -= 12;
  }

  char AMPM[] = "am";
  if (time >= 12) { // Convert to 12 hour format
    if (time > 12) time -= 12;
    strcpy(AMPM, "pm");
  }

  timeS = String(time) + ":00" + AMPM;

  return timeS;
}

void clearScreen()
{
  tft.fillScreen(BLACK);
}

void drawClearWeather()
{
  if (night)
  {
    drawTheMoon();
  } else
  {
    drawTheSun();
  }
}

void drawFewClouds()
{
  if (night)
  {
    drawCloudAndTheMoon();
  } else
  {
    drawCloudWithSun();
  }
}

void drawTheSun()
{
  tft.fillCircle(64, 80, 26, YELLOW);
}

void drawTheFullMoon()
{
  tft.fillCircle(64, 80, 26, GREY);
}

void drawTheMoon()
{
  tft.fillCircle(64, 80, 26, GREY);
  tft.fillCircle(75, 73, 26, BLACK);
}

void drawCloud()
{
  tft.drawBitmap(0, 35, cloud, 128, 90, GREY);
}

void drawCloudWithSun()
{
  tft.fillCircle(73, 70, 20, YELLOW);
  tft.drawBitmap(0, 36, cloud, 128, 90, BLACK);
  tft.drawBitmap(0, 40, cloud, 128, 90, GREY);
}

void drawLightRainWithSunOrMoon()
{
  if (night)
  {
    drawCloudTheMoonAndRain();
  } else
  {
    drawCloudSunAndRain();
  }
}

void drawLightRain()
{
  tft.drawBitmap(0, 35, cloud, 128, 90, GREY);
  tft.fillRoundRect(50, 105, 3, 13, 1, BLUE);
  tft.fillRoundRect(65, 105, 3, 13, 1, BLUE);
  tft.fillRoundRect(80, 105, 3, 13, 1, BLUE);
}

void drawModerateRain()
{
  tft.drawBitmap(0, 35, cloud, 128, 90, GREY);
  tft.fillRoundRect(50, 105, 3, 15, 1, BLUE);
  tft.fillRoundRect(57, 102, 3, 15, 1, BLUE);
  tft.fillRoundRect(65, 105, 3, 15, 1, BLUE);
  tft.fillRoundRect(72, 102, 3, 15, 1, BLUE);
  tft.fillRoundRect(80, 105, 3, 15, 1, BLUE);
}

void drawHeavyRain()
{
  tft.drawBitmap(0, 35, cloud, 128, 90, GREY);
  tft.fillRoundRect(43, 102, 3, 15, 1, BLUE);
  tft.fillRoundRect(50, 105, 3, 15, 1, BLUE);
  tft.fillRoundRect(57, 102, 3, 15, 1, BLUE);
  tft.fillRoundRect(65, 105, 3, 15, 1, BLUE);
  tft.fillRoundRect(72, 102, 3, 15, 1, BLUE);
  tft.fillRoundRect(80, 105, 3, 15, 1, BLUE);
  tft.fillRoundRect(87, 102, 3, 15, 1, BLUE);
}

void drawThunderstorm()
{
  tft.drawBitmap(0, 40, thunder, 128, 90, YELLOW);
  tft.drawBitmap(0, 35, cloud, 128, 90, GREY);
  tft.fillRoundRect(48, 102, 3, 15, 1, BLUE);
  tft.fillRoundRect(55, 102, 3, 15, 1, BLUE);
  tft.fillRoundRect(74, 102, 3, 15, 1, BLUE);
  tft.fillRoundRect(82, 102, 3, 15, 1, BLUE);
}

void drawLightSnowfall()
{
  tft.drawBitmap(0, 30, cloud, 128, 90, GREY);
  tft.fillCircle(50, 100, 3, GREY);
  tft.fillCircle(65, 103, 3, GREY);
  tft.fillCircle(82, 100, 3, GREY);
}

void drawModerateSnowfall()
{
  tft.drawBitmap(0, 35, cloud, 128, 90, GREY);
  tft.fillCircle(50, 105, 3, GREY);
  tft.fillCircle(50, 115, 3, GREY);
  tft.fillCircle(65, 108, 3, GREY);
  tft.fillCircle(65, 118, 3, GREY);
  tft.fillCircle(82, 105, 3, GREY);
  tft.fillCircle(82, 115, 3, GREY);
}

void drawHeavySnowfall()
{
  tft.drawBitmap(0, 35, cloud, 128, 90, GREY);
  tft.fillCircle(40, 105, 3, GREY);
  tft.fillCircle(52, 105, 3, GREY);
  tft.fillCircle(52, 115, 3, GREY);
  tft.fillCircle(65, 108, 3, GREY);
  tft.fillCircle(65, 118, 3, GREY);
  tft.fillCircle(80, 105, 3, GREY);
  tft.fillCircle(80, 115, 3, GREY);
  tft.fillCircle(92, 105, 3, GREY);
}

void drawCloudSunAndRain()
{
  tft.fillCircle(73, 70, 20, YELLOW);
  tft.drawBitmap(0, 32, cloud, 128, 90, BLACK);
  tft.drawBitmap(0, 35, cloud, 128, 90, GREY);
  tft.fillRoundRect(50, 105, 3, 13, 1, BLUE);
  tft.fillRoundRect(65, 105, 3, 13, 1, BLUE);
  tft.fillRoundRect(80, 105, 3, 13, 1, BLUE);
}

void drawCloudAndTheMoon()
{
  tft.fillCircle(94, 60, 18, GREY);
  tft.fillCircle(105, 53, 18, BLACK);
  tft.drawBitmap(0, 32, cloud, 128, 90, BLACK);
  tft.drawBitmap(0, 35, cloud, 128, 90, GREY);
}

void drawCloudTheMoonAndRain()
{
  tft.fillCircle(94, 60, 18, GREY);
  tft.fillCircle(105, 53, 18, BLACK);
  tft.drawBitmap(0, 32, cloud, 128, 90, BLACK);
  tft.drawBitmap(0, 35, cloud, 128, 90, GREY);
  tft.fillRoundRect(50, 105, 3, 11, 1, BLUE);
  tft.fillRoundRect(65, 105, 3, 11, 1, BLUE);
  tft.fillRoundRect(80, 105, 3, 11, 1, BLUE);
}

void drawWind()
{
  tft.drawBitmap(0, 35, wind, 128, 90, GREY);
}

void drawFog()
{
  tft.fillRoundRect(45, 60, 40, 4, 1, GREY);
  tft.fillRoundRect(40, 70, 50, 4, 1, GREY);
  tft.fillRoundRect(35, 80, 60, 4, 1, GREY);
  tft.fillRoundRect(40, 90, 50, 4, 1, GREY);
  tft.fillRoundRect(45, 100, 40, 4, 1, GREY);
}

void clearIcon()
{
  tft.fillRect(0, 40, 128, 100, BLACK);
}

/* Helper functions for determing local time & date (from UTC)

  The following three functions ripped off from Electrical Engineering Stack Exchange
  http://electronics.stackexchange.com/questions/66285/how-to-calculate-day-of-the-week-for-rtc

   Returns the number of days to the start of the specified year, taking leap
   years into account, but not the shift from the Julian calendar to the
   Gregorian calendar. Instead, it is as though the Gregorian calendar is
   extrapolated back in time to a hypothetical "year zero".
*/
uint16_t leap(uint16_t year)
{
  return year * 365 + (year / 4) - (year / 100) + (year / 400);
}
/* Returns a number representing the number of days since March 1 in the
   hypothetical year 0, not counting the change from the Julian calendar
   to the Gregorian calendar that occurred in the 16th century. This
   algorithm is loosely based on a function known as "Zeller's Congruence".
   This number MOD 7 gives the day of week, where 0 = Monday and 6 = Sunday.
*/
uint16_t zeller(uint16_t year, uint8_t month, uint8_t day)
{
  year += ((month + 9) / 12) - 1;
  month = (month + 9) % 12;
  return leap(year) + month * 30 + ((6 * month + 5) / 10) + day + 1;
}

// Returns the day of week for a given date.
uint8_t dayOfWeek(uint16_t year, uint8_t month, uint8_t day)
{
  return (zeller(year, month, day) % 7) + 1;
}

/* Ripped off from Stackoverflow
  http://stackoverflow.com/questions/5590429/calculating-daylight-saving-time-from-only-date

  Check to see if it's Daylight Savings Time (DST)
*/
bool IsDST(uint8_t day, uint8_t month , uint8_t DOW)
{
  // Make Day of Week (DOW) match with what Stackoverflow suggests
  // for DOW (Sunday = 0 to Saturday = 6)
  switch (DOW)
  {
    case 6:  DOW = 0; break; // Sun
    case 7:  DOW = 1; break; // Mon
    case 1:  DOW = 2; break; // Tue
    case 2:  DOW = 3; break; // Wed
    case 3:  DOW = 4; break; // Thu
    case 4:  DOW = 5; break; // Fri
    case 5:  DOW = 6; break; // Sat
    default: break;
  }
  // January, February, and December are out
  if (month < 3 || month > 11) {
    return false;
  }
  // April to October are in
  if (month > 3 && month < 11) {
    return true;
  }
  int8_t previousSunday = (int8_t)(day - DOW);
  // In march, we are DST if our previous Sunday was on or after the 8th
  if (month == 3) {
    return previousSunday >= 8;
  }
  // In November we must be before the first Sunday to be DST
  // That means the previous Sunday must be before the 1st
  return previousSunday <= 0;
}

/* Convert UTC time and date to local time and date
   Difference between UTC time/date (at Greenwich) and local time/date is 15 minutes
   per 1 degree of longitude. See the following:
   http://www.edaboard.com/thread101516.html
*/
bool convertToLocal(int* hour, int* year, int* month,
                    int* day, double lon, bool convertDate) {

  uint8_t DaysAMonth[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

  // Get Day of Week (DOW)
  uint8_t DOW = dayOfWeek(*year, *month, *day);
  // Get Daylight Saving Time (DST) or Standard Time (ST)
  bool DST = IsDST(*day, *month, DOW);
  // Compute local time (hours)
  int8_t UTCOffset = (int8_t)round((lon / 15.0d)); // UTC offset
  if (UTCOffset < 0) {
    // West of Greenwich, subtract
    UTCOffset = abs(UTCOffset); // Make offset positive
    if (DST && convertDate) --UTCOffset; // Compensate for DST
    if (*hour <= UTCOffset) *hour += 24;
    *hour -= UTCOffset; // Subtract offset
  }
  else {
    // East of Greenwich, add
    if (DST && convertDate) --UTCOffset; // Compensate for DST
    if (*hour <= UTCOffset) *hour += 24;
    *hour += UTCOffset; // Add offset
  }
  // Convert date if convertDate flag is set
  // Portions of the following code (with some modifications) ripped off from Instructables
  // http://www.instructables.com/id/GPS-time-UTC-to-local-time-conversion-using-Arduin/?ALLSTEPS
  if (convertDate) {
    if ((24 - *hour) <= UTCOffset) { // A new UTC day started
      if (*year % 4 == 0) DaysAMonth[1] = 29; //leap year check (the simple method)
      if (*hour < 24) {
        *day -= 1;
        if (*day < 1) {
          if (*month == 1) {
            *month = 12;
            *year -= 1;
          } // if (*month == 1)
          else {
            *month -= 1;
          }
          *day = DaysAMonth[*month - 1];
        } // if (*day < 1)
      } // if (*hour < 24)
      else if (*hour >= 24) {
        *day += 1;
        if (*day > DaysAMonth[*month - 1]) {
          *day = 1;
          *month += 1;
          if (*month > 12) *year += 1;
        } // if (*day > DaysAMonth[*month - 1])
      } // if (*hour >= 24)
    } // if ((24 - *hour) <= UTCOffset)
  } // if (convertDate)
  return (DST);
}

void convertDate(double lon, int* year, int* month, int* day) {

  uint8_t DaysAMonth[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

  if (*year % 4 == 0) DaysAMonth[1] = 29; //leap year check (the simple method)
  if (round((lon / 15.0d)) < 0.0d) { // East of Greenwich, subtract
    *day -= 1;
    if (*day < 1) {
      if (*month == 1) {
        *month = 12;
        *year -= 1;
      } // if (*month == 1)
      else {
        *month -= 1;
      }
      *day = DaysAMonth[*month - 1];
    } // if (*day < 1)
  } // if (round((lon / 15.0d)) < 0.0d)
  else { // West of Greenwich, add
    *day += 1;
    if (*day > DaysAMonth[*month - 1]) {
      *day = 1;
      *month += 1;
      if (*month > 12) *year += 1;
    } // if (*day > DaysAMonth[*month - 1])
  } // if (round((lon / 15.0d)) < 0.0d)
}

// Get the day of the week
String getDayOfWeek(uint8_t day)
{
  String DOW;
  if (day == 7) day = 0;
  switch (day)
  {
    case 0:  DOW = "Monday"; break;
    case 1:  DOW = "Tuesday"; break;
    case 2:  DOW = "Wensday"; break;
    case 3:  DOW = "Thursday"; break;
    case 4:  DOW = "Friday"; break;
    case 5:  DOW = "Saturday" ; break;
    case 6:  DOW = "Sunday"; break;
    default: DOW = "Error" ;
  }
  return (DOW);
}

/*
  Computes Sun rise/set times, start/end of twilight, and
  the length of the day at any date and latitude
{'fraction_non_alphanumeric': 0.15198084935311745, 'fraction_numerical': 0.041919450332578025, 'mean_word_length': 2.8457484188334505, 'pattern_counts': {'":': 6, '<': 55, '<?xml version=': 0, '>': 59, 'https://': 2, 'lorem ipsum': 0, 'www.': 6, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 36, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '65770', 'n_tokens_mistral': 21314, 'n_tokens_neox': 18784, 'n_words': 4992}