[
  {
    "path": ".gitattributes",
    "content": "# Auto detect text files and perform LF normalization\n* text=auto\n"
  },
  {
    "path": "Data_Point_Set.h",
    "content": "// The structures below are the repository for the data values extracted from the\n// JSON message. The structures are populated with the extracted data by the \"value()\"\n// member function in the main OpenWeather.cpp file.\n\n// Some structs contain arrays so watch out for memory consumption. You can\n// request a subset of the full weather report but this library grabs all values with\n// one GET request to avoid exceeding the 1000 free request count per day (count reset\n// at 00:00 UTC). 1000 per day means ~40 per hour. As the weather forecast changes slowly\n// the example requests the forecast every 15 minutes, so adapting to reduce memory\n// by requesting current, daily, hourly etc forecasts individually can be done.\n\n// The content is zero or \"\" when first created.\n\n/***************************************************************************************\n** Description:   Structure for current weather using onecall API\n***************************************************************************************/\ntypedef struct OW_current {\n\n  // current\n  uint32_t dt = 0;\n  uint32_t sunrise = 0;\n  uint32_t sunset = 0;\n  float    temp = 0;\n  float    feels_like = 0;\n  float    pressure = 0;\n  uint8_t  humidity = 0;\n  float    dew_point = 0;\n  uint8_t  clouds = 0;\n  float    uvi = 0;\n  uint32_t visibility = 0;\n  float    wind_speed = 0;\n  float    wind_gust = 0;\n  uint16_t wind_deg = 0;\n  float    rain = 0;\n  float    snow = 0;\n\n  // current.weather\n  uint16_t id = 0;\n  String   main;\n  String   description;\n  String   icon;\n\n} OW_current;\n\n/***************************************************************************************\n** Description:   Structure for hourly weather using onecall API\n***************************************************************************************/\ntypedef struct OW_hourly {\n\n  // hourly\n  uint32_t dt[MAX_HOURS] = { 0 };\n  float    temp[MAX_HOURS] = { 0 };\n  float    feels_like[MAX_HOURS] = { 0 };\n  float    pressure[MAX_HOURS] = { 0 };\n  uint8_t  humidity[MAX_HOURS] = { 0 };\n  float    dew_point[MAX_HOURS] = { 0 };\n  uint8_t  clouds[MAX_HOURS] = { 0 };\n  float    wind_speed[MAX_HOURS] = { 0 };\n  float    wind_gust[MAX_HOURS] = { 0 };\n  uint16_t wind_deg[MAX_HOURS] = { 0 };\n  float    rain[MAX_HOURS] = { 0 };\n  float    snow[MAX_HOURS] = { 0 };\n\n  // hourly.weather\n  uint16_t id[MAX_HOURS] = { 0 };\n  String   main[MAX_HOURS];\n  String   description[MAX_HOURS];\n  String   icon[MAX_HOURS];\n  float    pop[MAX_HOURS];\n  float    rain1h[MAX_HOURS];\n} OW_hourly;\n\n/***************************************************************************************\n** Description:   Structure for daily weather using onecall API\n***************************************************************************************/\ntypedef struct OW_daily {\n\n  // daily\n  uint32_t dt[MAX_DAYS] = { 0 };  // dt\n  uint32_t sunrise[MAX_DAYS] = { 0 };\n  uint32_t sunset[MAX_DAYS] = { 0 };\n  uint32_t moonrise[MAX_DAYS] = { 0 };\n  uint32_t moonset[MAX_DAYS] = { 0 };\n\n  // daily.temp\n  float    temp_morn[MAX_DAYS] = { 0 };\n  float    temp_day[MAX_DAYS] = { 0 };\n  float    temp_eve[MAX_DAYS] = { 0 };\n  float    temp_night[MAX_DAYS] = { 0 };\n  float    temp_min[MAX_DAYS] = { 0 };\n  float    temp_max[MAX_DAYS] = { 0 };\n\n  // daily.feels_like\n  float    feels_like_morn[MAX_DAYS] = { 0 };\n  float    feels_like_day[MAX_DAYS] = { 0 };\n  float    feels_like_eve[MAX_DAYS] = { 0 };\n  float    feels_like_night[MAX_DAYS] = { 0 };\n\n  // daily\n  float    pressure[MAX_DAYS] = { 0 };\n  uint8_t  humidity[MAX_DAYS] = { 0 };\n  float    dew_point[MAX_DAYS] = { 0 };\n  float    wind_speed[MAX_DAYS] = { 0 };\n  float    wind_gust[MAX_DAYS] = { 0 };\n  uint16_t wind_deg[MAX_DAYS] = { 0 };\n  uint8_t  clouds[MAX_DAYS] = { 0 };\n  float    uvi[MAX_DAYS] = { 0 };\n  uint32_t visibility[MAX_DAYS] = { 0 };\n\n  float    rain[MAX_DAYS] = { 0 };\n  float    snow[MAX_DAYS] = { 0 };\n\n  // hourly.weather\n  uint16_t id[MAX_DAYS] = { 0 };\n  String   main[MAX_DAYS];\n  String   description[MAX_DAYS];\n  String   icon[MAX_DAYS];\n  float    pop[MAX_DAYS];\n\n} OW_daily;\n\n\n/***************************************************************************************\n** Description:   Structure for new \"forecast\" API\n***************************************************************************************/\ntypedef struct OW_forecast {\n\n  // list.Nth 3hr slot\n  uint32_t dt[MAX_3HRS] = { 0 };  // dt\n\n  // main\n  float    temp[MAX_3HRS] = { 0 };\n  float    feels_like[MAX_3HRS] = { 0 };\n  float    temp_min[MAX_3HRS] = { 0 };\n  float    temp_max[MAX_3HRS] = { 0 };\n  float    pressure[MAX_3HRS] = { 0 };\n  float    sea_level[MAX_3HRS] = { 0 };\n  float    grnd_level[MAX_3HRS] = { 0 };\n  uint8_t  humidity[MAX_3HRS] = { 0 };\n\n  uint16_t id[MAX_3HRS] = { 0 };\n  String   main[MAX_3HRS];\n  String   description[MAX_3HRS];\n  String   icon[MAX_3HRS];\n\n  uint8_t  clouds_all[MAX_3HRS] = { 0 };\n\n  float    wind_speed[MAX_3HRS] = { 0 };\n  uint16_t wind_deg[MAX_3HRS] = { 0 };\n  float    wind_gust[MAX_3HRS] = { 0 };\n\n  uint32_t visibility[MAX_3HRS] = { 0 };\n  float    pop[MAX_3HRS] = { 0 };\n\n  String   dt_txt[MAX_3HRS];\n\n  // city\n  String   city_name = \"\";\n  int32_t  timezone = 0;\n  uint32_t sunrise = 0;\n  uint32_t sunset = 0;\n\n\n\n} OW_forecast;\n\n\n// Structures for minimal set of data points for TFT_eSPI examples to reduce RAM needs\n/*\ntypedef struct OW_current {\n\n  //float    lat = 0;\n  //float    lon = 0;\n  //String   timezone;\n\n  // current\n  uint32_t dt = 0;\n  uint32_t sunrise = 0;\n  uint32_t sunset = 0;\n  float    temp = 0;\n  //float    feels_like = 0;\n  float    pressure = 0;\n  uint8_t  humidity = 0;\n  //float    dew_point = 0;\n  uint8_t  clouds = 0;\n  //uint8_t  uvi = 0;\n  //uint32_t visibility = 0;\n  float    wind_speed = 0;\n  //float    wind_gust = 0;\n  uint16_t wind_deg = 0;\n  //float    rain = 0;\n  //float    snow = 0;\n\n  // current.weather\n  uint16_t id = 0;\n  String   main;\n  //String   description;\n  //String   icon;\n\n} OW_current;\n\n\ntypedef struct OW_hourly {\n\n} OW_hourly;\n\ntypedef struct OW_daily {\n\n  // daily\n  uint32_t dt[MAX_DAYS] = { 0 };  // dt\n  //uint32_t sunrise = 0;\n  //uint32_t sunset = 0;\n  \n  // daily.temp\n  //float    temp_morn[MAX_DAYS] = { 0 };\n  //float    temp_day[MAX_DAYS] = { 0 };\n  //float    temp_eve[MAX_DAYS] = { 0 };\n  //float    temp_night[MAX_DAYS] = { 0 };\n  float    temp_min[MAX_DAYS] = { 0 };\n  float    temp_max[MAX_DAYS] = { 0 };\n\n  // daily.feels_like\n  //float    feels_like_morn[MAX_DAYS] = { 0 };\n  //float    feels_like_day[MAX_DAYS] = { 0 };\n  //float    feels_like_eve[MAX_DAYS] = { 0 };\n  //float    feels_like_night[MAX_DAYS] = { 0 };\n\n  // daily\n  //float    pressure[MAX_DAYS] = { 0 };\n  //uint8_t  humidity[MAX_DAYS] = { 0 };\n  //float    dew_point[MAX_DAYS] = { 0 };\n  //float    wind_speed[MAX_DAYS] = { 0 };\n  //float    wind_gust[MAX_DAYS] = { 0 };\n  //uint16_t wind_deg[MAX_DAYS] = { 0 };\n  //uint8_t  clouds[MAX_DAYS] = { 0 };\n  //uint8_t  uvi[MAX_DAYS] = { 0 };\n  //uint32_t visibility[MAX_DAYS] = { 0 };\n\n  //float    rain[MAX_DAYS] = { 0 };\n  //float    snow[MAX_DAYS] = { 0 };\n\n  // hourly.weather\n  uint16_t id[MAX_DAYS] = { 0 };\n  //String   main[MAX_DAYS];\n  //String   description[MAX_DAYS];\n  //String   icon[MAX_DAYS];\n\n} OW_daily;\n*/"
  },
  {
    "path": "OpenWeather.cpp",
    "content": "// Client library for the OpenWeather data-point server\n// https://openweathermap.org/\n\n// Created by Bodmer 09/04/2020\n// Updated by Bodmer 08/01/2021\n// Updated by Bodmer 15/02/2023 to support free forecast API\n\n// See license.txt in root folder of library\n// Insecure mode added by ADAMSIN12\n\n#if defined(ARDUINO_ARCH_MBED) || defined(ARDUINO_ARCH_RP2040)\n  #if defined(ARDUINO_RASPBERRY_PI_PICO_W)\n    #include <WiFi.h>\n  #else\n    #include <WiFiNINA.h>\n  #endif\n#else\n  #ifdef ESP8266\n    #include <ESP8266WiFi.h>\n  #else\n    #include <WiFi.h>\n  #endif\n  #include <WiFiClientSecure.h>\n#endif\n\n\n#include \"OpenWeather.h\"\n\n\n/***************************************************************************************\n** Function name:           getForecast (using onecall API)\n** Description:             Setup the weather forecast request\n***************************************************************************************/\n// The structures etc are created by the sketch and passed to this function.\n// Pass a nullptr for current, hourly or daily pointers to exclude in response.\n// ESP8266: Setting secure to false will invoke an insecure connection with AXTLS\n//          for the connection, when set true BearSSL will be used.\n// ESP32:   Secure parameter has no affect.\nbool OW_Weather::getForecast(OW_current *current, OW_hourly *hourly, OW_daily *daily,\n                             String api_key, String latitude, String longitude,\n                             String units, String language, bool secure) {\n\n  data_set = \"\";\n  hourly_index = 0;\n  daily_index = 0;\n  Secure = secure;\n  oneCall = true;\n\n  // Local copies of structure pointers, the structures are filled during parsing\n  this->current  = current;\n  this->hourly   = hourly;\n  this->daily    = daily;\n\n  // Exclude some info by passing fn a NULL pointer to reduce memory needed\n  String exclude = \",alerts\";\n  if (!current)  exclude += \",current\";\n  if (!hourly)   exclude += \",hourly\";\n  if (!daily)    exclude += \",daily\";\n\n  // One call API now subscription\n  String url = \"https://api.openweathermap.org/data/2.5/onecall?lat=\" + latitude + \"&lon=\" + longitude + \"&exclude=minutely\" + exclude + \"&units=\" + units + \"&lang=\" + language + \"&appid=\" + api_key;\n\n  // Send GET request and feed the parser\n  bool result = parseRequest(url);\n\n  // Null out pointers to prevent crashes\n  this->current  = nullptr;\n  this->hourly   = nullptr;\n  this->daily    = nullptr;\n\n  return result;\n}\n\n/***************************************************************************************\n** Function name:           getForecast (using forecast API)\n** Description:             Setup the weather forecast request\n***************************************************************************************/\nbool OW_Weather::getForecast(OW_forecast *forecast, String api_key, \n                             String latitude, String longitude,\n                             String units, String language, bool secure)\n{\n  data_set = \"\";\n  forecast_index = 0;\n  Secure = secure;\n  oneCall = false;\n\n  // Local copies of structure pointers, the structures are filled during parsing\n  this->forecast  = forecast;\n\n  // 5 day forecast every 3 hours from request time\n  String url = \"https://api.openweathermap.org/data/2.5/forecast?lat=\" + latitude + \"&lon=\" + longitude + \"&units=\" + units + \"&lang=\" + language + \"&appid=\" + api_key;\n\n  // Send GET request and feed the parser\n  bool result = parseRequest(url);\n\n  // Null out pointers to prevent crashes\n  this->forecast  = nullptr;\n\n  return result;\n}\n/***************************************************************************************\n** Function name:           partialDataSet\n** Description:             Set requested data set to partial (true) or full (false)\n***************************************************************************************/\nvoid OW_Weather::partialDataSet(bool partialSet) {\n  \n  this->partialSet = partialSet;\n}\n\n#ifdef ESP32 // Decide if ESP32 or ESP8266 parseRequest available\n\n/***************************************************************************************\n** Function name:           parseRequest (for ESP32)\n** Description:             Fetches the JSON message and feeds to the parser\n***************************************************************************************/\nbool OW_Weather::parseRequest(String url) {\n\n  uint32_t dt = millis();\n\n  OW_STATUS_PRINTF(\"\\n\\nThe connection to server is secure (https). Certificate not checked.\\n\");\n  WiFiClientSecure client;\n  client.setInsecure(); // Certificate not checked\n\n  const char*  host = \"api.openweathermap.org\";\n  port = 443;\n\n  if (!client.connect(host, port))\n  {\n    OW_STATUS_PRINTF(\"Connection failed.\\n\");\n    return false;\n  }\n\n  JSON_Decoder parser;\n  parser.setListener(this);\n\n  uint32_t timeout = millis();\n  char c = 0;\n  parseOK = false;\n\n#ifdef SHOW_JSON\n  int ccount = 0;\n#endif\n  // Send GET request\n  Serial.println();\n  OW_STATUS_PRINT(\"Sending GET request to \"); OW_STATUS_PRINT(host); OW_STATUS_PRINT(\" port \"); OW_STATUS_PRINT(port); OW_STATUS_PRINTF(\"\\n\");\n  client.print(String(\"GET \") + url + \" HTTP/1.1\\r\\n\" + \"Host: \" + host + \"\\r\\n\" + \"Connection: close\\r\\n\\r\\n\");\n\n  // Pull out any header, X-Forecast-API-Calls: reports current daily API call count\n  while (client.connected())\n  {\n    String line = client.readStringUntil('\\n');\n    if (line == \"\\r\") {\n      OW_STATUS_PRINTF(\"Header end found\\n\");\n      break;\n    }\n\n#ifdef SHOW_HEADER\n    Serial.println(line);\n#endif\n\n    if ((millis() - timeout) > 5000UL)\n    {\n      OW_STATUS_PRINTF (\"HTTP header timeout\\n\");\n      client.stop();\n      return false;\n    }\n  }\n\n  OW_STATUS_PRINTF(\"\\nParsing JSON\\n\");\n\n  // Parse the JSON data, available() includes yields\n  while ( client.available() > 0 || client.connected())\n  {\n    while(client.available() > 0)\n    {\n      c = client.read();\n      parser.parse(c);\n#ifdef SHOW_JSON\n      if (c == '{' || c == '[' || c == '}' || c == ']') Serial.println();\n      Serial.println(c); if (ccount++ > 100 && c == ',') {ccount = 0; Serial.println();}\n#endif\n    }\n\n    if ((millis() - timeout) > 8000UL)\n    {\n      OW_STATUS_PRINTF(\"Client timeout during JSON parse\\n\");\n      parser.reset();\n      client.stop();\n      return false;\n    }\n    yield();\n  }\n\n  OW_STATUS_PRINTF(\"\\nDone in \"); OW_STATUS_PRINT(millis()-dt); OW_STATUS_PRINTF(\" ms\\n\");\n  Serial.println();\n\n  parser.reset();\n\n  client.stop();\n  \n  // A message has been parsed, but the data-point correctness is unknown\n  return parseOK;\n}\n\n#else // ESP8266 or Arduino RP2040 Nano Connect version\n\n/***************************************************************************************\n** Function name:           parseRequest (for ESP8266)\n** Description:             Fetches the JSON message and feeds to the parser\n***************************************************************************************/\nbool OW_Weather::parseRequest(String url) {\n  if (Secure) return parseRequestSecure(&url);\n  else return parseRequestInsecure(&url);\n}\n\nbool OW_Weather::parseRequestSecure(String* url) {\n\n  uint32_t dt = millis();\n\n  const char*  host = \"api.openweathermap.org\";\n\n  #if (defined(ARDUINO_ARCH_MBED) || defined(ARDUINO_ARCH_RP2040)) && !defined(ARDUINO_RASPBERRY_PI_PICO_W)\n  WiFiSSLClient client;\n  #else\n  // Must use namespace:: to select BearSSL\n  BearSSL::WiFiClientSecure client;\n  client.setInsecure(); // Certificate not checked\n  #endif\n  port = 443;\n\n  if (!client.connect(host, port))\n  {\n    OW_STATUS_PRINTF(\"Connection failed.\\n\");\n    return false;\n  }\n  JSON_Decoder parser;\n  parser.setListener(this);\n\n  uint32_t timeout = millis();\n  char c = 0;\n  parseOK = false;\n\n  #ifdef SHOW_JSON\n  int ccount = 0;\n  #endif\n\n  #ifdef ESP8266\n  OW_STATUS_PRINTF(\"\\nThe connection to server is using BearSSL in insecure mode (certificates not checked).\\n\");\n  #endif\n\n  // Send GET request\n  Serial.println();\n  OW_STATUS_PRINTF(\"Sending GET request to api.openweathermap.org...\\n\");\n  Serial.println();\n  client.print(String(\"GET \") + *url + \" HTTP/1.1\\r\\n\" + \"Host: \" + host + \"\\r\\n\" + \"Connection: close\\r\\n\\r\\n\");\n  Serial.println();\n\n  // Pull out any header, X-Forecast-API-Calls: reports current daily API call count\n  while (client.available() || client.connected())\n  {\n    String line = client.readStringUntil('\\n');\n    if (line == \"\\r\") {\n      OW_STATUS_PRINTF(\"Header end found\\n\");\n      break;\n    }\n\n    OW_STATUS_PRINT(line); OW_STATUS_PRINTF(\"\\n\");\n\n    if ((millis() - timeout) > 5000UL)\n    {\n      OW_STATUS_PRINTF (\"HTTP header timeout\\n\");\n      client.stop();\n      return false;\n    }\n  }\n\n\n  // Parse the JSON data, available() includes yields\n  while (client.available() || client.connected())\n  {\n    while (client.available())\n    {\n      c = client.read();\n      parser.parse(c);\n  #ifdef SHOW_JSON\n      if (c == '{' || c == '[' || c == '}' || c == ']') Serial.println();\n      Serial.print(c); if (ccount++ > 100 && c == ',') {ccount = 0; Serial.println();}\n  #endif\n    }\n\n    if ((millis() - timeout) > 8000UL)\n    {\n      OW_STATUS_PRINTF (\"JSON client timeout\\n\");\n      parser.reset();\n      client.stop();\n      return false;\n    }\n  }\n\n  Serial.println();\n  OW_STATUS_PRINTF(\"\\nDone in \"); OW_STATUS_PRINT(millis()-dt); OW_STATUS_PRINTF(\" ms\\n\");\n\n  parser.reset();\n\n  client.stop();\n  \n  // A message has been parsed without error but the data-point correctness is unknown\n  return parseOK;\n}\n\nbool OW_Weather::parseRequestInsecure(String* url) {\n\n  uint32_t dt = millis();\n\n  const char*  host = \"api.openweathermap.org\";\n\n  // AXTLS used (insecure)\n  WiFiClient client;\n  port = 80;\n \n  if (!client.connect(host, port))\n  {\n    OW_STATUS_PRINTF(\"Connection failed.\\n\");\n    return false;\n  }\n  JSON_Decoder parser;\n  parser.setListener(this);\n\n  uint32_t timeout = millis();\n  char c = 0;\n  parseOK = false;\n\n  #ifdef SHOW_JSON\n  int ccount = 0;\n  #endif\n\n  OW_STATUS_PRINTF(\"\\nThe connection to server is INSECURE (using AXTLS).\\n\");\n\n  // Send GET request\n  OW_STATUS_PRINTF(\"Sending GET request to api.openweathermap.org...\\n\");\n  client.print(String(\"GET \") + *url + \" HTTP/1.1\\r\\n\" + \"Host: \" + host + \"\\r\\n\" + \"Connection: close\\r\\n\\r\\n\");\n\n  // Pull out any header, X-Forecast-API-Calls: reports current daily API call count\n  while (client.available() || client.connected())\n  {\n    String line = client.readStringUntil('\\n');\n    if (line == \"\\r\") {\n      OW_STATUS_PRINTF(\"Header end found\\n\");\n      break;\n    }\n\n    OW_STATUS_PRINT(line); OW_STATUS_PRINTF(\"\\n\");\n\n    if ((millis() - timeout) > 5000UL)\n    {\n      OW_STATUS_PRINTF(\"HTTP header timeout\\n\");\n      client.stop();\n      return false;\n    }\n  }\n\n\n  // Parse the JSON data, available() includes yields\n  while (client.available() || client.connected())\n  {\n    while (client.available())\n    {\n      c = client.read();\n      parser.parse(c);\n  #ifdef SHOW_JSON\n      if (c == '{' || c == '[' || c == '}' || c == ']') Serial.println();\n      Serial.print(c); if (ccount++ > 100 && c == ',') {ccount = 0; Serial.println();}\n  #endif\n    }\n\n    if ((millis() - timeout) > 8000UL)\n    {\n      OW_STATUS_PRINTF(\"JSON client timeout\\n\");\n      parser.reset();\n      client.stop();\n      return false;\n    }\n  }\n\n  OW_STATUS_PRINTF(\"\\nDone in \"); OW_STATUS_PRINT(millis()-dt); OW_STATUS_PRINTF(\" ms\\n\");\n\n  parser.reset();\n\n  client.stop();\n  \n  // A message has been parsed without error but the data-point correctness is unknown\n  return parseOK;\n}\n\n #endif // ESP32 or ESP8266 parseRequest\n\n\n/***************************************************************************************\n** Function name:           key etc\n** Description:             These functions are called while parsing the JSON message\n***************************************************************************************/\nvoid OW_Weather::key(const char *key) {\n\n  currentKey = key;\n\n#ifdef SHOW_CALLBACK\n  Serial.println(\"\\n>>> Key >>>\" + (String)key);\n#endif\n}\n\nvoid OW_Weather::startDocument() {\n\n  currentParent = currentKey =   currentSet = \"\";\n  objectLevel = 0;\n  valuePath = \"\";\n  arrayIndex = 0;\n  arrayLevel = 0;\n  parseOK = true;\n\n#ifdef SHOW_CALLBACK\n  Serial.print(\"\\n>>> Start document >>>\");\n#endif\n}\n\nvoid OW_Weather::endDocument() {\n\n  currentParent = currentKey = \"\";\n  objectLevel = 0;\n  valuePath = \"\";\n  arrayIndex = 0;\n  arrayLevel = 0;\n\n#ifdef SHOW_CALLBACK\n  Serial.print(\"\\n<<< End document <<<\");\n#endif\n}\n\nvoid OW_Weather::startObject() {\n\n  if (arrayIndex == 0 && objectLevel == 1) currentParent = currentKey;\n  currentSet = currentKey;\n  objectLevel++;\n\n#ifdef SHOW_CALLBACK\n  Serial.print(\"\\n>>> Start object level:\" + (String) objectLevel + \" array level:\" + (String) arrayLevel + \" array index:\" + (String) arrayIndex +\" >>>\");\n#endif\n}\n\nvoid OW_Weather::endObject() {\n\n  if (arrayLevel == 0) currentParent = \"\";\n  if (arrayLevel == 1  && objectLevel == 2) arrayIndex++;\n  objectLevel--;\n  \n\n#ifdef SHOW_CALLBACK\n  Serial.print(\"\\n<<< End object <<<\");\n#endif\n}\n\nvoid OW_Weather::startArray() {\n\n  arrayLevel++;\n  valuePath = currentParent + \"/\" + currentKey; // aka = current Object, e.g. \"daily:data\"\n\n#ifdef SHOW_CALLBACK\n  Serial.print(\"\\n>>> Start array \" + valuePath + \"/\" + (String) arrayLevel + \"/\" + (String) arrayIndex +\" >>>\");\n#endif\n}\n\nvoid OW_Weather::endArray() {\n  if (arrayLevel > 0) arrayLevel--;\n  if (arrayLevel == 0) arrayIndex = 0;\n  valuePath = \"\";\n\n#ifdef SHOW_CALLBACK\n  Serial.print(\"\\n<<< End array <<<\");\n#endif\n}\n\nvoid OW_Weather::whitespace(char c) {\n  c = c; // Avoid warning\n}\n\nvoid OW_Weather::error( const char *message ) {\n  Serial.print(\"\\nParse error message: \");\n  Serial.print(message);\n  parseOK = false;\n}\n\n/***************************************************************************************\n** Function name:           value (full or partial data set)\n** Description:             Stores the parsed data in the structures for sketch access\n***************************************************************************************/\nvoid OW_Weather::value(const char *val)\n{\n  if (oneCall) {\n    if (!partialSet) fullDataSet(val);\n    else partialDataSet(val);\n  }\n  else {\n    forecastDataSet(val);\n  }\n}\n\n/***************************************************************************************\n** Function name:           fullDataSet\n** Description:             Collects full data set\n***************************************************************************************/\nvoid OW_Weather::fullDataSet(const char *val) {\n\n   String value = val;\n\n  // Start of JSON\n  if (currentParent == \"\") {\n    if (currentKey == \"lat\") lat = value.toFloat();\n    if (currentKey == \"lon\") lon = value.toFloat();\n    if (currentKey == \"timezone_offset\") timezone = value;\n  }\n\n  // Current forecast - no array index - short path\n  if (currentParent == \"current\") {\n    data_set = \"current\";\n    if (currentKey == \"dt\") current->dt = (uint32_t)value.toInt();\n    else\n    if (currentKey == \"sunrise\") current->sunrise = (uint32_t)value.toInt();\n    else\n    if (currentKey == \"sunset\") current->sunset = (uint32_t)value.toInt();\n    else\n    if (currentKey == \"temp\") current->temp = value.toFloat();\n    else\n    if (currentKey == \"feels_like\") current->feels_like = value.toFloat();\n    else\n    if (currentKey == \"pressure\") current->pressure = value.toFloat();\n    else\n    if (currentKey == \"humidity\") current->humidity = value.toInt();\n    else\n    if (currentKey == \"dew_point\") current->dew_point = value.toFloat();\n    else\n    if (currentKey == \"uvi\") current->uvi = value.toFloat();\n    else\n    if (currentKey == \"clouds\") current->clouds = value.toInt();\n    else\n    if (currentKey == \"visibility\") current->visibility = value.toInt();\n    else\n    if (currentKey == \"wind_speed\") current->wind_speed = value.toFloat();\n    else\n    if (currentKey == \"wind_gust\") current->wind_gust = value.toFloat();\n    else\n    if (currentKey == \"wind_deg\") current->wind_deg = (uint16_t)value.toInt();\n    else\n    if (currentKey == \"rain\") current->rain = value.toFloat();\n    else\n    if (currentKey == \"snow\") current->snow = value.toFloat();\n    else\n\n    if (currentKey == \"id\") current->id = value.toInt();\n    else\n    if (currentKey == \"main\") current->main = value;\n    else\n    if (currentKey == \"description\") current->description = value;\n    else\n    if (currentKey == \"icon\") current->icon = value;\n\n    return;\n  }\n\n  // Hourly forecast\n  if (currentParent == \"hourly\") {\n    data_set = \"hourly\";\n    \n    if (arrayIndex >= MAX_HOURS) return;\n    \n    if (currentKey == \"dt\") hourly->dt[arrayIndex] = (uint32_t)value.toInt();\n    else\n    if (currentKey == \"temp\") hourly->temp[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"feels_like\") hourly->feels_like[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"pressure\") hourly->pressure[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"humidity\") hourly->humidity[arrayIndex] = value.toInt();\n    else\n    if (currentKey == \"dew_point\") hourly->dew_point[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"clouds\") hourly->clouds[arrayIndex] = value.toInt();\n    else\n    if (currentKey == \"wind_speed\") hourly->wind_speed[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"wind_gust\") hourly->wind_gust[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"wind_deg\") hourly->wind_deg[arrayIndex] = (uint16_t)value.toInt();\n    else\n    if (currentKey == \"rain\") hourly->rain[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"snow\") hourly->snow[arrayIndex] = value.toFloat();\n    else\n\n    if (currentKey == \"id\") hourly->id[arrayIndex] = value.toInt();\n    else\n    if (currentKey == \"main\") hourly->main[arrayIndex] = value;\n    else\n    if (currentKey == \"description\") hourly->description[arrayIndex] = value;\n    else\n    if (currentKey == \"icon\") hourly->icon[arrayIndex] = value;\n    else\n    if (currentKey == \"pop\") hourly->pop[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"1h\") hourly->rain1h[arrayIndex] = value.toFloat();\n\n    return;\n  }\n\n\n  // Daily forecast\n  if (currentParent == \"daily\") {\n    data_set = \"daily\";\n    \n    if (arrayIndex >= MAX_DAYS) return;\n    \n    if (currentKey == \"dt\") daily->dt[arrayIndex] = (uint32_t)value.toInt();\n    else\n    if (currentKey == \"sunrise\") daily->sunrise[arrayIndex] = (uint32_t)value.toInt();\n    else\n    if (currentKey == \"sunset\") daily->sunset[arrayIndex] = (uint32_t)value.toInt();\n    else\n    if (currentKey == \"moonrise\") daily->moonrise[arrayIndex] = (uint32_t)value.toInt();\n    else\n    if (currentKey == \"moonset\") daily->moonset[arrayIndex] = (uint32_t)value.toInt();\n    else\n    if (currentKey == \"pressure\") daily->pressure[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"humidity\") daily->humidity[arrayIndex] = value.toInt();\n    else\n    if (currentKey == \"dew_point\") daily->dew_point[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"clouds\") daily->clouds[arrayIndex] = value.toInt();\n    else\n    if (currentKey == \"wind_speed\") daily->wind_speed[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"wind_gust\") daily->wind_gust[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"wind_deg\") daily->wind_deg[arrayIndex] = (uint16_t)value.toInt();\n    else\n    if (currentKey == \"rain\") daily->rain[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"snow\") daily->snow[arrayIndex] = value.toFloat();\n    else\n\n    if (currentKey == \"id\") daily->id[arrayIndex] = value.toInt();\n    else\n    if (currentKey == \"main\") daily->main[arrayIndex] = value;\n    else\n    if (currentKey == \"description\") daily->description[arrayIndex] = value;\n    else\n    if (currentKey == \"icon\") daily->icon[arrayIndex] = value;\n    else\n    if (currentKey == \"pop\") daily->pop[arrayIndex] = value.toFloat();\n\n    if (currentSet == \"temp\") {\n      if (currentKey == \"morn\") daily->temp_morn[arrayIndex] = value.toFloat();\n      else\n      if (currentKey == \"day\") daily->temp_day[arrayIndex] = value.toFloat();\n      else\n      if (currentKey == \"eve\") daily->temp_eve[arrayIndex] = value.toFloat();\n      else\n      if (currentKey == \"night\") daily->temp_night[arrayIndex] = value.toFloat();\n      else\n      if (currentKey == \"min\") daily->temp_min[arrayIndex] = value.toFloat();\n      else\n      if (currentKey == \"max\") daily->temp_max[arrayIndex] = value.toFloat();\n    }\n\n    if (currentSet == \"feels_like\") {\n      if (currentKey == \"morn\") daily->feels_like_morn[arrayIndex] = value.toFloat();\n      else\n      if (currentKey == \"day\") daily->feels_like_day[arrayIndex] = value.toFloat();\n      else\n      if (currentKey == \"eve\") daily->feels_like_eve[arrayIndex] = value.toFloat();\n      else\n      if (currentKey == \"night\") daily->feels_like_night[arrayIndex] = value.toFloat();\n    }\n\n    return;\n  }\n\n}\n\n/***************************************************************************************\n** Function name:           forecastDataSet\n** Description:             Collects full data set\n***************************************************************************************/\nvoid OW_Weather::forecastDataSet(const char *val) {\n\n   String value = val;\n\n  // Start of JSON\n  if (currentParent == \"\") {\n    if (currentKey == \"timezone\") forecast->timezone = value.toInt();\n    else\n    if (currentKey == \"sunrise\") forecast->sunrise = (uint32_t)value.toInt();\n    else\n    if (currentKey == \"sunset\") forecast->sunset = (uint32_t)value.toInt();\n\n    return;\n  }\n\n  // Loacation\n  if (currentParent == \"city\") {\n    if (currentKey == \"name\") forecast->city_name = value;\n    else\n    if (currentKey == \"lat\") lat = value.toFloat();\n    else\n    if (currentKey == \"lon\") lon = value.toFloat();\n\n    return;\n  }\n\n  // 3 hourly forecasts\n  if (currentParent == \"list\") {\n    data_set = \"list\";\n    if (arrayIndex >= MAX_3HRS) return;\n\n    if (currentKey == \"dt\") forecast->dt[arrayIndex] = (uint32_t)value.toInt();\n    else\n    if (currentKey == \"temp\") forecast->temp[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"temp_min\") forecast->temp_min[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"temp_max\") forecast->temp_max[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"feels_like\") forecast->feels_like[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"pressure\") forecast->pressure[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"sea_level\") forecast->sea_level[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"grnd_level\") forecast->grnd_level[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"humidity\") forecast->humidity[arrayIndex] = value.toInt();\n    else\n    if (currentKey == \"id\") forecast->id[arrayIndex] = value.toInt();\n    else\n    if (currentKey == \"main\") forecast->main[arrayIndex] = value;\n    else\n    if (currentKey == \"description\") forecast->description[arrayIndex] = value;\n    else\n    if (currentKey == \"icon\") forecast->icon[arrayIndex] = value;\n    else\n    if (currentKey == \"all\") forecast->clouds_all[arrayIndex] = (uint8_t)value.toInt();\n    else\n    if (currentKey == \"speed\") forecast->wind_speed[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"deg\") forecast->wind_deg[arrayIndex] = (uint16_t)value.toInt();\n    else\n    if (currentKey == \"gust\") forecast->wind_gust[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"visibility\") forecast->visibility[arrayIndex] = value.toInt();\n    else\n    if (currentKey == \"pop\") forecast->pop[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"dt_txt\") forecast->dt_txt[arrayIndex] = value;\n\n    return;\n  }\n\n}\n\n/***************************************************************************************\n** Function name:           partialDataSet\n** Description:             Collects partial data set\n***************************************************************************************/\nvoid OW_Weather::partialDataSet(const char *val) {\n\n   String value = val;\n\n  // Current forecast - no array index - short path\n  if (currentParent == \"current\") {\n    data_set = \"current\";\n    if (currentKey == \"dt\") current->dt = (uint32_t)value.toInt();\n    else\n    if (currentKey == \"sunrise\") current->sunrise = (uint32_t)value.toInt();\n    else\n    if (currentKey == \"sunset\") current->sunset = (uint32_t)value.toInt();\n    else\n    if (currentKey == \"temp\") current->temp = value.toFloat();\n    //else\n    //if (currentKey == \"feels_like\") current->feels_like = value.toFloat();\n    else\n    if (currentKey == \"pressure\") current->pressure = value.toFloat();\n    else\n    if (currentKey == \"humidity\") current->humidity = value.toInt();\n    //else\n    //if (currentKey == \"dew_point\") current->dew_point = value.toFloat();\n    //else\n    //if (currentKey == \"uvi\") current->uvi = value.toFloat();\n    else\n    if (currentKey == \"clouds\") current->clouds = value.toInt();\n    //else\n    //if (currentKey == \"visibility\") current->visibility = value.toInt();\n    else\n    if (currentKey == \"wind_speed\") current->wind_speed = value.toFloat();\n    //else\n    //if (currentKey == \"wind_gust\") current->wind_gust = value.toFloat();\n    else\n    if (currentKey == \"wind_deg\") current->wind_deg = (uint16_t)value.toInt();\n    //else\n    //if (currentKey == \"rain\") current->rain = value.toFloat();\n    //else\n    //if (currentKey == \"snow\") current->snow = value.toFloat();\n\n    else\n    if (currentKey == \"id\") current->id = value.toInt();\n    else\n    if (currentKey == \"main\") current->main = value;\n    else\n    if (currentKey == \"description\") current->description = value;\n    //else\n    //if (currentKey == \"icon\") current->icon = value;\n\n    return;\n  }\n\n/*\n  // Hourly forecast\n  if (currentParent == \"hourly\") {\n    data_set = \"hourly\";\n    \n    if (arrayIndex >= MAX_HOURS) return;\n    \n    if (currentKey == \"dt\") hourly->dt[arrayIndex] = (uint32_t)value.toInt();\n    else\n    if (currentKey == \"temp\") hourly->temp[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"feels_like\") hourly->feels_like[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"pressure\") hourly->pressure[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"humidity\") hourly->humidity[arrayIndex] = value.toInt();\n    else\n    if (currentKey == \"dew_point\") hourly->dew_point[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"clouds\") hourly->clouds[arrayIndex] = value.toInt();\n    else\n    if (currentKey == \"wind_speed\") hourly->wind_speed[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"wind_gust\") hourly->wind_gust[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"wind_deg\") hourly->wind_deg[arrayIndex] = (uint16_t)value.toInt();\n    else\n    if (currentKey == \"rain\") hourly->rain[arrayIndex] = value.toFloat();\n    else\n    if (currentKey == \"snow\") hourly->snow[arrayIndex] = value.toFloat();\n    else\n\n    if (currentKey == \"id\") hourly->id[arrayIndex] = value.toInt();\n    else\n    if (currentKey == \"main\") hourly->main[arrayIndex] = value;\n    else\n    if (currentKey == \"description\") hourly->description[arrayIndex] = value;\n    else\n    if (currentKey == \"icon\") hourly->icon[arrayIndex] = value;\n\n    return;\n  }\n*/\n\n  // Daily forecast\n  if (currentParent == \"daily\") {\n    data_set = \"daily\";\n    \n    if (arrayIndex >= MAX_DAYS) return;\n    \n    if (currentKey == \"dt\") daily->dt[arrayIndex] = (uint32_t)value.toInt();\n    else\n    //if (currentKey == \"sunrise\") daily->sunrise[arrayIndex] = (uint32_t)value.toInt();\n    //else\n    //if (currentKey == \"sunset\") daily->sunset[arrayIndex] = (uint32_t)value.toInt();\n    //else\n    //if (currentKey == \"pressure\") daily->pressure[arrayIndex] = value.toFloat();\n    //else\n    //if (currentKey == \"humidity\") daily->humidity[arrayIndex] = value.toInt();\n    //else\n    //if (currentKey == \"dew_point\") daily->dew_point[arrayIndex] = value.toFloat();\n    //else\n    //if (currentKey == \"clouds\") daily->clouds[arrayIndex] = value.toInt();\n    //else\n    //if (currentKey == \"wind_speed\") daily->wind_speed[arrayIndex] = value.toFloat();\n    //else\n    //if (currentKey == \"wind_gust\") daily->wind_gust[arrayIndex] = value.toFloat();\n    //else\n    //if (currentKey == \"wind_deg\") daily->wind_deg[arrayIndex] = (uint16_t)value.toInt();\n    //else\n    //if (currentKey == \"rain\") daily->rain[arrayIndex] = value.toFloat();\n    //else\n    //if (currentKey == \"snow\") daily->snow[arrayIndex] = value.toFloat();\n    //else\n\n    if (currentKey == \"id\") daily->id[arrayIndex] = value.toInt();\n    //else\n    //if (currentKey == \"main\") daily->main[arrayIndex] = value;\n    //else\n    //if (currentKey == \"description\") daily->description[arrayIndex] = value;\n    //else\n    //if (currentKey == \"icon\") daily->icon[arrayIndex] = value;\n\n    if (currentSet == \"temp\") {\n      //if (currentKey == \"morn\") daily->temp_morn[arrayIndex] = value.toFloat();\n      //else\n      //if (currentKey == \"day\") daily->temp_day[arrayIndex] = value.toFloat();\n      //else\n      //if (currentKey == \"eve\") daily->temp_eve[arrayIndex] = value.toFloat();\n      //else\n      //if (currentKey == \"night\") daily->temp_night[arrayIndex] = value.toFloat();\n      //else\n      if (currentKey == \"min\") daily->temp_min[arrayIndex] = value.toFloat();\n      else\n      if (currentKey == \"max\") daily->temp_max[arrayIndex] = value.toFloat();\n    }\n\n    //if (currentSet == \"feels_like\") {\n      //if (currentKey == \"morn\") daily->feels_like_morn[arrayIndex] = value.toFloat();\n      //else\n      //if (currentKey == \"day\") daily->feels_like_day[arrayIndex] = value.toFloat();\n      //else\n      //if (currentKey == \"eve\") daily->feels_like_eve[arrayIndex] = value.toFloat();\n      //else\n      //if (currentKey == \"night\") daily->feels_like_night[arrayIndex] = value.toFloat();\n    //}\n\n    return;\n  }\n\n}\n"
  },
  {
    "path": "OpenWeather.h",
    "content": "// Client library for the OpenWeatherMap data-point server\n// https://openweathermap.org/\n\n// The API server uses https, so a client library with secure support is needed\n\n// Created by Bodmer 9/4/2020\n// This is a beta test version and is subject to change!\n// Insecure mode added by ADAMSIN12\n\n// See license.txt in root folder of library\n\n#define MAX_ICON_INDEX 11 // Maximum for weather icon index\n#define ICON_RAIN 1       // Index for the rain icon bitmap (bmp file)\n#define NO_VALUE 11       // for precipType default (none)\n\n#ifndef OpenWeather_h\n#define OpenWeather_h\n\n// The streaming parser to use is not the Arduino IDE library manager default,\n// but this one which is slightly different and renamed to avoid conflicts:\n// https://github.com/Bodmer/JSON_Decoder\n\n#include <JSON_Listener.h>\n#include <JSON_Decoder.h>\n\n#include \"User_Setup.h\"\n#include \"Data_Point_Set.h\"\n\n\n/***************************************************************************************\n** Description:   JSON interface class\n***************************************************************************************/\nclass OW_Weather: public JsonListener {\n\n  public:\n    // Sketch calls this forecast request, it returns true if no parse errors encountered\n    // ESP8266 only: setting secure to false will invoke an insecure connection\n    bool getForecast(OW_current *current, OW_hourly *hourly, OW_daily  *daily,\n                     String api_key, String latitude, String longitude,\n                     String units, String language, bool secure = true);\n\n    // From 2023 the above call requires a subscription, this of uses the forecast API\n    // and is free for 1000 calls per day\n    bool getForecast(OW_forecast *forecast,\n                     String api_key, String latitude, String longitude,\n                     String units, String language, bool secure = true);\n\n    // Called by library (or user sketch), sends a GET request to a https (secure) url\n    bool parseRequest(String url); // and parses response, returns true if no parse errors\n\n    // Called by library (or user sketch), sends a GET request to a http (insecure) url\n    bool parseRequestSecure(String* url); \n    bool parseRequestInsecure(String* url); \n\n    void partialDataSet(bool partialSet);\n\n    float    lat = 0;\n    float    lon = 0;\n    String   timezone = \"\";\n\n  private: // Streaming parser callback functions, allow tracking and decisions\n\n    void startDocument(); // JSON document has started, typically starts once\n                          // Initialises variables used, e.g. sets objectLayer = 0\n                          // and arrayIndex =0\n    void endDocument();   // JSON document has ended, typically ends once\n\n    void startObject();   // Called every time an Object start detected\n                          // may be called multiple times as object layers entered\n                          // Used to increment objectLayer\n    void endObject();     // Called every time an object ends\n                          // Used to decrement objectLayer and zero arrayIndex\n\n\n    void startArray();    // An array of name:value pairs entered\n    void endArray();      // Array member ended, increments arrayIndex\n\n    void key(const char *key);            // The current \"object\" or \"name for a name:value pair\"\n    void value(const char *value);        // String value from name:value pair e.g. \"1.23\" or \"rain\"\n\n    void whitespace(char c);              // Whitespace character in JSON - not used\n\n    void error( const char *message );    // Error message is sent to serial port\n\n    void fullDataSet(const char *value);    // Populate structure with full data set\n    void partialDataSet(const char *value); // Populate structure with minimal data set\n    void forecastDataSet(const char *val);  // Populate forecast structure\n\n\n  private: // Variables used internal to library\n\n    uint16_t hourly_index;   // index into the OW_hourly structure's data arrays\n    uint16_t daily_index;    // index into the OW_daily structure's data arrays\n    uint16_t forecast_index; // index into the OW_forecast structure's data arrays\n\n    // The value storage structures are created and deleted by the sketch and\n    // a pointer passed via the library getForecast() call the value() function\n    // is then used to populate the structs with values\n    OW_current  *current;  // pointer provided by sketch to the OW_current struct\n    OW_hourly   *hourly;   // pointer provided by sketch to the OW_hourly struct\n    OW_daily    *daily;    // pointer provided by sketch to the OW_daily struct\n    OW_forecast *forecast; // pointer provided by sketch to the OW_forecast struct\n\n    String      valuePath;  // object (i.e. sequential key) path (like a \"file path\")\n                            // taken to the name:value pair in the form \"hourly/data\"\n                            // so values can be pulled from the correct array.\n                            // Needed since different objects contain \"data\" arrays.\n\n    String data_set;        // A copy of the last object name at the head of an array\n                            // short equivalent to path.\n\n    bool     parseOK;       // true if the parse been completed\n                            // (does not mean data values gathered are good!)\n\n    bool     partialSet = false;    // Set true for partial data set acquisition\n    bool     oneCall = true;        // Use the oneCall API\n\n    String   currentParent; // Current object e.g. \"daily\"\n    uint16_t objectLevel;   // Object level, increments for new object, decrements at end\n    String   currentKey;    // Name key of the name:value pair e.g \"temperature\"\n    String   currentSet;    // Name key of the data set\n    String   arrayPath;     // Path to name:value pair e.g.  \"daily/data\"\n    uint16_t arrayIndex;    // Array index e.g. 5 for day 5 forecast, qualify with arrayPath\n    uint16_t arrayLevel;    // Array level\n\n    bool     Secure = true; // Link security setting secure (https) or insecure (http)\n    uint16_t port;          // \n};\n\n/***************************************************************************************\n***************************************************************************************/\n#endif\n"
  },
  {
    "path": "README.md",
    "content": "# Raspberry Pico W, RP2040 Nano Connect, ESP8266 and ESP32 OpenWeather client\n\nArduino client library for https://openweathermap.org/\n\nCollects current weather plus daily forecasts.\n\nRequires the JSON parse library here:\nhttps://github.com/Bodmer/JSON_Decoder\n\nThe OpenWeather_Forecast_Test example sketch sends collected data to the Serial port for API test. It does not not require a TFT screen and works with the Raspberry Pico W, RP2040 Nano Connect, ESP32 and ESP8266 processor boards. This example provides access to the weather data via a ser of variables, so could be adapted for use in weather related projects.\n\nThe TFT_eSPI_OpenWeather_LittleFS example works with the RP2040 Pico W, RP2040 Nano Connect, ESP32 and ESP8266. It uses LittleFS and displays the weather data on a TFT screen. This example uses the TFT_eSPI library.\n\nThe above examples will work with a free subscription to the OpenWeather service. The examples in the Onecall folder however require a subscription account (See OpenWeatherMap website for details).\n\nThe Raspberry Pico W and RP2040 Nano Connect must be used with Earle Philhower's board package:\nhttps://github.com/earlephilhower/arduino-pico\n\nThese examples use anti-aliased fonts and newly created icons:\n\n![Weather isons](https://i.imgur.com/luK7Vcj.jpg)\n\nLatest screen grabs:\n\n![TFT screenshot 1](https://i.imgur.com/ORovwNY.png)\n\n"
  },
  {
    "path": "User_Setup.h",
    "content": "\n// Configuration settings for OpenWeather library\n\n\n// These parameters set the data point count stored in program memory (not the datapoint\n// count sent by the server). So they determine the memory used during collection\n// of the data points.\n\n#define MAX_HOURS 6     // Maximum \"hourly\" forecast period, can be up 1 to 48\n                        // Hourly forecast not used by TFT_eSPI_OpenWeather example\n\n#define MAX_DAYS 5      // Maximum \"daily\" forecast periods can be 1 to 8 (Today + 7 days = 8 maximum)\n                        // TFT_eSPI_OpenWeather example requires this to be >= 5 (today + 4 forecast days)\n\n//#define SHOW_HEADER   // Debug only - for checking response header via serial message\n//#define SHOW_JSON     // Debug only - simple serial output formatting of whole JSON message\n//#define SHOW_CALLBACK // Debug only to show the decode tree\n#define OW_STATUS_ON    // Debug only - turn on/off progress and status messages\n\n\n// ###############################################################################\n// DO NOT tinker below, this is configuration checking that helps stop crashes:\n// ###############################################################################\n\n#ifdef OW_STATUS_ON\n  #define OW_STATUS_PRINTF(C) Serial.print(F(C))\n  #define OW_STATUS_PRINT(V) Serial.print(V)\n#else\n  #define OW_STATUS_PRINTF(C)\n  #define OW_STATUS_PRINT(X)\n#endif\n\n// Check and correct bad setting\n#if (MAX_HOURS > 48) || (MAX_HOURS < 1)\n  #undef  MAX_HOURS\n  #define MAX_HOURS 48 // Ignore compiler warning!\n#endif\n\n// Check and correct bad setting\n#if (MAX_DAYS > 8) || (MAX_DAYS < 1)\n  #undef  MAX_DAYS\n  #define MAX_DAYS 8  // Ignore compiler warning!\n#endif\n\n#define MAX_3HRS (MAX_DAYS * 8)"
  },
  {
    "path": "examples/Onecall API (subscription required)/My_OpenWeather_Test/My_OpenWeather_Test.ino",
    "content": "// Sketch for Pico W, RP2040 Nano Connect, ESP32 and ESP8266 to fetch the Weather\n// Forecast from OpenWeather, an example from the library here:\n// https://github.com/Bodmer/OpenWeather\n\n// Sign up for a key and read API configuration info here:\n// https://openweathermap.org/\n\n// You can change the number of hours and days for the forecast in the\n// \"User_Setup.h\" file inside the OpenWeather library folder.\n// By default this is 6 hours (can be up to 48) and 5 days\n// (can be up to 8 days = today plus 7 days)\n\n// Choose library to load\n#ifdef ESP8266\n  #include <ESP8266WiFi.h>\n  #include <WiFiClientSecure.h>\n#elif defined(ARDUINO_ARCH_MBED) || defined(ARDUINO_ARCH_RP2040)\n  #if defined(ARDUINO_RASPBERRY_PI_PICO_W)\n    #include <WiFi.h>\n  #else\n    #include <WiFiNINA.h>\n  #endif\n#else // ESP32\n  #include <WiFi.h>\n#endif\n\n#include <JSON_Decoder.h>\n\n#include <OpenWeather.h>\n\n// Just using this library for unix time conversion\n#include <Time.h>\n\n// =====================================================\n// ========= User configured stuff starts here =========\n// Further configuration settings can be found in the\n// OpenWeather library \"User_Setup.h\" file\n\n#define TIME_OFFSET 1UL * 3600UL // UTC + 0 hour\n\n// Change to suit your WiFi router\n#define WIFI_SSID     \"Your_SSID\"\n#define WIFI_PASSWORD \"Your_password\"\n\n// OpenWeather API Details, replace x's with your API key\nString api_key = \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"; // Obtain this from your OpenWeather account\n\n// Set both your longitude and latitude to at least 4 decimal places\nString latitude =  \"27.9881\"; // 90.0000 to -90.0000 negative for Southern hemisphere\nString longitude = \"86.9250\"; // 180.000 to -180.000 negative for West\n\nString units = \"metric\";  // or \"imperial\"\nString language = \"en\";   // See notes tab\n\n// =========  User configured stuff ends here  =========\n// =====================================================\n\nOW_Weather ow; // Weather forecast library instance\n\nvoid setup() { \n  Serial.begin(250000); // Fast to stop it holding up the stream\n  Serial.println(\"\");\n\n  Serial.printf(\"\\n\\nConnecting to %s\\n\", WIFI_SSID);\n  Serial.println(\"\");\n\n  // Call once for ESP32 and ESP8266\n  #if !defined(ARDUINO_ARCH_MBED)\n    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);\n  #endif\n\n  while (WiFi.status() != WL_CONNECTED) {\n    Serial.print(\".\");\n    #if defined(ARDUINO_ARCH_MBED) || defined(ARDUINO_ARCH_RP2040)\n      if (WiFi.status() != WL_CONNECTED) WiFi.begin(WIFI_SSID, WIFI_PASSWORD);\n    #endif\n    delay(500);\n  }\n\n  Serial.println();\n  Serial.println(\"Connected\\n\");\n}\n\nvoid loop() {\n\n  printCurrentWeather();\n\n  // We can make 1000 requests a day\n  delay(5 * 60 * 1000); // Every 5 minutes = 288 requests per day\n}\n\n/***************************************************************************************\n**                          Send weather info to serial port\n***************************************************************************************/\nvoid printCurrentWeather()\n{\n  // Create the structures that hold the retrieved weather\n  OW_current *current = new OW_current;\n  OW_hourly *hourly = new OW_hourly;\n  OW_daily  *daily = new OW_daily;\n\n  Serial.println(\"\\nRequesting weather information from OpenWeather... \");\n\n  //On the ESP8266 (only) the library by default uses BearSSL, another option is to use AXTLS\n  //For problems with ESP8266 stability, use AXTLS by adding a false parameter thus       vvvvv\n  //ow.getForecast(current, hourly, daily, api_key, latitude, longitude, units, language, false);\n\n  ow.getForecast(current, hourly, daily, api_key, latitude, longitude, units, language);\n  Serial.println(\"\");\n  Serial.println(\"Weather from Open Weather\\n\");\n\n  // Position as reported by Open Weather\n  Serial.print(\"Latitude            : \"); Serial.println(ow.lat);\n  Serial.print(\"Longitude           : \"); Serial.println(ow.lon);\n  // We can use the timezone to set the offset eventually...\n  Serial.print(\"Timezone            : \"); Serial.println(ow.timezone);\n  Serial.println();\n\n  if (current)\n  {\n    Serial.println(\"############### Current weather ###############\\n\");\n    Serial.print(\"dt (time)        : \"); Serial.println(strTime(current->dt));\n    Serial.print(\"sunrise          : \"); Serial.println(strTime(current->sunrise));\n    Serial.print(\"sunset           : \"); Serial.println(strTime(current->sunset));\n    Serial.print(\"temp             : \"); Serial.println(current->temp);\n    Serial.print(\"feels_like       : \"); Serial.println(current->feels_like);\n    Serial.print(\"pressure         : \"); Serial.println(current->pressure);\n    Serial.print(\"humidity         : \"); Serial.println(current->humidity);\n    Serial.print(\"dew_point        : \"); Serial.println(current->dew_point);\n    Serial.print(\"uvi              : \"); Serial.println(current->uvi);\n    Serial.print(\"clouds           : \"); Serial.println(current->clouds);\n    Serial.print(\"visibility       : \"); Serial.println(current->visibility);\n    Serial.print(\"wind_speed       : \"); Serial.println(current->wind_speed);\n    Serial.print(\"wind_gust        : \"); Serial.println(current->wind_gust);\n    Serial.print(\"wind_deg         : \"); Serial.println(current->wind_deg);\n    Serial.print(\"rain             : \"); Serial.println(current->rain);\n    Serial.print(\"snow             : \"); Serial.println(current->snow);\n    Serial.println();\n    Serial.print(\"id               : \"); Serial.println(current->id);\n    Serial.print(\"main             : \"); Serial.println(current->main);\n    Serial.print(\"description      : \"); Serial.println(current->description);\n    Serial.print(\"icon             : \"); Serial.println(current->icon);\n\n    Serial.println();\n  }\n\n  if (hourly)\n  {\n    Serial.println(\"############### Hourly weather  ###############\\n\");\n    for (int i = 0; i < MAX_HOURS; i++)\n    {\n      Serial.print(\"Hourly summary  \"); if (i < 10) Serial.print(\" \"); Serial.print(i);\n      Serial.println();\n      Serial.print(\"dt (time)        : \"); Serial.println(strTime(hourly->dt[i]));\n      Serial.print(\"temp             : \"); Serial.println(hourly->temp[i]);\n      Serial.print(\"feels_like       : \"); Serial.println(hourly->feels_like[i]);\n      Serial.print(\"pressure         : \"); Serial.println(hourly->pressure[i]);\n      Serial.print(\"humidity         : \"); Serial.println(hourly->humidity[i]);\n      Serial.print(\"dew_point        : \"); Serial.println(hourly->dew_point[i]);\n      Serial.print(\"clouds           : \"); Serial.println(hourly->clouds[i]);\n      Serial.print(\"wind_speed       : \"); Serial.println(hourly->wind_speed[i]);\n      Serial.print(\"wind_gust        : \"); Serial.println(hourly->wind_gust[i]);\n      Serial.print(\"wind_deg         : \"); Serial.println(hourly->wind_deg[i]);\n      Serial.print(\"rain             : \"); Serial.println(hourly->rain[i]);\n      Serial.print(\"snow             : \"); Serial.println(hourly->snow[i]);\n      Serial.println();\n      Serial.print(\"id               : \"); Serial.println(hourly->id[i]);\n      Serial.print(\"main             : \"); Serial.println(hourly->main[i]);\n      Serial.print(\"description      : \"); Serial.println(hourly->description[i]);\n      Serial.print(\"icon             : \"); Serial.println(hourly->icon[i]);\n      Serial.print(\"pop              : \"); Serial.println(hourly->pop[i]);\n\n      Serial.println();\n    }\n  }\n\n  if (daily)\n  {\n    Serial.println(\"###############  Daily weather  ###############\\n\");\n    for (int i = 0; i < MAX_DAYS; i++)\n    {\n      Serial.print(\"Daily summary   \"); if (i < 10) Serial.print(\" \"); Serial.print(i);\n      Serial.println();\n      Serial.print(\"dt (time)        : \"); Serial.println(strTime(daily->dt[i]));\n      Serial.print(\"sunrise          : \"); Serial.println(strTime(daily->sunrise[i]));\n      Serial.print(\"sunset           : \"); Serial.println(strTime(daily->sunset[i]));\n\n      Serial.print(\"temp.morn        : \"); Serial.println(daily->temp_morn[i]);\n      Serial.print(\"temp.day         : \"); Serial.println(daily->temp_day[i]);\n      Serial.print(\"temp.eve         : \"); Serial.println(daily->temp_eve[i]);\n      Serial.print(\"temp.night       : \"); Serial.println(daily->temp_night[i]);\n      Serial.print(\"temp.min         : \"); Serial.println(daily->temp_min[i]);\n      Serial.print(\"temp.max         : \"); Serial.println(daily->temp_max[i]);\n\n      Serial.print(\"feels_like.morn  : \"); Serial.println(daily->feels_like_morn[i]);\n      Serial.print(\"feels_like.day   : \"); Serial.println(daily->feels_like_day[i]);\n      Serial.print(\"feels_like.eve   : \"); Serial.println(daily->feels_like_eve[i]);\n      Serial.print(\"feels_like.night : \"); Serial.println(daily->feels_like_night[i]);\n\n      Serial.print(\"pressure         : \"); Serial.println(daily->pressure[i]);\n      Serial.print(\"humidity         : \"); Serial.println(daily->humidity[i]);\n      Serial.print(\"dew_point        : \"); Serial.println(daily->dew_point[i]);\n      Serial.print(\"uvi              : \"); Serial.println(daily->uvi[i]);\n      Serial.print(\"clouds           : \"); Serial.println(daily->clouds[i]);\n      Serial.print(\"visibility       : \"); Serial.println(daily->visibility[i]);\n      Serial.print(\"wind_speed       : \"); Serial.println(daily->wind_speed[i]);\n      Serial.print(\"wind_gust        : \"); Serial.println(daily->wind_gust[i]);\n      Serial.print(\"wind_deg         : \"); Serial.println(daily->wind_deg[i]);\n      Serial.print(\"rain             : \"); Serial.println(daily->rain[i]);\n      Serial.print(\"snow             : \"); Serial.println(daily->snow[i]);\n      Serial.println();\n      Serial.print(\"id               : \"); Serial.println(daily->id[i]);\n      Serial.print(\"main             : \"); Serial.println(daily->main[i]);\n      Serial.print(\"description      : \"); Serial.println(daily->description[i]);\n      Serial.print(\"icon             : \"); Serial.println(daily->icon[i]);\n      Serial.print(\"pop              : \"); Serial.println(daily->pop[i]);\n\n      Serial.println();\n    }\n  }\n\n  // Delete to free up space and prevent fragmentation as strings change in length\n  delete current;\n  delete hourly;\n  delete daily;\n}\n\n/***************************************************************************************\n**                          Convert unix time to a time string\n***************************************************************************************/\nString strTime(time_t unixTime)\n{\n  unixTime += TIME_OFFSET;\n  return ctime(&unixTime);\n}\n"
  },
  {
    "path": "examples/Onecall API (subscription required)/My_OpenWeather_Test/Notes.ino",
    "content": "/*\n\n    [units] should be one of the following:\n\n    metric\n    imperial\n\n    lang=[language] optional\n\n    Return summary properties in the desired language.\n    (Note that units in the summary will be set according to the units parameter,\n    so be sure to set both parameters appropriately.) language may be:\n\n    af: Africans\n    ar: Arabic\n    az: Azerbaijani\n    bg: Bulgarian\n    ca: Catalan\n    cs: Czech\n    da: Danish\n    de: German\n    el: Greek\n    en: English (which is the default)\n    eu: Basque\n    fa: Persian (Farsi)\n    fi: Finnish\n    fr: French\n    gl: Galician\n    he: Hebrew\n    hi: Hindi\n    hr: Croatian\n    hu: Hungarian\n    id: Indonesian\n    it: Italian\n    ja: Japanese\n    kr: Korean\n    la: Latvian\n    lt: Lithuanian\n    mk: Macedonian\n    no: Norwegian Bokmål\n    nl: Dutch\n    pl: Polish\n    pt: Portuguese\n    pt_br: Português Brasil\n    ro: Romanian\n    ru: Russian\n    sk: Slovak\n    sl: Slovenian\n    sp, es: Spanish\n    sr: Serbian\n    sv, se: Swedish\n    th: Thai\n    tr: Turkish\n    ua, uk: Ukrainian\n    vi: Vietnamese\n    zh_cn: Chinese Simplified\n    zh-tw: Chinese Traditional\n    zu: Zulu\n\n*/\n"
  },
  {
    "path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/All_Settings.h",
    "content": "//  Use the OpenWeather library: https://github.com/Bodmer/OpenWeather\n\n//  The weather icons and fonts are in the sketch data folder, press Ctrl+K\n//  to view.\n\n\n//            >>>       IMPORTANT TO PREVENT CRASHES      <<<\n//>>>>>>  Set SPIFFS to at least 1.5Mbytes before uploading files  <<<<<<\n\n\n//                >>>           DON'T FORGET THIS             <<<\n//  Upload the fonts and icons to SPIFFS using the \"Tools\"  \"ESP32 Sketch Data Upload\"\n//  or \"ESP8266 Sketch Data Upload\" menu option in the IDE.\n//  To add this option follow instructions here for the ESP8266:\n//  https://github.com/esp8266/arduino-esp8266fs-plugin\n//  To add this option follow instructions here for the ESP32:\n//  https://github.com/me-no-dev/arduino-esp32fs-plugin\n\n//  Close the IDE and open again to see the new menu option.\n\n// You can change the number of hours and days for the forecast in the\n// \"User_Setup.h\" file inside the OpenWeather library folder.\n// By default this is 6 hours (can be up to 48) and 5 days\n// (can be up to 8 days = today plus 7 days). This sketch requires\n// at least 5 days of forecast. Forecast hours can be set to 1 as\n// the hourly forecast data is not used in this sketch.\n\n//////////////////////////////\n// Setttings defined below\n\n#define WIFI_SSID      \"Your_SSID\"\n#define WIFI_PASSWORD  \"Your_password\"\n\n#define TIMEZONE UK // See NTP_Time.h tab for other \"Zone references\", UK, usMT etc\n\n// Update every 15 minutes, up to 1000 request per day are free (viz average of ~40 per hour)\nconst int UPDATE_INTERVAL_SECS = 15 * 60UL; // 15 minutes\n\n// Pins for the TFT interface are defined in the User_Config.h file inside the TFT_eSPI library\n\n// For units use \"metric\" or \"imperial\"\nconst String units = \"metric\";\n\n// Sign up for a key and read API configuration info here:\n// https://openweathermap.org/, change x's to your API key\nconst String api_key = \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\";\n\n// Set the forecast longitude and latitude to at least 4 decimal places\nconst String latitude =  \"27.9881\"; // 90.0000 to -90.0000 negative for Southern hemisphere\nconst String longitude = \"86.9250\"; // 180.000 to -180.000 negative for West\n\n// For language codes see https://openweathermap.org/current#multi\nconst String language = \"en\"; // Default language = en = English\n\n// Short day of week abbreviations used in 4 day forecast (change to your language)\nconst String shortDOW [8] = {\"???\", \"SUN\", \"MON\", \"TUE\", \"WED\", \"THU\", \"FRI\", \"SAT\"};\n\n// Change the labels to your language here:\nconst char sunStr[]        = \"Sun\";\nconst char cloudStr[]      = \"Cloud\";\nconst char humidityStr[]   = \"Humidity\";\nconst String moonPhase [8] = {\"New\", \"Waxing\", \"1st qtr\", \"Waxing\", \"Full\", \"Waning\", \"Last qtr\", \"Waning\"};\n\n// End of user settings\n//////////////////////////////\n"
  },
  {
    "path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/GfxUi.cpp",
    "content": "/**The MIT License (MIT)\nCopyright (c) 2015 by Daniel Eichhorn\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\nSee more at http://blog.squix.ch\n*/\n\n// Adapted by Bodmer to use the TFT_eSPI library: https://github.com/Bodmer/TFT_eSPI\n// Functions no longer needed weeded out, Jpeg decoder functions added and updated\n// drawBMP() updated to buffer input and output pixels and avoid slow seeks\n\n#include \"GfxUi.h\"\n\nGfxUi::GfxUi(TFT_eSPI *tft) {\n  _tft = tft;\n}\n\nvoid GfxUi::drawProgressBar(uint16_t x0, uint16_t y0, uint16_t w, uint16_t h, uint8_t percentage, uint16_t frameColor, uint16_t barColor) {\n  if (percentage == 0) {\n    _tft->fillRoundRect(x0, y0, w, h, 3, TFT_BLACK);\n  }\n  uint8_t margin = 2;\n  uint16_t barHeight = h - 2 * margin;\n  uint16_t barWidth = w - 2 * margin;\n  _tft->drawRoundRect(x0, y0, w, h, 3, frameColor);\n  _tft->fillRect(x0 + margin, y0 + margin, barWidth * percentage / 100.0, barHeight, barColor);\n}\n\n// Bodmer's streamlined x2 faster \"no seek\" version\nvoid GfxUi::drawBmp(String filename, uint16_t x, uint16_t y)\n{\n\n  if ((x >= _tft->width()) || (y >= _tft->height())) return;\n\n  fs::File bmpFS;\n\n  // Check file exists and open it\n  // Serial.println(filename);\n\n  // Note: ESP32 passes \"open\" test even if file does not exist, whereas ESP8266 returns NULL\n  if ( !SPIFFS.exists(filename) )\n  {\n    Serial.println(F(\" File not found\")); // Can comment out if not needed\n    return;\n  }\n\n  // Open requested file\n  bmpFS = SPIFFS.open(filename, \"r\");\n\n  uint32_t seekOffset;\n  uint16_t w, h, row;\n  uint8_t  r, g, b;\n  bool     oldSwap = false;\n\n  if (read16(bmpFS) == 0x4D42)\n  {\n    read32(bmpFS);\n    read32(bmpFS);\n    seekOffset = read32(bmpFS);\n    read32(bmpFS);\n    w = read32(bmpFS);\n    h = read32(bmpFS);\n\n    if ((read16(bmpFS) == 1) && (read16(bmpFS) == 24) && (read32(bmpFS) == 0))\n    {\n      y += h - 1;\n\n      oldSwap = _tft->getSwapBytes();\n      _tft->setSwapBytes(true);\n      bmpFS.seek(seekOffset);\n\n      // Calculate padding to avoid seek\n      uint16_t padding = (4 - ((w * 3) & 3)) & 3;\n      uint8_t lineBuffer[w * 3 + padding];\n\n      for (row = 0; row < h; row++) {\n        \n        bmpFS.read(lineBuffer, sizeof(lineBuffer));\n        uint8_t*  bptr = lineBuffer;\n        uint16_t* tptr = (uint16_t*)lineBuffer;\n        // Convert 24 to 16 bit colours using the same line buffer for results\n        for (uint16_t col = 0; col < w; col++)\n        {\n          b = *bptr++;\n          g = *bptr++;\n          r = *bptr++;\n          *tptr++ = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);\n        }\n\n        // Push the pixel row to screen, pushImage will crop the line if needed\n        // y is decremented as the BMP image is drawn bottom up\n        _tft->pushImage(x, y--, w, 1, (uint16_t*)lineBuffer);\n      }\n    }\n    else Serial.println(\"BMP format not recognized.\");\n  }\n  _tft->setSwapBytes(oldSwap);\n  bmpFS.close();\n}\n\n// These read 16- and 32-bit types from the SD card file.\n// BMP data is stored little-endian, Arduino is little-endian too.\n// May need to reverse subscript order if porting elsewhere.\n\nuint16_t GfxUi::read16(fs::File &f) {\n  uint16_t result;\n  ((uint8_t *)&result)[0] = f.read(); // LSB\n  ((uint8_t *)&result)[1] = f.read(); // MSB\n  return result;\n}\n\nuint32_t GfxUi::read32(fs::File &f) {\n  uint32_t result;\n  ((uint8_t *)&result)[0] = f.read(); // LSB\n  ((uint8_t *)&result)[1] = f.read();\n  ((uint8_t *)&result)[2] = f.read();\n  ((uint8_t *)&result)[3] = f.read(); // MSB\n  return result;\n}\n\n/*====================================================================================\n  This sketch support functions to render the Jpeg images.\n\n  Created by Bodmer 15th Jan 2017\n  ==================================================================================*/\n\n// Return the minimum of two values a and b\n#define minimum(a,b)     (((a) < (b)) ? (a) : (b))\n\n#define USE_SPI_BUFFER // Comment out to use slower 16 bit pushColor()\n\n//====================================================================================\n//   Opens the image file and prime the Jpeg decoder\n//====================================================================================\nvoid GfxUi::drawJpeg(String filename, int xpos, int ypos) {\n\n  Serial.println(\"===========================\");\n  Serial.print(\"Drawing file: \"); Serial.println(filename);\n  Serial.println(\"===========================\");\n\n  // Open the named file (the Jpeg decoder library will close it after rendering image)\n  fs::File jpegFile = SPIFFS.open( filename, \"r\");    // File handle reference for SPIFFS\n  //  File jpegFile = SD.open( filename, FILE_READ);  // or, file handle reference for SD library\n \n  if ( !jpegFile ) {\n    Serial.print(\"ERROR: File \\\"\"); Serial.print(filename); Serial.println (\"\\\" not found!\");\n    return;\n  }\n\n  // Use one of the three following methods to initialise the decoder:\n  //boolean decoded = JpegDec.decodeFsFile(jpegFile); // Pass a SPIFFS file handle to the decoder,\n  //boolean decoded = JpegDec.decodeSdFile(jpegFile); // or pass the SD file handle to the decoder,\n  boolean decoded = JpegDec.decodeFsFile(filename);  // or pass the filename (leading / distinguishes SPIFFS files)\n                                   // Note: the filename can be a String or character array type\n  if (decoded) {\n    // print information about the image to the serial port\n    jpegInfo();\n\n    // render the image onto the screen at given coordinates\n    jpegRender(xpos, ypos);\n  }\n  else {\n    Serial.println(\"Jpeg file format not supported!\");\n  }\n}\n\n//====================================================================================\n//   Decode and render the Jpeg image onto the TFT screen\n//====================================================================================\nvoid GfxUi::jpegRender(int xpos, int ypos) {\n\n  // retrieve infomration about the image\n  uint16_t  *pImg;\n  int16_t mcu_w = JpegDec.MCUWidth;\n  int16_t mcu_h = JpegDec.MCUHeight;\n  int32_t max_x = JpegDec.width;\n  int32_t max_y = JpegDec.height;\n\n  // Jpeg images are draw as a set of image block (tiles) called Minimum Coding Units (MCUs)\n  // Typically these MCUs are 16x16 pixel blocks\n  // Determine the width and height of the right and bottom edge image blocks\n  uint32_t min_w = minimum(mcu_w, max_x % mcu_w);\n  uint32_t min_h = minimum(mcu_h, max_y % mcu_h);\n\n  // save the current image block size\n  int32_t win_w = mcu_w;\n  int32_t win_h = mcu_h;\n\n  // record the current time so we can measure how long it takes to draw an image\n  uint32_t drawTime = millis();\n\n  // save the coordinate of the right and bottom edges to assist image cropping\n  // to the screen size\n  max_x += xpos;\n  max_y += ypos;\n\n  // read each MCU block until there are no more\n#ifdef USE_SPI_BUFFER\n  while( JpegDec.readSwappedBytes()){ // Swap byte order so the SPI buffer can be used\n#else\n  while ( JpegDec.read()) { // Normal byte order read\n#endif\n    // save a pointer to the image block\n    pImg = JpegDec.pImage;\n\n    // calculate where the image block should be drawn on the screen\n    int32_t mcu_x = JpegDec.MCUx * mcu_w + xpos;\n    int32_t mcu_y = JpegDec.MCUy * mcu_h + ypos;\n\n    // check if the image block size needs to be changed for the right edge\n    if (mcu_x + mcu_w <= max_x) win_w = mcu_w;\n    else win_w = min_w;\n\n    // check if the image block size needs to be changed for the bottom edge\n    if (mcu_y + mcu_h <= max_y) win_h = mcu_h;\n    else win_h = min_h;\n\n    // copy pixels into a contiguous block\n    if (win_w != mcu_w)\n    {\n      uint16_t *cImg;\n      int p = 0;\n      cImg = pImg + win_w;\n      for (int h = 1; h < win_h; h++)\n      {\n        p += mcu_w;\n        for (int w = 0; w < win_w; w++)\n        {\n          *cImg = *(pImg + w + p);\n          cImg++;\n        }\n      }\n    }\n\n    // draw image MCU block only if it will fit on the screen\n    if ( ( mcu_x + win_w) <= _tft->width() && ( mcu_y + win_h) <= _tft->height())\n    {\n      _tft->pushImage(mcu_x, mcu_y, win_w, win_h, pImg);\n    }\n\n    else if ( ( mcu_y + win_h) >= _tft->height()) JpegDec.abort();\n\n  }\n\n  // calculate how long it took to draw the image\n  drawTime = millis() - drawTime; // Calculate the time it took\n\n  // print the results to the serial port\n  Serial.print  (\"Total render time was    : \"); Serial.print(drawTime); Serial.println(\" ms\");\n  Serial.println(\"=====================================\");\n\n}\n\n//====================================================================================\n//   Print information decoded from the Jpeg image\n//====================================================================================\nvoid GfxUi::jpegInfo() {\n\n  Serial.println(\"===============\");\n  Serial.println(\"JPEG image info\");\n  Serial.println(\"===============\");\n  Serial.print  (\"Width      :\"); Serial.println(JpegDec.width);\n  Serial.print  (\"Height     :\"); Serial.println(JpegDec.height);\n  Serial.print  (\"Components :\"); Serial.println(JpegDec.comps);\n  Serial.print  (\"MCU / row  :\"); Serial.println(JpegDec.MCUSPerRow);\n  Serial.print  (\"MCU / col  :\"); Serial.println(JpegDec.MCUSPerCol);\n  Serial.print  (\"Scan type  :\"); Serial.println(JpegDec.scanType);\n  Serial.print  (\"MCU width  :\"); Serial.println(JpegDec.MCUWidth);\n  Serial.print  (\"MCU height :\"); Serial.println(JpegDec.MCUHeight);\n  Serial.println(\"===============\");\n  Serial.println(\"\");\n\n}\n//====================================================================================\n"
  },
  {
    "path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/GfxUi.h",
    "content": "/**The MIT License (MIT)\nCopyright (c) 2015 by Daniel Eichhorn\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\nSee more at http://blog.squix.ch\n*/\n\n// Adapted by Bodmer to use the TFT_eSPI library:\n// https://github.com/Bodmer/TFT_eSPI\n\n\n\n#include <TFT_eSPI.h> // Hardware-specific library\n\n#define FS_NO_GLOBALS // Avoid conflict with SD library File type definition\n#include <FS.h>\n\n// JPEG decoder library\n#include <JPEGDecoder.h>\n\n#ifndef _GFX_UI_H\n#define _GFX_UI_H\n\n// Maximum of 85 for BUFFPIXEL as 3 x this value is stored in an 8 bit variable!\n// 32 is an efficient size for SPIFFS due to SPI hardware pipeline buffer size\n// A larger value of 80 is better for SD cards\n#define BUFFPIXEL 32\n\nclass GfxUi {\n  public:\n    GfxUi(TFT_eSPI * tft);\n    void drawBmp(String filename, uint16_t x, uint16_t y);\n    void drawProgressBar(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t percentage, uint16_t frameColor, uint16_t barColor);\n    void jpegInfo();\n    void drawJpeg(String filename, int xpos, int ypos);\n    void jpegRender(int xpos, int ypos);\n    \n  private:\n    TFT_eSPI * _tft;\n    uint16_t read16(fs::File &f);\n    uint32_t read32(fs::File &f);\n\n};\n\n#endif\n"
  },
  {
    "path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/MoonPhase.ino",
    "content": "/* Moon phase calculation for OpenWeather library*/\n// Adapted by Bodmer from code here:\n// http://www.voidware.com/moon_phase.htm\n\n#include <stdio.h>\n#include <math.h>\n\n#define PI  3.1415926535897932384626433832795\n#define RAD (PI/180.0)\n#define SMALL_FLOAT (1e-12)\n\ndouble Julian(int year, int month, double day)\n{\n  int a, b, c, e;\n  if (month < 3) {\n    year--;\n    month += 12;\n  }\n  if (year > 1582 || (year == 1582 && month > 10) ||\n      (year == 1582 && month == 10 && day > 15)) {\n    a = year / 100;\n    b = 2 - a + a / 4;\n  }\n  c = 365.25 * year;\n  e = 30.6001 * (month + 1);\n  return b + c + e + day + 1720994.5;\n}\n\ndouble sun_position(double j)\n{\n  double n, x, e, l, dl, v;\n  double m2;\n  int i;\n\n  n = 360 / 365.2422 * j;\n  i = n / 360;\n  n = n - i * 360.0;\n  x = n - 3.762863;\n  if (x < 0) x += 360;\n  x *= RAD;\n  e = x;\n  do {\n    dl = e - .016718 * sin(e) - x;\n    e = e - dl / (1 - .016718 * cos(e));\n  } while (fabs(dl) >= SMALL_FLOAT);\n  v = 360 / PI * atan(1.01686011182 * tan(e / 2));\n  l = v + 282.596403;\n  i = l / 360;\n  l = l - i * 360.0;\n  return l;\n}\n\ndouble moon_position(double j, double ls)\n{\n  double ms, l, mm, n, ev, sms, z, x, lm, bm, ae, ec;\n  double d;\n  int i;\n\n  /* ls = sun_position(j) */\n  ms = 0.985647332099 * j - 3.762863;\n  if (ms < 0) ms += 360.0;\n  l = 13.176396 * j + 64.975464;\n  i = l / 360;\n  l = l - i * 360.0;\n  if (l < 0) l += 360.0;\n  mm = l - 0.1114041 * j - 349.383063;\n  i = mm / 360;\n  mm -= i * 360.0;\n  n = 151.950429 - 0.0529539 * j;\n  i = n / 360;\n  n -= i * 360.0;\n  ev = 1.2739 * sin((2 * (l - ls) - mm) * RAD);\n  sms = sin(ms * RAD);\n  ae = 0.1858 * sms;\n  mm += ev - ae - 0.37 * sms;\n  ec = 6.2886 * sin(mm * RAD);\n  l += ev + ec - ae + 0.214 * sin(2 * mm * RAD);\n  l = 0.6583 * sin(2 * (l - ls) * RAD) + l;\n  return l;\n}\n\nuint8_t moon_phase(int year, int month, int day, double hour, int* ip)\n{\n  double j = Julian(year, month, (double)day + hour / 24.0) - 2444238.5;\n  double ls = sun_position(j);\n  double lm = moon_position(j, ls);\n\n  double t = lm - ls;\n  if (t < 0) t += 360;\n\n  *ip = (int)((t + 22.5)/45) & 0x7;        // Moon state 0-7 for moonPhase[] index\n  return ((int)((t + 7.5)/15) + 23) % 24;  // Moon state 0-23 for icon bitmap\n\n  //return 100.0 * ((1.0 - cos((lm - ls) * RAD)) / 2) + 0.5; // percent illuminated\n}\n"
  },
  {
    "path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/NTP_Time.h",
    "content": "//====================================================================================\n//                                  Libraries\n//====================================================================================\n\n// Time library:\n// https://github.com/PaulStoffregen/Time\n#include <Time.h>\n\n// Time zone correction library:\n// https://github.com/JChristensen/Timezone\n#include <Timezone.h>\n\n// Libraries built into IDE\n#ifdef ESP8266\n  #include <ESP8266WiFi.h>\n#else\n  #include <WiFi.h>\n#endif\n\n#include <WiFiUdp.h>\n\n// A UDP instance to let us send and receive packets over UDP\nWiFiUDP udp;\n\n//====================================================================================\n//                                  Settings\n//====================================================================================\n\n#ifdef ESP32 // Temporary fix, ESP8266 fails to communicate with some servers...\n// Try to use pool url instead so the server IP address is looked up from those available\n// (use a pool server in your own country to improve response time and reliability)\n//const char* ntpServerName = \"time.nist.gov\";\n//const char* ntpServerName = \"pool.ntp.org\";\nconst char* ntpServerName = \"time.google.com\";\n#else\n// Try to use pool url instead so the server IP address is looked up from those available\n// (use a pool server in your own country to improve response time and reliability)\n// const char* ntpServerName = \"time.nist.gov\";\nconst char* ntpServerName = \"pool.ntp.org\";\n//const char* ntpServerName = \"time.google.com\";\n#endif\n\n// Try not to use hard-coded IP addresses which might change, you can if you want though...\n//IPAddress timeServerIP(129, 6, 15, 30);   // time-c.nist.gov NTP server\n//IPAddress timeServerIP(24, 56, 178, 140); // wwv.nist.gov NTP server\nIPAddress timeServerIP;                     // Use server pool\n\n// Example time zone and DST rules, see Timezone library documents to see how\n// to add more time zones https://github.com/JChristensen/Timezone\n\n// Zone reference \"UK\" United Kingdom (London, Belfast)\nTimeChangeRule BST = {\"BST\", Last, Sun, Mar, 1, 60};        //British Summer (Daylight saving) Time\nTimeChangeRule GMT = {\"GMT\", Last, Sun, Oct, 2, 0};         //Standard Time\nTimezone UK(BST, GMT);\n\n// Zone reference \"euCET\" Central European Time (Frankfurt, Paris)\nTimeChangeRule CEST = {\"CEST\", Last, Sun, Mar, 2, 120};     //Central European Summer Time\nTimeChangeRule  CET = {\"CET \", Last, Sun, Oct, 3, 60};      //Central European Standard Time\nTimezone euCET(CEST, CET);\n\n// Zone reference \"ausET\" Australia Eastern Time Zone (Sydney, Melbourne)\nTimeChangeRule aEDT = {\"AEDT\", First, Sun, Oct, 2, 660};    //UTC + 11 hours\nTimeChangeRule aEST = {\"AEST\", First, Sun, Apr, 3, 600};    //UTC + 10 hours\nTimezone ausET(aEDT, aEST);\n\n// Zone reference \"usET US Eastern Time Zone (New York, Detroit)\nTimeChangeRule usEDT = {\"EDT\", Second, Sun, Mar, 2, -240};  //Eastern Daylight Time = UTC - 4 hours\nTimeChangeRule usEST = {\"EST\", First, Sun, Nov, 2, -300};   //Eastern Standard Time = UTC - 5 hours\nTimezone usET(usEDT, usEST);\n\n// Zone reference \"usCT\" US Central Time Zone (Chicago, Houston)\nTimeChangeRule usCDT = {\"CDT\", Second, dowSunday, Mar, 2, -300};\nTimeChangeRule usCST = {\"CST\", First, dowSunday, Nov, 2, -360};\nTimezone usCT(usCDT, usCST);\n\n// Zone reference \"usMT\" US Mountain Time Zone (Denver, Salt Lake City)\nTimeChangeRule usMDT = {\"MDT\", Second, dowSunday, Mar, 2, -360};\nTimeChangeRule usMST = {\"MST\", First, dowSunday, Nov, 2, -420};\nTimezone usMT(usMDT, usMST);\n\n// Zone reference \"usAZ\" Arizona is US Mountain Time Zone but does not use DST\nTimezone usAZ(usMST, usMST);\n\n// Zone reference \"usPT\" US Pacific Time Zone (Las Vegas, Los Angeles)\nTimeChangeRule usPDT = {\"PDT\", Second, dowSunday, Mar, 2, -420};\nTimeChangeRule usPST = {\"PST\", First, dowSunday, Nov, 2, -480};\nTimezone usPT(usPDT, usPST);\n\n\n//====================================================================================\n//                                  Variables\n//====================================================================================\nTimeChangeRule *tz1_Code;   // Pointer to the time change rule, use to get the TZ abbrev, e.g. \"GMT\"\n\ntime_t utc = 0;\n\nbool timeValid = false;\n\nunsigned int localPort = 2390;      // local port to listen for UDP packets\n\nconst int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message\n\nbyte packetBuffer[ NTP_PACKET_SIZE ]; //buffer to hold incoming and outgoing packets\n\nuint8_t lastMinute = 0;\n\nuint32_t nextSendTime = 0;\nuint32_t newRecvTime = 0;\nuint32_t lastRecvTime = 0;\n\nuint32_t newTickTime = 0;\nuint32_t lastTickTime = 0;\n\nbool rebooted = 1;\n\nuint32_t no_packet_count = 0;\n\n\n//====================================================================================\n//                                    Function prototype\n//====================================================================================\n\nvoid syncTime(void);\nvoid displayTime(void);\nvoid printTime(time_t zone, char *tzCode);\nvoid decodeNTP(void);\nvoid sendNTPpacket(IPAddress& address);\n\n//====================================================================================\n//                                    Update Time\n//====================================================================================\nvoid syncTime(void)\n{\n  // Don't send too often so we don't trigger Denial of Service\n  if (nextSendTime < millis()) {\n    // Get a random server from the pool\n    WiFi.hostByName(ntpServerName, timeServerIP);\n    nextSendTime = millis() + 5000;\n\n    // Flush old late packets\n    while  (udp.parsePacket() > 0)  {                // Is a packet there?\n      Serial.println(\"Reading delayed NTP packet.\"); // Yes\n      udp.read(packetBuffer, NTP_PACKET_SIZE);       // read the packet into the buffer\n    }\n\n    sendNTPpacket(timeServerIP); // send an NTP packet to a time server\n    decodeNTP();\n  }\n}\n\n//====================================================================================\n// Send an NTP request to the time server at the given address\n//====================================================================================\nvoid sendNTPpacket(IPAddress& address)\n{\n  // Serial.println(\"sending NTP packet...\");\n  // set all bytes in the buffer to 0\n  memset(packetBuffer, 0, NTP_PACKET_SIZE);\n  // Initialize values needed to form NTP request\n  // (see URL above for details on the packets)\n  packetBuffer[0] = 0b11100011;   // LI, Version, Mode\n  packetBuffer[1] = 0;            // Stratum, or type of clock\n  packetBuffer[2] = 6;            // Polling Interval\n  packetBuffer[3] = 0xEC;         // Peer Clock Precision\n\n  // 8 bytes of zero for Root Delay & Root Dispersion\n\n  packetBuffer[12]  = 49;\n  packetBuffer[13]  = 0x4E;\n  packetBuffer[14]  = 49;\n  packetBuffer[15]  = 52;\n\n  // all NTP fields have been given values, now\n  // you can send a packet requesting a timestamp:\n  udp.beginPacket(address, 123); //NTP requests are to port 123\n  udp.write(packetBuffer, NTP_PACKET_SIZE);\n  udp.endPacket();\n}\n\n//====================================================================================\n// Decode the NTP message and print status to serial port\n//====================================================================================\nvoid decodeNTP(void)\n{\n  timeValid = false;\n  uint32_t waitTime = millis() + 500;\n  while (millis() < waitTime && !timeValid)\n  {\n    yield();\n    if (udp.parsePacket())\n    {\n      newRecvTime = millis();\n\n      // We've received a packet, read the data from it\n      udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer\n\n      Serial.print(\"\\nNTP response time was : \");\n      Serial.print(500 - (waitTime - newRecvTime));\n      Serial.println(\" ms\");\n\n      Serial.print(\"Time since last sync is: \");\n      Serial.print((newRecvTime - lastRecvTime) / 1000.0);\n      Serial.println(\" s\");\n      lastRecvTime = newRecvTime;\n\n      // The timestamp starts at byte 40 of the received packet and is four bytes,\n      // or two words, long. First, extract the two words:\n      unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);\n      unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);\n\n      // Combine the four bytes (two words) into a long integer\n      // this is NTP time (seconds since Jan 1 1900):\n      unsigned long secsSince1900 = highWord << 16 | lowWord;\n\n      // Now convert NTP Unix time (Seconds since Jan 1 1900) into everyday time:\n      // UTC time starts on Jan 1 1970. In seconds the difference is 2208988800:\n      utc = secsSince1900 - 2208988800UL;\n\n      setTime(utc);      // Set system clock to utc time (not time zone compensated)\n\n      timeValid = true;\n\n      // Print the hour, minute and second:\n      Serial.print(\"Received NTP UTC time : \");\n\n      uint8_t hh = hour(utc);\n      Serial.print(hh); // print the hour (86400 equals secs per day)\n\n      Serial.print(':');\n      uint8_t mm = minute(utc);\n      if (mm < 10 ) Serial.print('0');\n      Serial.print(mm); // print the minute (3600 equals secs per minute)\n\n      Serial.print(':');\n      uint8_t ss = second(utc);\n      if ( ss < 10 ) Serial.print('0');\n      Serial.println(ss); // print the second\n    }\n  }\n\n  // Keep a count of missing or bad NTP replies\n\n  if ( timeValid ) {\n    no_packet_count = 0;\n  }\n  else\n  {\n    Serial.println(\"\\nNo NTP reply, trying again in 1 minute...\");\n    no_packet_count++;\n  }\n\n  if (no_packet_count >= 10) {\n    no_packet_count = 0;\n    // TODO: Flag the lack of sync on the display\n    Serial.println(\"\\nNo NTP packet in last 10 minutes\");\n  }\n}\n\n//====================================================================================\n//                                  Debug use only\n//====================================================================================\nvoid printTime(time_t t, char *tzCode)\n{\n  String dateString = dayStr(weekday(t));\n  dateString += \" \";\n  dateString += day(t);\n  if (day(t) == 1 || day(t) == 21 || day(t) == 31) dateString += \"st\";\n  else if (day(t) == 2 || day(t) == 22) dateString += \"nd\";\n  else if (day(t) == 3 || day(t) == 23) dateString += \"rd\";\n  else dateString += \"th\";\n\n  dateString += \" \";\n  dateString += monthStr(month(t));\n  dateString += \" \";\n  dateString += year(t);\n\n  // Print time to serial port\n  Serial.print(hour(t));\n  Serial.print(\":\");\n  Serial.print(minute(t));\n  Serial.print(\":\");\n  Serial.print(second(t));\n  Serial.print(\" \");\n  // Print time t\n  Serial.print(tzCode);\n  Serial.print(\" \");\n\n  // Print date\n  Serial.print(day(t));\n  Serial.print(\"/\");\n  Serial.print(month(t));\n  Serial.print(\"/\");\n  Serial.print(year(t));\n  Serial.print(\"  \");\n\n  // Now test some other functions that might be useful one day!\n  Serial.print(dayStr(weekday(t)));\n  Serial.print(\" \");\n  Serial.print(monthStr(month(t)));\n  Serial.print(\" \");\n  Serial.print(dayShortStr(weekday(t)));\n  Serial.print(\" \");\n  Serial.print(monthShortStr(month(t)));\n  Serial.println();\n}\n\n//====================================================================================\n"
  },
  {
    "path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/SPIFFS_Support.h",
    "content": "/*====================================================================================\n  This sketch contains support functions for the ESP6266 SPIFFS filing system\n\n  Created by Bodmer 15th Jan 2017\n  ==================================================================================*/\nvoid listDir(fs::FS &fs, const char * dirname, uint8_t levels);\n\n// -------------------------------------------------------------------------\n// List SPIFFS files in a neat format for ESP8266 or ESP32\n// -------------------------------------------------------------------------\nvoid listFiles(void) {\n  Serial.println();\n  Serial.println(\"SPIFFS files found:\");\n\n#ifdef ESP32\n  listDir(SPIFFS, \"/\", true);\n#else\n  fs::Dir dir = SPIFFS.openDir(\"/\"); // Root directory\n  String  line = \"=====================================\";\n\n  Serial.println(line);\n  Serial.println(\"  File name               Size\");\n  Serial.println(line);\n\n  while (dir.next()) {\n    String fileName = dir.fileName();\n    Serial.print(fileName);\n    // File path can be 31 characters maximum in SPIFFS\n    int spaces = 33 - fileName.length(); // Tabulate nicely\n    if (spaces < 1) spaces = 1;\n    while (spaces--) Serial.print(\" \");\n    fs::File f = dir.openFile(\"r\");\n    Serial.print(f.size()); Serial.println(\" bytes\");\n    yield();\n  }\n\n  Serial.println(line);\n#endif\n  Serial.println();\n  delay(1000);\n}\n\n#ifdef ESP32\nvoid listDir(fs::FS &fs, const char * dirname, uint8_t levels) {\n  Serial.printf(\"Listing directory: %s\\n\", dirname);\n\n  fs::File root = fs.open(dirname);\n  if (!root) {\n    Serial.println(\"Failed to open directory\");\n    return;\n  }\n  if (!root.isDirectory()) {\n    Serial.println(\"Not a directory\");\n    return;\n  }\n\n  fs::File file = root.openNextFile();\n  while (file) {\n\n    if (file.isDirectory()) {\n      Serial.print(\"DIR : \");\n      String fileName = file.name();\n      Serial.print(fileName);\n      if (levels) {\n        listDir(fs, file.name(), levels - 1);\n      }\n    } else {\n      String fileName = file.name();\n      Serial.print(\"  \" + fileName);\n      // File path can be 31 characters maximum in SPIFFS\n      int spaces = 33 - fileName.length(); // Tabulate nicely\n      if (spaces < 1) spaces = 1;\n      while (spaces--) Serial.print(\" \");\n      String fileSize = (String) file.size();\n      spaces = 8 - fileSize.length(); // Tabulate nicely\n      if (spaces < 1) spaces = 1;\n      while (spaces--) Serial.print(\" \");\n      Serial.println(fileSize + \" bytes\");\n    }\n\n    file = root.openNextFile();\n  }\n}\n#endif\n//====================================================================================\n"
  },
  {
    "path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/ScreenGrabClient.ino",
    "content": "// This is a copy of the processing sketch that can be used to capture the images\n// Not needed by this sketch, used during development with screenSaver() functions.\n\n// Copy the sketch below into the Processing IDE and remove the /* and */ at the beginning and\n// end.\n\n// The sketch runs in Processing version 3.3 (or later) on a PC, it can be downloaded here:\n// https://processing.org/download/\n\n/*\n\n// This is a Processing sketch, see https://processing.org/ to download the IDE\n\n// The sketch is a client that requests TFT screen-shots from an Arduino board.\n// The Arduino must call a screen-shot server function to respond with pixels.\n\n// It has been created to work with the TFT_eSPI library here:\n// https://github.com/Bodmer/TFT_eSPI\n\n// The sketch must only be run when the designated serial port is available and enumerated\n// otherwise the screen-shot window may freeze and that process will need to be terminated\n// This is a limitation of the Processing environment and not the sketch.\n// If anyone knows how to determine if a serial port is available at start up the PM me\n// on (Bodmer) the Arduino forum.\n\n// The block below contains variables that the user may need to change for a particular setup\n// As a minimum set the serial port and baud rate must be defined. The capture window is\n// automatically resized for landscape, portrait and different TFT resolutions.\n\n// Captured images are stored in the sketch folder, use the Processing IDE \"Sketch\" menu\n// option \"Show Sketch Folder\" or press Ctrl+K\n\n// Created by: Bodmer  5/3/17\n// Updated by: Bodmer 12/3/17\n// Version: 0.07\n\n// MIT licence applies, all text above must be included in derivative works\n\n\n// ###########################################################################################\n// #                  These are the values to change for a particular setup                  #\n//                                                                                           #\nint serial_port = 0;     // Use enumerated value from list provided when sketch is run       #\n//                                                                                           #\n// On an Arduino Due Programming Port use a baud rate of:115200)                             #\n// On an Arduino Due Native USB Port use a baud rate of any value                            #\nint serial_baud_rate = 250000; //                                                            #\n//                                                                                           #\n// Change the image file type saved here, comment out all but one                            #\n//String image_type = \".jpg\"; //                                                             #\nString image_type = \".png\";   // Lossless compression                                        #\n//String image_type = \".bmp\"; //                                                             #\n//String image_type = \".tif\"; //                                                             #\n//                                                                                           #\nboolean save_border = true;   // Save the image with a border                                #\nint border = 5;               // Border pixel width                                          #\nboolean fade = false;         // Fade out image after saving                                 #\n//                                                                                           #\nint max_images = 100; // Maximum of numbered file images before over-writing files           #\n//                                                                                           #\nint max_allowed  = 1000; // Maximum number of save images allowed before a restart           #\n//                                                                                           #\n// #                   End of the values to change for a particular setup                    #\n// ###########################################################################################\n\n// These are default values, this sketch obtains the actual values from the Arduino board\nint tft_width  = 480;    // default TFT width  (automatic - sent by Arduino)\nint tft_height = 480;    // default TFT height (automatic - sent by Arduino)\nint color_bytes = 2;     // 2 for 16 bit, 3 for three RGB bytes (automatic - sent by Arduino)\n\nimport processing.serial.*;\n\nSerial serial;           // Create an instance called serial\n\nint serialCount = 0;     // Count of colour bytes arriving\n\n// Stage window graded background colours\ncolor bgcolor1 = color(0, 100, 104);      // Arduino IDE style background color 1\ncolor bgcolor2 = color(77, 183, 187);     // Arduino IDE style background color 2\n//color bgcolor2 = color(255, 255, 255);  // White\n\n// TFT image frame greyscale value (dark grey)\ncolor frameColor = 42;\n\ncolor buttonStopped = color(255, 0, 0);\ncolor buttonRunning = color(128, 204, 206);\ncolor buttonDimmed  = color(180, 0, 0);\nboolean dimmed   = false;\nboolean running  = true;\nboolean mouseClick = false;\n\nint[] rgb = new int[3]; // Buffer for the colour bytes\nint indexRed   = 0;     // Colour byte index in the array\nint indexGreen = 1;\nint indexBlue  = 2;\n\nint n = 0;\n\nint x_offset = (500 - tft_width) /2; // Image offsets in the window\nint y_offset = 20;\n\nint xpos = 0, ypos = 0; // Current pixel position\n\nint beginTime     = 0;\nint pixelWaitTime = 1000;  // Maximum 1000ms wait for image pixels to arrive\nint lastPixelTime = 0;     // Time that \"image send\" command was sent\n\nint requestTime = 0;\nint requestCount = 0;\n\nint state = 0;  // State machine current state\n\nint   progress_bar = 0; // Console progress bar dot count\nint   pixel_count  = 0; // Number of pixels read for 1 screen\nfloat percentage   = 0; // Percentage of pixels received\n\nint  saved_image_count = 0; // Stats - number of images processed\nint  bad_image_count  = 0;  // Stats - number of images that had lost pixels\nString filename = \"\";\n\nint drawLoopCount = 0;      // Used for the fade out\n\nvoid setup() {\n\n  size(500, 540);  // Stage size, can handle 480 pixels wide screen\n  noStroke();      // No border on the next thing drawn\n  noSmooth();      // No anti-aliasing to avoid adjacent pixel colour merging\n\n  // Graded background and title\n  drawWindow();\n\n  frameRate(2000); // High frame rate so draw() loops fast\n\n  // Print a list of the available serial ports\n  println(\"-----------------------\");\n  println(\"Available Serial Ports:\");\n  println(\"-----------------------\");\n  printArray(Serial.list());\n  println(\"-----------------------\");\n\n  print(\"Port currently used: [\");\n  print(serial_port);\n  println(\"]\");\n\n  String portName = Serial.list()[serial_port];\n\n  serial = new Serial(this, portName, serial_baud_rate);\n\n  state = 99;\n}\n\nvoid draw() {\n\n  if (mouseClick) buttonClicked();\n\n  switch(state) {\n\n  case 0: // Init varaibles, send start request\n    if (running) {\n      tint(0, 0, 0, 255);\n      flushBuffer();\n      println(\"\");\n      print(\"Ready: \");\n\n      xpos = 0;\n      ypos = 0;\n      serialCount = 0;\n      progress_bar = 0;\n      pixel_count = 0;\n      percentage   = 0;\n      drawLoopCount = frameCount;\n      lastPixelTime = millis() + 1000;\n\n      state = 1;\n    } else {\n      if (millis() > beginTime) {\n        beginTime = millis() + 500;\n        dimmed = !dimmed;\n        if (dimmed) drawButton(buttonDimmed);\n        else drawButton(buttonStopped);\n      }\n    }\n    break;\n\n  case 1: // Console message, give server some time\n    print(\"requesting image \");\n    serial.write(\"S\");\n    delay(10);\n    beginTime = millis();\n    requestTime = millis() + 1000;\n    requestCount = 1;\n    state = 2;\n    break;\n\n  case 2: // Get size and set start time for rendering duration report\n    if (millis() > requestTime) {\n      requestCount++;\n      print(\"*\");\n      serial.clear();\n      serial.write(\"S\");\n      if (requestCount > 32) {\n        requestCount = 0;\n        System.err.println(\" - no response!\");\n        state = 0;\n      }\n      requestTime = millis() + 1000;\n    }\n    if ( getSize() == true ) { // Go to next state when we have the size and bits per pixel\n      getFilename();\n      flushBuffer(); // Precaution in case image header size increases in later versions\n      lastPixelTime = millis() + 1000;\n      beginTime = millis();\n      state = 3;\n    }\n    break;\n\n  case 3: // Request pixels and render returned RGB values\n    state = renderPixels(); // State will change when all pixels are rendered\n\n    // Request more pixels, changing the number requested allows the average transfer rate to\n    // be controlled. The pixel transfer rate is dependant on four things:\n    //    1. The frame rate defined in this Processing sketch in setup()\n    //    2. The baud rate of the serial link (~10 bit periods per byte)\n    //    3. The number of request bytes 'R' sent in the lines below\n    //    4. The number of pixels sent in a burst by the server sketch (defined via NPIXELS)\n\n    //serial.write(\"RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR\"); // 32 x NPIXELS more\n    serial.write(\"RRRRRRRRRRRRRRRR\"); // 16 x NPIXELS more\n    //serial.write(\"RRRRRRRR\"); // 8 x NPIXELS more\n    //serial.write(\"RRRR\"); // 4 x NPIXELS more\n    //serial.write(\"RR\"); // 2 x NPIXELS more\n    //serial.write(\"R\"); // 1 x NPIXELS more\n    if (!running) state = 4;\n    break;\n\n  case 4: // Pixel receive time-out, flush serial buffer\n    flushBuffer();\n    state = 6;\n    break;\n\n  case 5: // Save the image to the sketch folder (Ctrl+K to access)\n    saveScreenshot();\n    saved_image_count++;\n    println(\"Saved image count = \" + saved_image_count);\n    if (bad_image_count > 0) System.err.println(\" Bad image count = \" + bad_image_count);\n    drawLoopCount = frameCount; // Reset value ready for counting in step 6\n    state = 6;\n    break;\n\n  case 6: // Fade the old image if enabled\n    if ( fadedImage() == true ) state = 0; // Go to next state when image has faded\n    break;\n\n  case 99: // Draw image viewer window\n    drawWindow();\n    delay(50); // Delay here seems to be required for the IDE console to get ready\n    state = 0;\n    break;\n\n  default:\n    println(\"\");\n    System.err.println(\"Error state reached - check sketch!\");\n    break;\n  }\n}\n\nvoid drawWindow()\n{\n  // Graded background in Arduino colours\n  for (int i = 0; i < height - 25; i++) {\n    float inter = map(i, 0, height - 25, 0, 1);\n    color c = lerpColor(bgcolor1, bgcolor2, inter);\n    stroke(c);\n    line(0, i, 500, i);\n  }\n  fill(bgcolor2);\n  rect( 0, height-25, width-1, 24);\n  textAlign(CENTER);\n  textSize(20);\n  fill(0);\n  text(\"Bodmer's TFT image viewer\", width/2, height-6);\n\n  if (running) drawButton(buttonRunning);\n  else drawButton(buttonStopped);\n}\n\nvoid flushBuffer()\n{\n  //println(\"Clearing serial pipe after a time-out\");\n  int clearTime = millis() + 50;\n  while ( millis() < clearTime ) serial.clear();\n}\n\nboolean getSize()\n{\n  if ( serial.available() > 6 ) {\n    println();\n    char code = (char)serial.read();\n    if (code == 'W') {\n      tft_width = serial.read()<<8 | serial.read();\n    }\n    code = (char)serial.read();\n    if (code == 'H') {\n      tft_height = serial.read()<<8 | serial.read();\n    }\n    code = (char)serial.read();\n    if (code == 'Y') {\n      int bits_per_pixel = (char)serial.read();\n      if (bits_per_pixel == 24) color_bytes = 3;\n      else color_bytes = 2;\n    }\n    code = (char)serial.read();\n    if (code == '?') {\n      drawWindow();\n\n      x_offset = (500 - tft_width) /2;\n      tint(0, 0, 0, 255);\n      noStroke();\n      fill(frameColor);\n      rect((width - tft_width)/2 - border, y_offset - border, tft_width + 2 * border, tft_height + 2 * border);\n      return true;\n    }\n  }\n  return false;\n}\n\nvoid saveScreenshot()\n{\n  println();\n  if (saved_image_count < max_allowed)\n  {\n  if (filename == \"\") filename = \"tft_screen_\" + (n++);\n  filename = filename  + image_type;\n  println(\"Saving image as \\\"\" + filename + \"\\\"\");\n  if (save_border)\n  {\n    PImage partialSave = get(x_offset - border, y_offset - border, tft_width + 2*border, tft_height + 2*border);\n    partialSave.save(filename);\n  } else {\n    PImage partialSave = get(x_offset, y_offset, tft_width, tft_height);\n    partialSave.save(filename);\n  }\n\n  if (n>=max_images) n = 0;\n  }\n  else\n  {\n    System.err.println(max_allowed + \" saved image count exceeded, restart the sketch\");\n  }\n}\n\nvoid getFilename()\n{\n  int readTime = millis() + 20;\n  int inByte = 0;\n  filename = \"\";\n  while ( serial.available() > 0 && millis() < readTime && inByte != '.')\n  {\n    inByte = serial.read();\n    if (inByte == ' ') inByte = '_';\n    if ( unicodeCheck(inByte) ) filename += (char)inByte;\n  }\n\n  inByte = serial.read();\n       if (inByte == '@') filename += \"_\" + timeCode();\n  else if (inByte == '#') filename += \"_\" + saved_image_count%100;\n  else if (inByte == '%') filename += \"_\" + millis();\n  else if (inByte != '*') filename  = \"\";\n\n  inByte = serial.read();\n       if (inByte == 'j') image_type =\".jpg\";\n  else if (inByte == 'b') image_type =\".bmp\";\n  else if (inByte == 'p') image_type =\".png\";\n  else if (inByte == 't') image_type =\".tif\";\n}\n\nboolean unicodeCheck(int unicode)\n{\n  if (  unicode >= '0' && unicode <= '9' ) return true;\n  if ( (unicode >= 'A' && unicode <= 'Z' ) || (unicode >= 'a' && unicode <= 'z')) return true;\n  if (  unicode == '_' || unicode == '/' ) return true;\n  return false;\n}\n\nString timeCode()\n{\n String timeCode  = (int)year() + \"_\" + (int)month()  + \"_\" + (int)day() + \"_\";\n        timeCode += (int)hour() + \"_\" + (int)minute() + \"_\" + (int)second(); \n return timeCode;\n}\n\nint renderPixels()\n{\n  if ( serial.available() > 0 ) {\n\n    // Add the latest byte from the serial port to array:\n    while (serial.available()>0)\n    {\n      rgb[serialCount++] = serial.read();\n\n      // If we have 3 colour bytes:\n      if ( serialCount >= color_bytes ) {\n        serialCount = 0;\n        pixel_count++;\n        if (color_bytes == 3)\n        {\n          stroke(rgb[indexRed], rgb[indexGreen], rgb[indexBlue], 1000);\n        } else\n        { // Can cater for various byte orders\n          //stroke( (rgb[0] & 0x1F)<<3, (rgb[0] & 0xE0)>>3 | (rgb[1] & 0x07)<<5, (rgb[1] & 0xF8));\n          //stroke( (rgb[1] & 0x1F)<<3, (rgb[1] & 0xE0)>>3 | (rgb[0] & 0x07)<<5, (rgb[0] & 0xF8));\n          stroke( (rgb[0] & 0xF8), (rgb[1] & 0xE0)>>3 | (rgb[0] & 0x07)<<5, (rgb[1] & 0x1F)<<3);\n          //stroke( (rgb[1] & 0xF8), (rgb[0] & 0xE0)>>3 | (rgb[1] & 0x07)<<5, (rgb[0] & 0x1F)<<3);\n        }\n        // We get some pixel merge aliasing if smooth() is defined, so draw pixel twice\n        point(xpos + x_offset, ypos + y_offset);\n        //point(xpos + x_offset, ypos + y_offset);\n\n        lastPixelTime = millis();\n        xpos++;\n        if (xpos >= tft_width) {\n          xpos = 0; \n          progressBar();\n          ypos++;\n          if (ypos>=tft_height) {\n            ypos = 0;\n            if ((int)percentage <100) {\n              while (progress_bar++ < 64) print(\" \");\n              percent(100);\n            }\n            println(\"Image fetch time = \" + (millis()-beginTime)/1000.0 + \" s\");\n            return 5;\n          }\n        }\n      }\n    }\n  } else\n  {\n    if (millis() > (lastPixelTime + pixelWaitTime))\n    {\n      println(\"\");\n      System.err.println(pixelWaitTime + \"ms time-out for pixels exceeded...\");\n      if (pixel_count > 0) {\n        bad_image_count++;\n        System.err.print(\"Pixels missing = \" + (tft_width * tft_height - pixel_count));\n        System.err.println(\", corrupted image not saved\");\n        System.err.println(\"Good image count = \" + saved_image_count);\n        System.err.println(\" Bad image count = \" + bad_image_count);\n      }\n      return 4;\n    }\n  }\n  return 3;\n}\n\nvoid progressBar()\n{\n  progress_bar++;\n  print(\".\");\n  if (progress_bar >63)\n  {\n    progress_bar = 0;\n    percentage = 0.5 + 100 * pixel_count/(0.001 + tft_width * tft_height);\n    percent(percentage);\n  }\n}\n\nvoid percent(float percentage)\n{\n  if (percentage > 100) percentage = 100;\n  println(\" [ \" + (int)percentage + \"% ]\");\n  textAlign(LEFT);\n  textSize(16);\n  noStroke();\n  fill(bgcolor2);\n  rect(10, height - 25, 70, 20);\n  fill(0);\n  text(\" [ \" + (int)percentage + \"% ]\", 10, height-8);\n}\n\nboolean fadedImage()\n{\n  int opacity = frameCount - drawLoopCount;  // So we get increasing fade\n  if (fade)\n  {\n    tint(255, opacity);\n    //image(tft_img, x_offset, y_offset);\n    noStroke();\n    fill(50, 50, 50, opacity);\n    rect( (width - tft_width)/2, y_offset, tft_width, tft_height);\n    delay(10);\n  }\n  if (opacity > 50)       // End fade after 50 cycles\n  {\n    return true;\n  }\n  return false;\n}\n\nvoid drawButton(color buttonColor)\n{\n  stroke(0);\n  fill(buttonColor);\n  rect(500 - 100, 540 - 26, 80, 24);\n  textAlign(CENTER);\n  textSize(20);\n  fill(0);\n  if (running) text(\" Pause \", 500 - 60, height-7);\n  else text(\" Run \", 500 - 60, height-7);\n}\n\nvoid buttonClicked()\n{\n  mouseClick = false;\n  if (running) {\n    running = false;\n    drawButton(buttonStopped);\n    System.err.println(\"\");\n    System.err.println(\"Stopped - click 'Run' button: \");\n    //noStroke();\n    //fill(50);\n    //rect( (width - tft_width)/2, y_offset, tft_width, tft_height);\n    beginTime = millis() + 500;\n    dimmed = false;\n    state = 4;\n  } else {\n    running = true;\n    drawButton(buttonRunning);\n  }\n}\n\nvoid mousePressed() {\n  if (mouseX > (500 - 100) && mouseX < (500 - 20) && mouseY > (540 - 26) && mouseY < (540 - 2)) {\n    mouseClick = true;\n  }\n}\n\n*/\n"
  },
  {
    "path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/ScreenGrabServer.ino",
    "content": "// Reads a screen image off the TFT and send it to a processing client sketch\n// over the serial port. Use a high baud rate, e.g. for an ESP8266:\n// Serial.begin(921600);\n\n// ONLY works if TFT CGRAM can be read back, not all displays support this!\n// Tested with ILI9341 display\n\n// At 921600 baud a 320 x 240 image with 16 bit colour transfers can be sent to the\n// PC client in ~1.67s and 24 bit colour in ~2.5s which is close to the theoretical\n// minimum transfer time.\n\n// This sketch has been created to work with the TFT_eSPI library here:\n// https://github.com/Bodmer/TFT_eSPI\n\n// Created by: Bodmer 27/1/17\n// Updated by: Bodmer 10/3/17\n// Version: 0.07\n\n// MIT licence applies, all text above must be included in derivative works\n\n//====================================================================================\n//                                  Definitions\n//====================================================================================\n\n#define PIXEL_TIMEOUT 100     // 100ms Time-out between pixel requests\n#define START_TIMEOUT 10000   // 10s Maximum time to wait at start transfer\n\n#define BITS_PER_PIXEL 16     // 24 for RGB colour format, 16 for 565 colour format\n\n// File names must be alpha-numeric characters (0-9, a-z, A-Z) or \"/\" underscore \"_\"\n// other ascii characters are stripped out by client, including / generates\n// sub-directories\n#define DEFAULT_FILENAME \"tft_screenshots/screenshot\" // In case none is specified\n#define FILE_TYPE \"png\"       // jpg, bmp, png, tif are valid\n\n// Filename extension\n// '#' = add 0-9, '@' = add timestamp, '%' add millis() timestamp, '*' = add nothing\n// '@' and '%' will generate new unique filenames, so beware of cluttering up your\n// hard drive with lots of images! The PC client sketch is set to limit the number of\n// saved images to 1000 and will then prompt for a restart.\n#define FILE_EXT  '%'         \n\n// Number of pixels to send in a burst (minimum of 1), no benefit above 8\n// NPIXELS values and render times: 1 = 5.0s, 2 = 1.75s, 4 = 1.68s, 8 = 1.67s\n#define NPIXELS 8  // Must be integer division of both TFT width and TFT height\n\nboolean screenServer(void);\nboolean screenServer(String filename);\nboolean serialScreenServer(String filename);\nvoid sendParameters(String filename);\n\n//====================================================================================\n//                           Screen server call with no filename\n//====================================================================================\n// Start a screen dump server (serial or network) - no filename specified\nboolean screenServer(void)\n{\n  // With no filename the screenshot will be saved with a default name e.g. tft_screen_#.xxx\n  // where # is a number 0-9 and xxx is a file type specified below\n  return screenServer(DEFAULT_FILENAME);\n}\n\n//====================================================================================\n//                           Screen server call with filename\n//====================================================================================\n// Start a screen dump server (serial or network) - filename specified\nboolean screenServer(String filename)\n{\n  boolean result = serialScreenServer(filename); // Screenshot serial port server\n  //boolean result = wifiScreenServer(filename);   // Screenshot WiFi UDP port server (WIP)\n\n  delay(0); // Equivalent to yield() for ESP8266;\n\n  //Serial.println();\n  //if (result) Serial.println(F(\"Screen dump passed :-)\"));\n  //else        Serial.println(F(\"Screen dump failed :-(\"));\n\n  return result;\n}\n\n//====================================================================================\n//                Serial server function that sends the data to the client\n//====================================================================================\nboolean serialScreenServer(String filename)\n{\n  // Precautionary receive buffer garbage flush for 50ms\n  uint32_t clearTime = millis() + 50;\n  while ( millis() < clearTime && Serial.read() >= 0) delay(0); // Equivalent to yield() for ESP8266;\n\n  boolean wait = true;\n  uint32_t lastCmdTime = millis();     // Initialise start of command time-out\n\n  // Wait for the starting flag with a start time-out\n  while (wait)\n  {\n    delay(0); // Equivalent to yield() for ESP8266;\n    // Check serial buffer\n    if (Serial.available() > 0) {\n      // Read the command byte\n      uint8_t cmd = Serial.read();\n      // If it is 'S' (start command) then clear the serial buffer for 100ms and stop waiting\n      if ( cmd == 'S' ) {\n        // Precautionary receive buffer garbage flush for 50ms\n        clearTime = millis() + 50;\n        while ( millis() < clearTime && Serial.read() >= 0) delay(0); // Equivalent to yield() for ESP8266;\n\n        wait = false;           // No need to wait anymore\n        lastCmdTime = millis(); // Set last received command time\n\n        // Send screen size etc using a simple header with delimiters for client checks\n        sendParameters(filename);\n      }\n    }\n    else\n    {\n      // Check for time-out\n      if ( millis() > lastCmdTime + START_TIMEOUT) return false;\n    }\n  }\n\n  uint8_t color[3 * NPIXELS]; // RGB and 565 format color buffer for N pixels\n\n  // Send all the pixels on the whole screen\n  for ( uint32_t y = 0; y < tft.height(); y++)\n  {\n    // Increment x by NPIXELS as we send NPIXELS for every byte received\n    for ( uint32_t x = 0; x < tft.width(); x += NPIXELS)\n    {\n      delay(0); // Equivalent to yield() for ESP8266;\n\n      // Wait here for serial data to arrive or a time-out elapses\n      while ( Serial.available() == 0 )\n      {\n        if ( millis() > lastCmdTime + PIXEL_TIMEOUT) return false;\n        delay(0); // Equivalent to yield() for ESP8266;\n      }\n\n      // Serial data must be available to get here, read 1 byte and\n      // respond with N pixels, i.e. N x 3 RGB bytes or N x 2 565 format bytes\n      if ( Serial.read() == 'X' ) {\n        // X command byte means abort, so clear the buffer and return\n        clearTime = millis() + 50;\n        while ( millis() < clearTime && Serial.read() >= 0) delay(0); // Equivalent to yield() for ESP8266;\n        return false;\n      }\n      // Save arrival time of the read command (for later time-out check)\n      lastCmdTime = millis();\n\n#if defined BITS_PER_PIXEL && BITS_PER_PIXEL >= 24\n      // Fetch N RGB pixels from x,y and put in buffer\n      tft.readRectRGB(x, y, NPIXELS, 1, color);\n      // Send buffer to client\n      Serial.write(color, 3 * NPIXELS); // Write all pixels in the buffer\n#else\n      // Fetch N 565 format pixels from x,y and put in buffer\n      tft.readRect(x, y, NPIXELS, 1, (uint16_t *)color);\n      // Send buffer to client\n      Serial.write(color, 2 * NPIXELS); // Write all pixels in the buffer\n#endif\n    }\n  }\n\n  Serial.flush(); // Make sure all pixel bytes have been despatched\n\n  return true;\n}\n\n//====================================================================================\n//    Send screen size etc using a simple header with delimiters for client checks\n//====================================================================================\nvoid sendParameters(String filename)\n{\n  Serial.write('W'); // Width\n  Serial.write(tft.width()  >> 8);\n  Serial.write(tft.width()  & 0xFF);\n\n  Serial.write('H'); // Height\n  Serial.write(tft.height() >> 8);\n  Serial.write(tft.height() & 0xFF);\n\n  Serial.write('Y'); // Bits per pixel (16 or 24)\n  Serial.write(BITS_PER_PIXEL);\n\n  Serial.write('?'); // Filename next\n  Serial.print(filename);\n\n  Serial.write('.'); // End of filename marker\n\n  Serial.write(FILE_EXT); // Filename extension identifier\n\n  Serial.write(*FILE_TYPE); // First character defines file type j,b,p,t\n}\n"
  },
  {
    "path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/TFT_eSPI_OpenWeather.ino",
    "content": "//  Example from OpenWeather library: https://github.com/Bodmer/OpenWeather\n//  Adapted by Bodmer to use the TFT_eSPI library:  https://github.com/Bodmer/TFT_eSPI\n\n//  This sketch is compatible with the ESP8266 and ESP32\n\n//                           >>>  IMPORTANT  <<<\n//         Modify setup in All_Settings.h tab to configure your location etc\n\n//                >>>  EVEN MORE IMPORTANT TO PREVENT CRASHES <<<\n//>>>>>>  For ESP8266 set SPIFFS to at least 2Mbytes before uploading files  <<<<<<\n\n//  ESP8266/ESP32 pin connections to the TFT are defined in the TFT_eSPI library.\n\n//  Original by Daniel Eichhorn, see license at end of file.\n\n//#define SERIAL_MESSAGES // For serial output weather reports\n//#define SCREEN_SERVER   // For dumping screen shots from TFT\n//#define RANDOM_LOCATION // Test only, selects random weather location every refresh\n//#define FORMAT_SPIFFS   // Wipe SPIFFS and all files!\n\n// This sketch uses font files created from the Noto family of fonts as bitmaps\n// generated from these fonts may be freely distributed:\n// https://www.google.com/get/noto/\n\n// A processing sketch to create new fonts can be found in the Tools folder of TFT_eSPI\n// https://github.com/Bodmer/TFT_eSPI/tree/master/Tools/Create_Smooth_Font/Create_font\n// New fonts can be generated to include language specific characters. The Noto family\n// of fonts has an extensive character set coverage.\n\n// Json streaming parser (do not use IDE library manager version) to use is here:\n// https://github.com/Bodmer/JSON_Decoder\n\n#define AA_FONT_SMALL \"fonts/NotoSansBold15\" // 15 point sans serif bold\n#define AA_FONT_LARGE \"fonts/NotoSansBold36\" // 36 point sans serif bold\n/***************************************************************************************\n**                          Load the libraries and settings\n***************************************************************************************/\n#include <Arduino.h>\n\n#include <SPI.h>\n#include <TFT_eSPI.h> // https://github.com/Bodmer/TFT_eSPI\n\n// Additional functions\n#include \"GfxUi.h\"          // Attached to this sketch\n#include \"SPIFFS_Support.h\" // Attached to this sketch\n\n#ifdef ESP8266\n  #include <ESP8266WiFi.h>\n#else\n  #include <WiFi.h>\n#endif\n\n\n// check All_Settings.h for adapting to your needs\n#include \"All_Settings.h\"\n\n#include <JSON_Decoder.h> // https://github.com/Bodmer/JSON_Decoder\n\n#include <OpenWeather.h>  // Latest here: https://github.com/Bodmer/OpenWeather\n\n#include \"NTP_Time.h\"     // Attached to this sketch, see that tab for library needs\n\n/***************************************************************************************\n**                          Define the globals and class instances\n***************************************************************************************/\n\nTFT_eSPI tft = TFT_eSPI();             // Invoke custom library\n\nOW_Weather ow;      // Weather forecast library instance\n\nOW_current *current; // Pointers to structs that temporarily holds weather data\nOW_hourly  *hourly;  // Not used\nOW_daily   *daily;\n\nboolean booted = true;\n\nGfxUi ui = GfxUi(&tft); // Jpeg and bmpDraw functions TODO: pull outside of a class\n\nlong lastDownloadUpdate = millis();\n\n/***************************************************************************************\n**                          Declare prototypes\n***************************************************************************************/\nvoid updateData();\nvoid drawProgress(uint8_t percentage, String text);\nvoid drawTime();\nvoid drawCurrentWeather();\nvoid drawForecast();\nvoid drawForecastDetail(uint16_t x, uint16_t y, uint8_t dayIndex);\nconst char* getMeteoconIcon(uint16_t id, bool today);\nvoid drawAstronomy();\nvoid drawSeparator(uint16_t y);\nvoid fillSegment(int x, int y, int start_angle, int sub_angle, int r, unsigned int colour);\nString strDate(time_t unixTime);\nString strTime(time_t unixTime);\nvoid printWeather(void);\nint leftOffset(String text, String sub);\nint rightOffset(String text, String sub);\nint splitIndex(String text);\n\n/***************************************************************************************\n**                          Setup\n***************************************************************************************/\nvoid setup() {\n  Serial.begin(250000);\n\n  tft.begin();\n  tft.fillScreen(TFT_BLACK);\n\n  SPIFFS.begin();\n  listFiles();\n\n  // Enable if you want to erase SPIFFS, this takes some time!\n  // then disable and reload sketch to avoid reformatting on every boot!\n  #ifdef FORMAT_SPIFFS\n    tft.setTextDatum(BC_DATUM); // Bottom Centre datum\n    tft.drawString(\"Formatting SPIFFS, so wait!\", 120, 195); SPIFFS.format();\n  #endif\n\n  // Draw splash screen\n  if (SPIFFS.exists(\"/splash/OpenWeather.jpg\")   == true) ui.drawJpeg(\"/splash/OpenWeather.jpg\",   0, 40);\n\n  delay(2000);\n\n  // Clear bottom section of screen\n  tft.fillRect(0, 206, 240, 320 - 206, TFT_BLACK);\n\n  tft.loadFont(AA_FONT_SMALL);\n  tft.setTextDatum(BC_DATUM); // Bottom Centre datum\n  tft.setTextColor(TFT_LIGHTGREY, TFT_BLACK);\n\n  tft.drawString(\"Original by: blog.squix.org\", 120, 260);\n  tft.drawString(\"Adapted by: Bodmer\", 120, 280);\n\n  tft.setTextColor(TFT_YELLOW, TFT_BLACK);\n\n  delay(2000);\n\n  tft.fillRect(0, 206, 240, 320 - 206, TFT_BLACK);\n\n  tft.drawString(\"Connecting to WiFi\", 120, 240);\n  tft.setTextPadding(240); // Pad next drawString() text to full width to over-write old text\n\n  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);\n\n  while (WiFi.status() != WL_CONNECTED) {\n    delay(1000);\n    Serial.print(\".\");\n  }\n  Serial.println();\n\n  tft.setTextDatum(BC_DATUM);\n  tft.setTextPadding(240); // Pad next drawString() text to full width to over-write old text\n  tft.drawString(\" \", 120, 220);  // Clear line above using set padding width\n  tft.drawString(\"Fetching weather data...\", 120, 240);\n\n  // Fetch the time\n  udp.begin(localPort);\n  syncTime();\n\n  tft.unloadFont();\n\n  ow.partialDataSet(true); // Collect a subset of the data available\n}\n\n/***************************************************************************************\n**                          Loop\n***************************************************************************************/\nvoid loop() {\n\n  // Check if we should update weather information\n  if (booted || (millis() - lastDownloadUpdate > 1000UL * UPDATE_INTERVAL_SECS))\n  {\n    updateData();\n    lastDownloadUpdate = millis();\n  }\n\n  // If minute has changed then request new time from NTP server\n  if (booted || minute() != lastMinute)\n  {\n    // Update displayed time first as we may have to wait for a response\n    drawTime();\n    lastMinute = minute();\n\n    // Request and synchronise the local clock\n    syncTime();\n\n    #ifdef SCREEN_SERVER\n      screenServer();\n    #endif\n  }\n\n  booted = false;\n}\n\n/***************************************************************************************\n**                          Fetch the weather data  and update screen\n***************************************************************************************/\n// Update the Internet based information and update screen\nvoid updateData() {\n  // booted = true;  // Test only\n  // booted = false; // Test only\n\n  if (booted) drawProgress(20, \"Updating time...\");\n  else fillSegment(22, 22, 0, (int) (20 * 3.6), 16, TFT_NAVY);\n\n  if (booted) drawProgress(50, \"Updating conditions...\");\n  else fillSegment(22, 22, 0, (int) (50 * 3.6), 16, TFT_NAVY);\n\n  // Create the structures that hold the retrieved weather\n  current = new OW_current;\n  daily =   new OW_daily;\n  hourly =  new OW_hourly;\n\n#ifdef RANDOM_LOCATION // Randomly choose a place on Earth to test icons etc\n  String latitude = \"\";\n  latitude = (random(180) - 90);\n  String longitude = \"\";\n  longitude = (random(360) - 180);\n  Serial.print(\"Lat = \"); Serial.print(latitude);\n  Serial.print(\", Lon = \"); Serial.println(longitude);\n#endif\n\n  //On the ESP8266 (only) the library by default uses BearSSL, another option is to use AXTLS\n  //For problems with ESP8266 stability, use AXTLS by adding a false parameter thus       vvvvv\n  //ow.getForecast(current, hourly, daily, api_key, latitude, longitude, units, language, false);\n\n  bool parsed = ow.getForecast(current, hourly, daily, api_key, latitude, longitude, units, language);\n\n  if (parsed) Serial.println(\"Data points received\");\n  else Serial.println(\"Failed to get data points\");\n\n  Serial.print(\"Free heap = \"); Serial.println(ESP.getFreeHeap(), DEC);\n\n  printWeather(); // For debug, turn on output with #define SERIAL_MESSAGES\n\n  if (booted)\n  {\n    drawProgress(100, \"Done...\");\n    delay(2000);\n    tft.fillScreen(TFT_BLACK);\n  }\n  else\n  {\n    fillSegment(22, 22, 0, 360, 16, TFT_NAVY);\n    fillSegment(22, 22, 0, 360, 22, TFT_BLACK);\n  }\n\n  if (parsed)\n  {\n    tft.loadFont(AA_FONT_SMALL);\n    drawCurrentWeather();\n    drawForecast();\n    drawAstronomy();\n    tft.unloadFont();\n\n    // Update the temperature here so we don't need to keep\n    // loading and unloading font which takes time\n    tft.loadFont(AA_FONT_LARGE);\n    tft.setTextDatum(TR_DATUM);\n    tft.setTextColor(TFT_YELLOW, TFT_BLACK);\n\n    // Font ASCII code 0xB0 is a degree symbol, but o used instead in small font\n    tft.setTextPadding(tft.textWidth(\" -88\")); // Max width of values\n\n    String weatherText = \"\";\n    weatherText = String(current->temp, 0);  // Make it integer temperature\n    tft.drawString(weatherText, 215, 95); //  + \"°\" symbol is big... use o in small font\n    tft.unloadFont();\n  }\n  else\n  {\n    Serial.println(\"Failed to get weather\");\n  }\n\n  // Delete to free up space\n  delete current;\n  delete hourly;\n  delete daily;\n}\n\n/***************************************************************************************\n**                          Update progress bar\n***************************************************************************************/\nvoid drawProgress(uint8_t percentage, String text) {\n  tft.loadFont(AA_FONT_SMALL);\n  tft.setTextDatum(BC_DATUM);\n  tft.setTextColor(TFT_ORANGE, TFT_BLACK);\n  tft.setTextPadding(240);\n  tft.drawString(text, 120, 260);\n\n  ui.drawProgressBar(10, 269, 240 - 20, 15, percentage, TFT_WHITE, TFT_BLUE);\n\n  tft.setTextPadding(0);\n  tft.unloadFont();\n}\n\n/***************************************************************************************\n**                          Draw the clock digits\n***************************************************************************************/\nvoid drawTime() {\n  tft.loadFont(AA_FONT_LARGE);\n\n  // Convert UTC to local time, returns zone code in tz1_Code, e.g \"GMT\"\n  time_t local_time = TIMEZONE.toLocal(now(), &tz1_Code);\n\n  String timeNow = \"\";\n\n  if (hour(local_time) < 10) timeNow += \"0\";\n  timeNow += hour(local_time);\n  timeNow += \":\";\n  if (minute(local_time) < 10) timeNow += \"0\";\n  timeNow += minute(local_time);\n\n  tft.setTextDatum(BC_DATUM);\n  tft.setTextColor(TFT_YELLOW, TFT_BLACK);\n  tft.setTextPadding(tft.textWidth(\" 44:44 \"));  // String width + margin\n  tft.drawString(timeNow, 120, 53);\n\n  drawSeparator(51);\n\n  tft.setTextPadding(0);\n\n  tft.unloadFont();\n}\n\n/***************************************************************************************\n**                          Draw the current weather\n***************************************************************************************/\nvoid drawCurrentWeather() {\n  String date = \"Updated: \" + strDate(current->dt);\n  String weatherText = \"None\";\n\n  tft.setTextDatum(BC_DATUM);\n  tft.setTextColor(TFT_ORANGE, TFT_BLACK);\n  tft.setTextPadding(tft.textWidth(\" Updated: Mmm 44 44:44 \"));  // String width + margin\n  tft.drawString(date, 120, 16);\n\n  String weatherIcon = \"\";\n\n  String currentSummary = current->main;\n  currentSummary.toLowerCase();\n\n  weatherIcon = getMeteoconIcon(current->id, true);\n\n  //uint32_t dt = millis();\n  ui.drawBmp(\"/icon/\" + weatherIcon + \".bmp\", 0, 53);\n  //Serial.print(\"Icon draw time = \"); Serial.println(millis()-dt);\n\n  // Weather Text\n  if (language == \"en\")\n    weatherText = current->main;\n  else\n    weatherText = current->description;\n\n  tft.setTextDatum(BR_DATUM);\n  tft.setTextColor(TFT_ORANGE, TFT_BLACK);\n\n  int splitPoint = 0;\n  int xpos = 235;\n  splitPoint =  splitIndex(weatherText);\n\n  tft.setTextPadding(xpos - 100);  // xpos - icon width\n  if (splitPoint) tft.drawString(weatherText.substring(0, splitPoint), xpos, 69);\n  else tft.drawString(\" \", xpos, 69);\n  tft.drawString(weatherText.substring(splitPoint), xpos, 86);\n\n  tft.setTextColor(TFT_YELLOW, TFT_BLACK);\n  tft.setTextDatum(TR_DATUM);\n  tft.setTextPadding(0);\n  if (units == \"metric\") tft.drawString(\"oC\", 237, 95);\n  else  tft.drawString(\"oF\", 237, 95);\n\n  //Temperature large digits added in updateData() to save swapping font here\n \n  tft.setTextColor(TFT_ORANGE, TFT_BLACK);\n  weatherText = String(current->wind_speed, 0);\n\n  if (units == \"metric\") weatherText += \" m/s\";\n  else weatherText += \" mph\";\n\n  tft.setTextDatum(TC_DATUM);\n  tft.setTextPadding(tft.textWidth(\"888 m/s\")); // Max string length?\n  tft.drawString(weatherText, 124, 136);\n\n  if (units == \"imperial\")\n  {\n    weatherText = current->pressure * 0.02953;\n    weatherText += \" in\";\n  }\n  else\n  {\n    weatherText = String(current->pressure, 0);\n    weatherText += \" hPa\";\n  }\n\n  tft.setTextDatum(TR_DATUM);\n  tft.setTextPadding(tft.textWidth(\" 8888hPa\")); // Max string length?\n  tft.drawString(weatherText, 230, 136);\n\n  int windAngle = (current->wind_deg + 22.5) / 45;\n  if (windAngle > 7) windAngle = 0;\n  String wind[] = {\"N\", \"NE\", \"E\", \"SE\", \"S\", \"SW\", \"W\", \"NW\" };\n  ui.drawBmp(\"/wind/\" + wind[windAngle] + \".bmp\", 101, 86);\n\n  drawSeparator(153);\n\n  tft.setTextDatum(TL_DATUM); // Reset datum to normal\n  tft.setTextPadding(0);      // Reset padding width to none\n}\n\n/***************************************************************************************\n**                          Draw the 4 forecast columns\n***************************************************************************************/\n// draws the three forecast columns\nvoid drawForecast() {\n  int8_t dayIndex = 1;\n\n  drawForecastDetail(  8, 171, dayIndex++);\n  drawForecastDetail( 66, 171, dayIndex++); // was 95\n  drawForecastDetail(124, 171, dayIndex++); // was 180\n  drawForecastDetail(182, 171, dayIndex  ); // was 180\n  drawSeparator(171 + 69);\n}\n\n/***************************************************************************************\n**                          Draw 1 forecast column at x, y\n***************************************************************************************/\n// helper for the forecast columns\nvoid drawForecastDetail(uint16_t x, uint16_t y, uint8_t dayIndex) {\n\n  if (dayIndex >= MAX_DAYS) return;\n\n  String day  = shortDOW[weekday(TIMEZONE.toLocal(daily->dt[dayIndex], &tz1_Code))];\n  day.toUpperCase();\n\n  tft.setTextDatum(BC_DATUM);\n\n  tft.setTextColor(TFT_ORANGE, TFT_BLACK);\n  tft.setTextPadding(tft.textWidth(\"WWW\"));\n  tft.drawString(day, x + 25, y);\n\n  tft.setTextColor(TFT_WHITE, TFT_BLACK);\n  tft.setTextPadding(tft.textWidth(\"-88   -88\"));\n  String highTemp = String(daily->temp_max[dayIndex], 0);\n  String lowTemp  = String(daily->temp_min[dayIndex], 0);\n  tft.drawString(highTemp + \" \" + lowTemp, x + 25, y + 17);\n\n  String weatherIcon = getMeteoconIcon(daily->id[dayIndex], false);\n\n  ui.drawBmp(\"/icon50/\" + weatherIcon + \".bmp\", x, y + 18);\n\n  tft.setTextPadding(0); // Reset padding width to none\n}\n\n/***************************************************************************************\n**                          Draw Sun rise/set, Moon, cloud cover and humidity\n***************************************************************************************/\nvoid drawAstronomy() {\n\n  tft.setTextDatum(BC_DATUM);\n  tft.setTextColor(TFT_WHITE, TFT_BLACK);\n  tft.setTextPadding(tft.textWidth(\" Last qtr \"));\n\n  time_t local_time = TIMEZONE.toLocal(current->dt, &tz1_Code);\n  uint16_t y = year(local_time);\n  uint8_t  m = month(local_time);\n  uint8_t  d = day(local_time);\n  uint8_t  h = hour(local_time);\n  int      ip;\n  uint8_t icon = moon_phase(y, m, d, h, &ip);\n\n  tft.drawString(moonPhase[ip], 120, 319);\n  ui.drawBmp(\"/moon/moonphase_L\" + String(icon) + \".bmp\", 120 - 30, 318 - 16 - 60);\n\n  tft.setTextDatum(BC_DATUM);\n  tft.setTextColor(TFT_ORANGE, TFT_BLACK);\n  tft.setTextPadding(0); // Reset padding width to none\n  tft.drawString(sunStr, 40, 270);\n\n  tft.setTextDatum(BR_DATUM);\n  tft.setTextColor(TFT_WHITE, TFT_BLACK);\n  tft.setTextPadding(tft.textWidth(\" 88:88 \"));\n\n  String rising = strTime(current->sunrise) + \" \";\n  int dt = rightOffset(rising, \":\"); // Draw relative to colon to them aligned\n  tft.drawString(rising, 40 + dt, 290);\n\n  String setting = strTime(current->sunset) + \" \";\n  dt = rightOffset(setting, \":\");\n  tft.drawString(setting, 40 + dt, 305);\n\n  tft.setTextDatum(BC_DATUM);\n  tft.setTextColor(TFT_ORANGE, TFT_BLACK);\n  tft.drawString(cloudStr, 195, 260);\n\n  String cloudCover = \"\";\n  cloudCover += current->clouds;\n  cloudCover += \"%\";\n\n  tft.setTextDatum(BR_DATUM);\n  tft.setTextColor(TFT_WHITE, TFT_BLACK);\n  tft.setTextPadding(tft.textWidth(\" 100%\"));\n  tft.drawString(cloudCover, 210, 277);\n\n  tft.setTextDatum(BC_DATUM);\n  tft.setTextColor(TFT_ORANGE, TFT_BLACK);\n  tft.drawString(humidityStr, 195, 300 - 2);\n\n  String humidity = \"\";\n  humidity += current->humidity;\n  humidity += \"%\";\n\n  tft.setTextDatum(BR_DATUM);\n  tft.setTextColor(TFT_WHITE, TFT_BLACK);\n  tft.setTextPadding(tft.textWidth(\"100%\"));\n  tft.drawString(humidity, 210, 315);\n\n  tft.setTextPadding(0); // Reset padding width to none\n}\n\n/***************************************************************************************\n**                          Get the icon file name from the index number\n***************************************************************************************/\nconst char* getMeteoconIcon(uint16_t id, bool today)\n{\n  if ( today && id/100 == 8 && (current->dt < current->sunrise || current->dt > current->sunset)) id += 1000; \n\n  if (id/100 == 2) return \"thunderstorm\";\n  if (id/100 == 3) return \"drizzle\";\n  if (id/100 == 4) return \"unknown\";\n  if (id == 500) return \"lightRain\";\n  else if (id == 511) return \"sleet\";\n  else if (id/100 == 5) return \"rain\";\n  if (id >= 611 && id <= 616) return \"sleet\";\n  else if (id/100 == 6) return \"snow\";\n  if (id/100 == 7) return \"fog\";\n  if (id == 800) return \"clear-day\";\n  if (id == 801) return \"partly-cloudy-day\";\n  if (id == 802) return \"cloudy\";\n  if (id == 803) return \"cloudy\";\n  if (id == 804) return \"cloudy\";\n  if (id == 1800) return \"clear-night\";\n  if (id == 1801) return \"partly-cloudy-night\";\n  if (id == 1802) return \"cloudy\";\n  if (id == 1803) return \"cloudy\";\n  if (id == 1804) return \"cloudy\";\n\n  return \"unknown\";\n}\n\n/***************************************************************************************\n**                          Draw screen section separator line\n***************************************************************************************/\n// if you don't want separators, comment out the tft-line\nvoid drawSeparator(uint16_t y) {\n  tft.drawFastHLine(10, y, 240 - 2 * 10, 0x4228);\n}\n\n/***************************************************************************************\n**                          Determine place to split a line line\n***************************************************************************************/\n// determine the \"space\" split point in a long string\nint splitIndex(String text)\n{\n  uint16_t index = 0;\n  while ( (text.indexOf(' ', index) >= 0) && ( index <= text.length() / 2 ) ) {\n    index = text.indexOf(' ', index) + 1;\n  }\n  if (index) index--;\n  return index;\n}\n\n/***************************************************************************************\n**                          Right side offset to a character\n***************************************************************************************/\n// Calculate coord delta from end of text String to start of sub String contained within that text\n// Can be used to vertically right align text so for example a colon \":\" in the time value is always\n// plotted at same point on the screen irrespective of different proportional character widths,\n// could also be used to align decimal points for neat formatting\nint rightOffset(String text, String sub)\n{\n  int index = text.indexOf(sub);\n  return tft.textWidth(text.substring(index));\n}\n\n/***************************************************************************************\n**                          Left side offset to a character\n***************************************************************************************/\n// Calculate coord delta from start of text String to start of sub String contained within that text\n// Can be used to vertically left align text so for example a colon \":\" in the time value is always\n// plotted at same point on the screen irrespective of different proportional character widths,\n// could also be used to align decimal points for neat formatting\nint leftOffset(String text, String sub)\n{\n  int index = text.indexOf(sub);\n  return tft.textWidth(text.substring(0, index));\n}\n\n/***************************************************************************************\n**                          Draw circle segment\n***************************************************************************************/\n// Draw a segment of a circle, centred on x,y with defined start_angle and subtended sub_angle\n// Angles are defined in a clockwise direction with 0 at top\n// Segment has radius r and it is plotted in defined colour\n// Can be used for pie charts etc, in this sketch it is used for wind direction\n#define DEG2RAD 0.0174532925 // Degrees to Radians conversion factor\n#define INC 2 // Minimum segment subtended angle and plotting angle increment (in degrees)\nvoid fillSegment(int x, int y, int start_angle, int sub_angle, int r, unsigned int colour)\n{\n  // Calculate first pair of coordinates for segment start\n  float sx = cos((start_angle - 90) * DEG2RAD);\n  float sy = sin((start_angle - 90) * DEG2RAD);\n  uint16_t x1 = sx * r + x;\n  uint16_t y1 = sy * r + y;\n\n  // Draw colour blocks every INC degrees\n  for (int i = start_angle; i < start_angle + sub_angle; i += INC) {\n\n    // Calculate pair of coordinates for segment end\n    int x2 = cos((i + 1 - 90) * DEG2RAD) * r + x;\n    int y2 = sin((i + 1 - 90) * DEG2RAD) * r + y;\n\n    tft.fillTriangle(x1, y1, x2, y2, x, y, colour);\n\n    // Copy segment end to segment start for next segment\n    x1 = x2;\n    y1 = y2;\n  }\n}\n\n/***************************************************************************************\n**                          Print the weather info to the Serial Monitor\n***************************************************************************************/\nvoid printWeather(void)\n{\n#ifdef SERIAL_MESSAGES\n  Serial.println(\"Weather from OpenWeather\\n\");\n\n  Serial.println(\"############### Current weather ###############\\n\");\n  Serial.print(\"dt (time)          : \"); Serial.println(strDate(current->dt));\n  Serial.print(\"sunrise            : \"); Serial.println(strDate(current->sunrise));\n  Serial.print(\"sunset             : \"); Serial.println(strDate(current->sunset));\n  Serial.print(\"main               : \"); Serial.println(current->main);\n  Serial.print(\"temp               : \"); Serial.println(current->temp);\n  Serial.print(\"humidity           : \"); Serial.println(current->humidity);\n  Serial.print(\"pressure           : \"); Serial.println(current->pressure);\n  Serial.print(\"wind_speed         : \"); Serial.println(current->wind_speed);\n  Serial.print(\"wind_deg           : \"); Serial.println(current->wind_deg);\n  Serial.print(\"clouds             : \"); Serial.println(current->clouds);\n  Serial.print(\"id                 : \"); Serial.println(current->id);\n  Serial.println();\n\n  Serial.println(\"###############  Daily weather  ###############\\n\");\n  Serial.println();\n\n  for (int i = 0; i < 5; i++)\n  {\n    Serial.print(\"dt (time)          : \"); Serial.println(strDate(daily->dt[i]));\n    Serial.print(\"id                 : \"); Serial.println(daily->id[i]);\n    Serial.print(\"temp_max           : \"); Serial.println(daily->temp_max[i]);\n    Serial.print(\"temp_min           : \"); Serial.println(daily->temp_min[i]);\n    Serial.println();\n  }\n\n#endif\n}\n/***************************************************************************************\n**             Convert Unix time to a \"local time\" time string \"12:34\"\n***************************************************************************************/\nString strTime(time_t unixTime)\n{\n  time_t local_time = TIMEZONE.toLocal(unixTime, &tz1_Code);\n\n  String localTime = \"\";\n\n  if (hour(local_time) < 10) localTime += \"0\";\n  localTime += hour(local_time);\n  localTime += \":\";\n  if (minute(local_time) < 10) localTime += \"0\";\n  localTime += minute(local_time);\n\n  return localTime;\n}\n\n/***************************************************************************************\n**  Convert Unix time to a local date + time string \"Oct 16 17:18\", ends with newline\n***************************************************************************************/\nString strDate(time_t unixTime)\n{\n  time_t local_time = TIMEZONE.toLocal(unixTime, &tz1_Code);\n\n  String localDate = \"\";\n\n  localDate += monthShortStr(month(local_time));\n  localDate += \" \";\n  localDate += day(local_time);\n  localDate += \" \" + strTime(unixTime);\n\n  return localDate;\n}\n\n/**The MIT License (MIT)\n  Copyright (c) 2015 by Daniel Eichhorn\n  Permission is hereby granted, free of charge, to any person obtaining a copy\n  of this software and associated documentation files (the \"Software\"), to deal\n  in the Software without restriction, including without limitation the rights\n  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n  copies of the Software, and to permit persons to whom the Software is\n  furnished to do so, subject to the following conditions:\n  The above copyright notice and this permission notice shall be included in all\n  copies or substantial portions of the Software.\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n  AUTHORS OR COPYBR_DATUM HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n  SOFTWARE.\n  See more at http://blog.squix.ch\n*/\n\n//  Changes made by Bodmer:\n\n//  Minor changes to text placement and auto-blanking out old text with background colour padding\n//  Moon phase text added (not provided by OpenWeather)\n//  Forecast text lines are automatically split onto two lines at a central space (some are long!)\n//  Time is printed with colons aligned to tidy display\n//  Min and max forecast temperatures spaced out\n//  New smart splash startup screen and updated progress messages\n//  Display does not need to be blanked between updates\n//  Icons nudged about slightly to add wind direction + speed\n//  Barometric pressure added\n\n//  Adapted to use the OpenWeather library: https://github.com/Bodmer/OpenWeather\n//  Moon phase/rise/set (not provided by OpenWeather) replace with  and cloud cover humidity\n//  Created and added new 100x100 and 50x50 pixel weather icons, these are in the\n//  sketch data folder, press Ctrl+K to view\n//  Add moon icons, eliminate all downloads of icons (may lose server!)\n//  Adapted to use anti-aliased fonts, tweaked coords\n//  Added forecast for 4th day\n//  Added cloud cover and humidity in lieu of Moon rise/set\n//  Adapted to be compatible with ESP32\n"
  },
  {
    "path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather_LittleFS/All_Settings.h",
    "content": "//  Use the OpenWeather library: https://github.com/Bodmer/OpenWeather\n\n//  The weather icons and fonts are in the sketch data folder, press Ctrl+K\n//  to view.\n\n// The ESP32 board support package 2.0.0 or later must be loaded in the\n// Arduino boards manager to provide LittleFS support.\n\n//            >>>       IMPORTANT TO PREVENT CRASHES      <<<\n//>>>>>>  Set LittleFS to at least 1.5Mbytes before uploading files  <<<<<<\n\n//            >>>           DON'T FORGET THIS             <<<\n//  Upload the fonts and icons to LittleFS using the \"Tools\" menu option.\n\n// You can change the number of hours and days for the forecast in the\n// \"User_Setup.h\" file inside the OpenWeather library folder.\n// By default this is 6 hours (can be up to 48) and 5 days\n// (can be up to 8 days = today plus 7 days). This sketch requires\n// at least 5 days of forecast. Forecast hours can be set to 1 as\n// the hourly forecast data is not used in this sketch.\n\n//////////////////////////////\n// Setttings defined below\n\n#define WIFI_SSID      \"Your_SSID\"\n#define WIFI_PASSWORD  \"Your_password\"\n\n#define TIMEZONE UK // See NTP_Time.h tab for other \"Zone references\", UK, usMT etc\n\n// Update every 15 minutes, up to 1000 request per day are free (viz average of ~40 per hour)\nconst int UPDATE_INTERVAL_SECS = 15UL * 60UL; // 15 minutes\n\n// Pins for the TFT interface are defined in the User_Config.h file inside the TFT_eSPI library\n\n// For units use \"metric\" or \"imperial\"\nconst String units = \"metric\";\n\n// Sign up for a key and read API configuration info here:\n// https://openweathermap.org/, change x's to your API key\nconst String api_key = \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\";\n\n\n// Set the forecast longitude and latitude to at least 4 decimal places\nconst String latitude =  \"27.9881\"; // 90.0000 to -90.0000 negative for Southern hemisphere\nconst String longitude = \"86.9250\"; // 180.000 to -180.000 negative for West\n\n// For language codes see https://openweathermap.org/current#multi\nconst String language = \"en\"; // Default language = en = English\n\n// Short day of week abbreviations used in 4 day forecast (change to your language)\nconst String shortDOW [8] = {\"???\", \"SUN\", \"MON\", \"TUE\", \"WED\", \"THU\", \"FRI\", \"SAT\"};\n\n// Change the labels to your language here:\nconst char sunStr[]        = \"Sun\";\nconst char cloudStr[]      = \"Cloud\";\nconst char humidityStr[]   = \"Humidity\";\nconst String moonPhase [8] = {\"New\", \"Waxing\", \"1st qtr\", \"Waxing\", \"Full\", \"Waning\", \"Last qtr\", \"Waning\"};\n\n// End of user settings\n//////////////////////////////\n"
  },
  {
    "path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather_LittleFS/GfxUi.cpp",
    "content": "/**The MIT License (MIT)\nCopyright (c) 2015 by Daniel Eichhorn\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\nSee more at http://blog.squix.ch\n*/\n\n// Adapted by Bodmer to use the TFT_eSPI library: https://github.com/Bodmer/TFT_eSPI\n// Functions no longer needed weeded out, Jpeg decoder functions added and updated\n// drawBMP() updated to buffer input and output pixels and avoid slow seeks\n\n#include \"GfxUi.h\"\n\nGfxUi::GfxUi(TFT_eSPI *tft) {\n  _tft = tft;\n}\n\nvoid GfxUi::drawProgressBar(uint16_t x0, uint16_t y0, uint16_t w, uint16_t h, uint8_t percentage, uint16_t frameColor, uint16_t barColor) {\n  if (percentage == 0) {\n    _tft->fillRoundRect(x0, y0, w, h, 3, TFT_BLACK);\n  }\n  uint8_t margin = 2;\n  uint16_t barHeight = h - 2 * margin;\n  uint16_t barWidth = w - 2 * margin;\n  _tft->drawRoundRect(x0, y0, w, h, 3, frameColor);\n  _tft->fillRect(x0 + margin, y0 + margin, barWidth * percentage / 100.0, barHeight, barColor);\n}\n\n// Bodmer's streamlined x2 faster \"no seek\" version\nvoid GfxUi::drawBmp(String filename, uint16_t x, uint16_t y)\n{\n\n  if ((x >= _tft->width()) || (y >= _tft->height())) return;\n\n  fs::File bmpFS;\n\n  // Check file exists and open it\n  // Serial.println(filename);\n\n  // Note: ESP32 passes \"open\" test even if file does not exist, whereas ESP8266 returns NULL\n  if ( !LittleFS.exists(filename) )\n  {\n    Serial.println(F(\" File not found\")); // Can comment out if not needed\n    return;\n  }\n\n  // Open requested file\n  bmpFS = LittleFS.open(filename, \"r\");\n\n  uint32_t seekOffset;\n  uint16_t w, h, row;\n  uint8_t  r, g, b;\n  bool     oldSwap = false;\n\n  if (read16(bmpFS) == 0x4D42)\n  {\n    read32(bmpFS);\n    read32(bmpFS);\n    seekOffset = read32(bmpFS);\n    read32(bmpFS);\n    w = read32(bmpFS);\n    h = read32(bmpFS);\n\n    if ((read16(bmpFS) == 1) && (read16(bmpFS) == 24) && (read32(bmpFS) == 0))\n    {\n      y += h - 1;\n\n      oldSwap = _tft->getSwapBytes();\n      _tft->setSwapBytes(true);\n      bmpFS.seek(seekOffset);\n\n      // Calculate padding to avoid seek\n      uint16_t padding = (4 - ((w * 3) & 3)) & 3;\n      uint8_t lineBuffer[w * 3 + padding];\n\n      for (row = 0; row < h; row++) {\n        \n        bmpFS.read(lineBuffer, sizeof(lineBuffer));\n        uint8_t*  bptr = lineBuffer;\n        uint16_t* tptr = (uint16_t*)lineBuffer;\n        // Convert 24 to 16 bit colours using the same line buffer for results\n        for (uint16_t col = 0; col < w; col++)\n        {\n          b = *bptr++;\n          g = *bptr++;\n          r = *bptr++;\n          *tptr++ = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);\n        }\n\n        // Push the pixel row to screen, pushImage will crop the line if needed\n        // y is decremented as the BMP image is drawn bottom up\n        _tft->pushImage(x, y--, w, 1, (uint16_t*)lineBuffer);\n      }\n    }\n    else Serial.println(\"BMP format not recognized.\");\n  }\n  _tft->setSwapBytes(oldSwap);\n  bmpFS.close();\n}\n\n// These read 16- and 32-bit types from the SD card file.\n// BMP data is stored little-endian, Arduino is little-endian too.\n// May need to reverse subscript order if porting elsewhere.\n\nuint16_t GfxUi::read16(fs::File &f) {\n  uint16_t result;\n  ((uint8_t *)&result)[0] = f.read(); // LSB\n  ((uint8_t *)&result)[1] = f.read(); // MSB\n  return result;\n}\n\nuint32_t GfxUi::read32(fs::File &f) {\n  uint32_t result;\n  ((uint8_t *)&result)[0] = f.read(); // LSB\n  ((uint8_t *)&result)[1] = f.read();\n  ((uint8_t *)&result)[2] = f.read();\n  ((uint8_t *)&result)[3] = f.read(); // MSB\n  return result;\n}\n\n//====================================================================================\n"
  },
  {
    "path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather_LittleFS/GfxUi.h",
    "content": "/**The MIT License (MIT)\nCopyright (c) 2015 by Daniel Eichhorn\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\nSee more at http://blog.squix.ch\n*/\n\n// Adapted by Bodmer to use the TFT_eSPI library:\n// https://github.com/Bodmer/TFT_eSPI\n\n\n\n#include <TFT_eSPI.h> // Hardware-specific library\n\n#include <FS.h>\n#include <LittleFS.h>\n\n// JPEG decoder library\n#include <TJpg_Decoder.h>\n\n#ifndef _GFX_UI_H\n#define _GFX_UI_H\n\n// Maximum of 85 for BUFFPIXEL as 3 x this value is stored in an 8 bit variable!\n// 32 is an efficient size for LittleFS due to SPI hardware pipeline buffer size\n// A larger value of 80 is better for SD cards\n#define BUFFPIXEL 32\n\nclass GfxUi {\n  public:\n    GfxUi(TFT_eSPI * tft);\n    void drawBmp(String filename, uint16_t x, uint16_t y);\n    void drawProgressBar(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t percentage, uint16_t frameColor, uint16_t barColor);\n    \n  private:\n    TFT_eSPI * _tft;\n    uint16_t read16(fs::File &f);\n    uint32_t read32(fs::File &f);\n\n};\n\n#endif\n"
  },
  {
    "path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather_LittleFS/MoonPhase.ino",
    "content": "/* Moon phase calculation for OpenWeather library*/\n// Adapted by Bodmer from code here:\n// http://www.voidware.com/moon_phase.htm\n\n#include <stdio.h>\n#include <math.h>\n\n#define PI  3.1415926535897932384626433832795\n#define RAD (PI/180.0)\n#define SMALL_FLOAT (1e-12)\n\ndouble Julian(int year, int month, double day)\n{\n  int a, b = 0, c, e;\n  if (month < 3) {\n    year--;\n    month += 12;\n  }\n  if (year > 1582 || (year == 1582 && month > 10) ||\n      (year == 1582 && month == 10 && day > 15)) {\n    a = year / 100;\n    b = 2 - a + a / 4;\n  }\n  c = 365.25 * year;\n  e = 30.6001 * (month + 1);\n  return b + c + e + day + 1720994.5;\n}\n\ndouble sun_position(double j)\n{\n  double n, x, e, l, dl, v;\n  int i;\n\n  n = 360 / 365.2422 * j;\n  i = n / 360;\n  n = n - i * 360.0;\n  x = n - 3.762863;\n  if (x < 0) x += 360;\n  x *= RAD;\n  e = x;\n  do {\n    dl = e - .016718 * sin(e) - x;\n    e = e - dl / (1 - .016718 * cos(e));\n  } while (fabs(dl) >= SMALL_FLOAT);\n  v = 360 / PI * atan(1.01686011182 * tan(e / 2));\n  l = v + 282.596403;\n  i = l / 360;\n  l = l - i * 360.0;\n  return l;\n}\n\ndouble moon_position(double j, double ls)\n{\n  double ms, l, mm, n, ev, sms, ae, ec;\n  int i;\n\n  /* ls = sun_position(j) */\n  ms = 0.985647332099 * j - 3.762863;\n  if (ms < 0) ms += 360.0;\n  l = 13.176396 * j + 64.975464;\n  i = l / 360;\n  l = l - i * 360.0;\n  if (l < 0) l += 360.0;\n  mm = l - 0.1114041 * j - 349.383063;\n  i = mm / 360;\n  mm -= i * 360.0;\n  n = 151.950429 - 0.0529539 * j;\n  i = n / 360;\n  n -= i * 360.0;\n  ev = 1.2739 * sin((2 * (l - ls) - mm) * RAD);\n  sms = sin(ms * RAD);\n  ae = 0.1858 * sms;\n  mm += ev - ae - 0.37 * sms;\n  ec = 6.2886 * sin(mm * RAD);\n  l += ev + ec - ae + 0.214 * sin(2 * mm * RAD);\n  l = 0.6583 * sin(2 * (l - ls) * RAD) + l;\n  return l;\n}\n\nuint8_t moon_phase(int year, int month, int day, double hour, int* ip)\n{\n  double j = Julian(year, month, (double)day + hour / 24.0) - 2444238.5;\n  double ls = sun_position(j);\n  double lm = moon_position(j, ls);\n\n  double t = lm - ls;\n  if (t < 0) t += 360;\n\n  *ip = (int)((t + 22.5)/45) & 0x7;        // Moon state 0-7 for moonPhase[] index\n  return ((int)((t + 7.5)/15) + 23) % 24;  // Moon state 0-23 for icon bitmap\n\n  //return 100.0 * ((1.0 - cos((lm - ls) * RAD)) / 2) + 0.5; // percent illuminated\n}\n"
  },
  {
    "path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather_LittleFS/NTP_Time.h",
    "content": "//====================================================================================\n//                                  Libraries\n//====================================================================================\n\n// Time library:\n// https://github.com/PaulStoffregen/Time\n#include <Time.h>\n\n// Time zone correction library:\n// https://github.com/JChristensen/Timezone\n#include <Timezone.h>\n\n// Libraries built into IDE\n#ifdef ESP8266\n#include <ESP8266WiFi.h>\n#else\n#include <WiFi.h>\n#endif\n\n#include <WiFiUdp.h>\n\n// A UDP instance to let us send and receive packets over UDP\nWiFiUDP udp;\n\n//====================================================================================\n//                                  Settings\n//====================================================================================\n\n#ifdef ESP32 // Temporary fix, ESP8266 fails to communicate with some servers...\n// Try to use pool url instead so the server IP address is looked up from those available\n// (use a pool server in your own country to improve response time and reliability)\n//const char* ntpServerName = \"time.nist.gov\";\n//const char* ntpServerName = \"pool.ntp.org\";\nconst char* ntpServerName = \"time.google.com\";\n#else\n// Try to use pool url instead so the server IP address is looked up from those available\n// (use a pool server in your own country to improve response time and reliability)\n// const char* ntpServerName = \"time.nist.gov\";\nconst char* ntpServerName = \"pool.ntp.org\";\n//const char* ntpServerName = \"time.google.com\";\n#endif\n\n// Try not to use hard-coded IP addresses which might change, you can if you want though...\n//IPAddress timeServerIP(129, 6, 15, 30);   // time-c.nist.gov NTP server\n//IPAddress timeServerIP(24, 56, 178, 140); // wwv.nist.gov NTP server\nIPAddress timeServerIP;                     // Use server pool\n\n// Example time zone and DST rules, see Timezone library documents to see how\n// to add more time zones https://github.com/JChristensen/Timezone\n\n// Zone reference \"UK\" United Kingdom (London, Belfast)\nTimeChangeRule BST = {\"BST\", Last, Sun, Mar, 1, 60};        //British Summer (Daylight saving) Time\nTimeChangeRule GMT = {\"GMT\", Last, Sun, Oct, 2, 0};         //Standard Time\nTimezone UK(BST, GMT);\n\n// Zone reference \"euCET\" Central European Time (Frankfurt, Paris)\nTimeChangeRule CEST = {\"CEST\", Last, Sun, Mar, 2, 120};     //Central European Summer Time\nTimeChangeRule  CET = {\"CET \", Last, Sun, Oct, 3, 60};      //Central European Standard Time\nTimezone euCET(CEST, CET);\n\n// Zone reference \"ausET\" Australia Eastern Time Zone (Sydney, Melbourne)\nTimeChangeRule aEDT = {\"AEDT\", First, Sun, Oct, 2, 660};    //UTC + 11 hours\nTimeChangeRule aEST = {\"AEST\", First, Sun, Apr, 3, 600};    //UTC + 10 hours\nTimezone ausET(aEDT, aEST);\n\n// Zone reference \"usET US Eastern Time Zone (New York, Detroit)\nTimeChangeRule usEDT = {\"EDT\", Second, Sun, Mar, 2, -240};  //Eastern Daylight Time = UTC - 4 hours\nTimeChangeRule usEST = {\"EST\", First, Sun, Nov, 2, -300};   //Eastern Standard Time = UTC - 5 hours\nTimezone usET(usEDT, usEST);\n\n// Zone reference \"usCT\" US Central Time Zone (Chicago, Houston)\nTimeChangeRule usCDT = {\"CDT\", Second, dowSunday, Mar, 2, -300};\nTimeChangeRule usCST = {\"CST\", First, dowSunday, Nov, 2, -360};\nTimezone usCT(usCDT, usCST);\n\n// Zone reference \"usMT\" US Mountain Time Zone (Denver, Salt Lake City)\nTimeChangeRule usMDT = {\"MDT\", Second, dowSunday, Mar, 2, -360};\nTimeChangeRule usMST = {\"MST\", First, dowSunday, Nov, 2, -420};\nTimezone usMT(usMDT, usMST);\n\n// Zone reference \"usAZ\" Arizona is US Mountain Time Zone but does not use DST\nTimezone usAZ(usMST, usMST);\n\n// Zone reference \"usPT\" US Pacific Time Zone (Las Vegas, Los Angeles)\nTimeChangeRule usPDT = {\"PDT\", Second, dowSunday, Mar, 2, -420};\nTimeChangeRule usPST = {\"PST\", First, dowSunday, Nov, 2, -480};\nTimezone usPT(usPDT, usPST);\n\n\n//====================================================================================\n//                                  Variables\n//====================================================================================\nTimeChangeRule *tz1_Code;   // Pointer to the time change rule, use to get the TZ abbrev, e.g. \"GMT\"\n\ntime_t utc = 0;\n\nbool timeValid = false;\n\nunsigned int localPort = 2390;      // local port to listen for UDP packets\n\nconst int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message\n\nbyte packetBuffer[ NTP_PACKET_SIZE ]; //buffer to hold incoming and outgoing packets\n\nuint8_t lastMinute = 0;\n\nuint32_t nextSendTime = 0;\nuint32_t newRecvTime = 0;\nuint32_t lastRecvTime = 0;\n\nuint32_t newTickTime = 0;\nuint32_t lastTickTime = 0;\n\nbool rebooted = 1;\n\nuint32_t no_packet_count = 0;\n\n\n//====================================================================================\n//                                    Function prototype\n//====================================================================================\n\nvoid syncTime(void);\nvoid displayTime(void);\nvoid printTime(time_t zone, char *tzCode);\nvoid decodeNTP(void);\nvoid sendNTPpacket(IPAddress& address);\n\n//====================================================================================\n//                                    Update Time\n//====================================================================================\nvoid syncTime(void)\n{\n  // Don't send too often so we don't trigger Denial of Service\n  if (nextSendTime < millis()) {\n    // Get a random server from the pool\n    WiFi.hostByName(ntpServerName, timeServerIP);\n    nextSendTime = millis() + 5000;\n\n    // Flush old late packets\n    while  (udp.parsePacket() > 0)  {                // Is a packet there?\n      Serial.println(\"Reading delayed NTP packet.\"); // Yes\n      udp.read(packetBuffer, NTP_PACKET_SIZE);       // read the packet into the buffer\n    }\n\n    sendNTPpacket(timeServerIP); // send an NTP packet to a time server\n    decodeNTP();\n  }\n}\n\n//====================================================================================\n// Send an NTP request to the time server at the given address\n//====================================================================================\nvoid sendNTPpacket(IPAddress& address)\n{\n  // Serial.println(\"sending NTP packet...\");\n  // set all bytes in the buffer to 0\n  memset(packetBuffer, 0, NTP_PACKET_SIZE);\n  // Initialize values needed to form NTP request\n  // (see URL above for details on the packets)\n  packetBuffer[0] = 0b11100011;   // LI, Version, Mode\n  packetBuffer[1] = 0;            // Stratum, or type of clock\n  packetBuffer[2] = 6;            // Polling Interval\n  packetBuffer[3] = 0xEC;         // Peer Clock Precision\n\n  // 8 bytes of zero for Root Delay & Root Dispersion\n\n  packetBuffer[12]  = 49;\n  packetBuffer[13]  = 0x4E;\n  packetBuffer[14]  = 49;\n  packetBuffer[15]  = 52;\n\n  // all NTP fields have been given values, now\n  // you can send a packet requesting a timestamp:\n  udp.beginPacket(address, 123); //NTP requests are to port 123\n  udp.write(packetBuffer, NTP_PACKET_SIZE);\n  udp.endPacket();\n}\n\n//====================================================================================\n// Decode the NTP message and print status to serial port\n//====================================================================================\nvoid decodeNTP(void)\n{\n  timeValid = false;\n  uint32_t waitTime = millis() + 500;\n  while (millis() < waitTime && !timeValid)\n  {\n    yield();\n    if (udp.parsePacket())\n    {\n      newRecvTime = millis();\n\n      // We've received a packet, read the data from it\n      udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer\n\n      Serial.print(\"\\nNTP response time was  : \");\n      Serial.print(500 - (waitTime - newRecvTime));\n      Serial.println(\" ms\");\n\n      Serial.print(\"Time since last sync is: \");\n      Serial.print((newRecvTime - lastRecvTime) / 1000.0);\n      Serial.println(\" s\");\n      lastRecvTime = newRecvTime;\n\n      // The timestamp starts at byte 40 of the received packet and is four bytes,\n      // or two words, long. First, extract the two words:\n      unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);\n      unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);\n\n      // Combine the four bytes (two words) into a long integer\n      // this is NTP time (seconds since Jan 1 1900):\n      unsigned long secsSince1900 = highWord << 16 | lowWord;\n\n      // Now convert NTP Unix time (Seconds since Jan 1 1900) into everyday time:\n      // UTC time starts on Jan 1 1970. In seconds the difference is 2208988800:\n      utc = secsSince1900 - 2208988800UL;\n\n      setTime(utc);      // Set system clock to utc time (not time zone compensated)\n\n      timeValid = true;\n\n      // Print the hour, minute and second:\n      Serial.print(\"Received NTP UTC time  : \");\n\n      uint8_t hh = hour(utc);\n      Serial.print(hh); // print the hour (86400 equals secs per day)\n\n      Serial.print(':');\n      uint8_t mm = minute(utc);\n      if (mm < 10 ) Serial.print('0');\n      Serial.print(mm); // print the minute (3600 equals secs per minute)\n\n      Serial.print(':');\n      uint8_t ss = second(utc);\n      if ( ss < 10 ) Serial.print('0');\n      Serial.println(ss); // print the second\n    }\n  }\n\n  // Keep a count of missing or bad NTP replies\n\n  if ( timeValid ) {\n    no_packet_count = 0;\n  }\n  else\n  {\n    Serial.println(\"\\nNo NTP reply, trying again in 1 minute...\");\n    no_packet_count++;\n  }\n\n  if (no_packet_count >= 10) {\n    no_packet_count = 0;\n    // TODO: Flag the lack of sync on the display\n    Serial.println(\"\\nNo NTP packet in last 10 minutes\");\n  }\n}\n\n//====================================================================================\n//                                  Debug use only\n//====================================================================================\nvoid printTime(time_t t, char *tzCode)\n{\n  String dateString = dayStr(weekday(t));\n  dateString += \" \";\n  dateString += day(t);\n  if (day(t) == 1 || day(t) == 21 || day(t) == 31) dateString += \"st\";\n  else if (day(t) == 2 || day(t) == 22) dateString += \"nd\";\n  else if (day(t) == 3 || day(t) == 23) dateString += \"rd\";\n  else dateString += \"th\";\n\n  dateString += \" \";\n  dateString += monthStr(month(t));\n  dateString += \" \";\n  dateString += year(t);\n\n  // Print time to serial port\n  Serial.print(hour(t));\n  Serial.print(\":\");\n  Serial.print(minute(t));\n  Serial.print(\":\");\n  Serial.print(second(t));\n  Serial.print(\" \");\n  // Print time t\n  Serial.print(tzCode);\n  Serial.print(\" \");\n\n  // Print date\n  Serial.print(day(t));\n  Serial.print(\"/\");\n  Serial.print(month(t));\n  Serial.print(\"/\");\n  Serial.print(year(t));\n  Serial.print(\"  \");\n\n  // Now test some other functions that might be useful one day!\n  Serial.print(dayStr(weekday(t)));\n  Serial.print(\" \");\n  Serial.print(monthStr(month(t)));\n  Serial.print(\" \");\n  Serial.print(dayShortStr(weekday(t)));\n  Serial.print(\" \");\n  Serial.print(monthShortStr(month(t)));\n  Serial.println();\n}\n\n//====================================================================================\n"
  },
  {
    "path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather_LittleFS/ScreenGrabClient.ino",
    "content": "// This is a copy of the processing sketch that can be used to capture the images\n// Not needed by this sketch, used during development with screenSaver() functions.\n\n// Copy the sketch below into the Processing IDE and remove the /* and */ at the beginning and\n// end.\n\n// The sketch runs in Processing version 3.3 (or later) on a PC, it can be downloaded here:\n// https://processing.org/download/\n\n/*\n\n// This is a Processing sketch, see https://processing.org/ to download the IDE\n\n// The sketch is a client that requests TFT screen-shots from an Arduino board.\n// The Arduino must call a screen-shot server function to respond with pixels.\n\n// It has been created to work with the TFT_eSPI library here:\n// https://github.com/Bodmer/TFT_eSPI\n\n// The sketch must only be run when the designated serial port is available and enumerated\n// otherwise the screen-shot window may freeze and that process will need to be terminated\n// This is a limitation of the Processing environment and not the sketch.\n// If anyone knows how to determine if a serial port is available at start up the PM me\n// on (Bodmer) the Arduino forum.\n\n// The block below contains variables that the user may need to change for a particular setup\n// As a minimum set the serial port and baud rate must be defined. The capture window is\n// automatically resized for landscape, portrait and different TFT resolutions.\n\n// Captured images are stored in the sketch folder, use the Processing IDE \"Sketch\" menu\n// option \"Show Sketch Folder\" or press Ctrl+K\n\n// Created by: Bodmer  5/3/17\n// Updated by: Bodmer 12/3/17\n// Version: 0.07\n\n// MIT licence applies, all text above must be included in derivative works\n\n\n// ###########################################################################################\n// #                  These are the values to change for a particular setup                  #\n//                                                                                           #\nint serial_port = 0;     // Use enumerated value from list provided when sketch is run       #\n//                                                                                           #\n// On an Arduino Due Programming Port use a baud rate of:115200)                             #\n// On an Arduino Due Native USB Port use a baud rate of any value                            #\nint serial_baud_rate = 250000; //                                                            #\n//                                                                                           #\n// Change the image file type saved here, comment out all but one                            #\n//String image_type = \".jpg\"; //                                                             #\nString image_type = \".png\";   // Lossless compression                                        #\n//String image_type = \".bmp\"; //                                                             #\n//String image_type = \".tif\"; //                                                             #\n//                                                                                           #\nboolean save_border = true;   // Save the image with a border                                #\nint border = 5;               // Border pixel width                                          #\nboolean fade = false;         // Fade out image after saving                                 #\n//                                                                                           #\nint max_images = 100; // Maximum of numbered file images before over-writing files           #\n//                                                                                           #\nint max_allowed  = 1000; // Maximum number of save images allowed before a restart           #\n//                                                                                           #\n// #                   End of the values to change for a particular setup                    #\n// ###########################################################################################\n\n// These are default values, this sketch obtains the actual values from the Arduino board\nint tft_width  = 480;    // default TFT width  (automatic - sent by Arduino)\nint tft_height = 480;    // default TFT height (automatic - sent by Arduino)\nint color_bytes = 2;     // 2 for 16 bit, 3 for three RGB bytes (automatic - sent by Arduino)\n\nimport processing.serial.*;\n\nSerial serial;           // Create an instance called serial\n\nint serialCount = 0;     // Count of colour bytes arriving\n\n// Stage window graded background colours\ncolor bgcolor1 = color(0, 100, 104);      // Arduino IDE style background color 1\ncolor bgcolor2 = color(77, 183, 187);     // Arduino IDE style background color 2\n//color bgcolor2 = color(255, 255, 255);  // White\n\n// TFT image frame greyscale value (dark grey)\ncolor frameColor = 42;\n\ncolor buttonStopped = color(255, 0, 0);\ncolor buttonRunning = color(128, 204, 206);\ncolor buttonDimmed  = color(180, 0, 0);\nboolean dimmed   = false;\nboolean running  = true;\nboolean mouseClick = false;\n\nint[] rgb = new int[3]; // Buffer for the colour bytes\nint indexRed   = 0;     // Colour byte index in the array\nint indexGreen = 1;\nint indexBlue  = 2;\n\nint n = 0;\n\nint x_offset = (500 - tft_width) /2; // Image offsets in the window\nint y_offset = 20;\n\nint xpos = 0, ypos = 0; // Current pixel position\n\nint beginTime     = 0;\nint pixelWaitTime = 1000;  // Maximum 1000ms wait for image pixels to arrive\nint lastPixelTime = 0;     // Time that \"image send\" command was sent\n\nint requestTime = 0;\nint requestCount = 0;\n\nint state = 0;  // State machine current state\n\nint   progress_bar = 0; // Console progress bar dot count\nint   pixel_count  = 0; // Number of pixels read for 1 screen\nfloat percentage   = 0; // Percentage of pixels received\n\nint  saved_image_count = 0; // Stats - number of images processed\nint  bad_image_count  = 0;  // Stats - number of images that had lost pixels\nString filename = \"\";\n\nint drawLoopCount = 0;      // Used for the fade out\n\nvoid setup() {\n\n  size(500, 540);  // Stage size, can handle 480 pixels wide screen\n  noStroke();      // No border on the next thing drawn\n  noSmooth();      // No anti-aliasing to avoid adjacent pixel colour merging\n\n  // Graded background and title\n  drawWindow();\n\n  frameRate(2000); // High frame rate so draw() loops fast\n\n  // Print a list of the available serial ports\n  println(\"-----------------------\");\n  println(\"Available Serial Ports:\");\n  println(\"-----------------------\");\n  printArray(Serial.list());\n  println(\"-----------------------\");\n\n  print(\"Port currently used: [\");\n  print(serial_port);\n  println(\"]\");\n\n  String portName = Serial.list()[serial_port];\n\n  serial = new Serial(this, portName, serial_baud_rate);\n\n  state = 99;\n}\n\nvoid draw() {\n\n  if (mouseClick) buttonClicked();\n\n  switch(state) {\n\n  case 0: // Init varaibles, send start request\n    if (running) {\n      tint(0, 0, 0, 255);\n      flushBuffer();\n      println(\"\");\n      print(\"Ready: \");\n\n      xpos = 0;\n      ypos = 0;\n      serialCount = 0;\n      progress_bar = 0;\n      pixel_count = 0;\n      percentage   = 0;\n      drawLoopCount = frameCount;\n      lastPixelTime = millis() + 1000;\n\n      state = 1;\n    } else {\n      if (millis() > beginTime) {\n        beginTime = millis() + 500;\n        dimmed = !dimmed;\n        if (dimmed) drawButton(buttonDimmed);\n        else drawButton(buttonStopped);\n      }\n    }\n    break;\n\n  case 1: // Console message, give server some time\n    print(\"requesting image \");\n    serial.write(\"S\");\n    delay(10);\n    beginTime = millis();\n    requestTime = millis() + 1000;\n    requestCount = 1;\n    state = 2;\n    break;\n\n  case 2: // Get size and set start time for rendering duration report\n    if (millis() > requestTime) {\n      requestCount++;\n      print(\"*\");\n      serial.clear();\n      serial.write(\"S\");\n      if (requestCount > 32) {\n        requestCount = 0;\n        System.err.println(\" - no response!\");\n        state = 0;\n      }\n      requestTime = millis() + 1000;\n    }\n    if ( getSize() == true ) { // Go to next state when we have the size and bits per pixel\n      getFilename();\n      flushBuffer(); // Precaution in case image header size increases in later versions\n      lastPixelTime = millis() + 1000;\n      beginTime = millis();\n      state = 3;\n    }\n    break;\n\n  case 3: // Request pixels and render returned RGB values\n    state = renderPixels(); // State will change when all pixels are rendered\n\n    // Request more pixels, changing the number requested allows the average transfer rate to\n    // be controlled. The pixel transfer rate is dependant on four things:\n    //    1. The frame rate defined in this Processing sketch in setup()\n    //    2. The baud rate of the serial link (~10 bit periods per byte)\n    //    3. The number of request bytes 'R' sent in the lines below\n    //    4. The number of pixels sent in a burst by the server sketch (defined via NPIXELS)\n\n    //serial.write(\"RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR\"); // 32 x NPIXELS more\n    serial.write(\"RRRRRRRRRRRRRRRR\"); // 16 x NPIXELS more\n    //serial.write(\"RRRRRRRR\"); // 8 x NPIXELS more\n    //serial.write(\"RRRR\"); // 4 x NPIXELS more\n    //serial.write(\"RR\"); // 2 x NPIXELS more\n    //serial.write(\"R\"); // 1 x NPIXELS more\n    if (!running) state = 4;\n    break;\n\n  case 4: // Pixel receive time-out, flush serial buffer\n    flushBuffer();\n    state = 6;\n    break;\n\n  case 5: // Save the image to the sketch folder (Ctrl+K to access)\n    saveScreenshot();\n    saved_image_count++;\n    println(\"Saved image count = \" + saved_image_count);\n    if (bad_image_count > 0) System.err.println(\" Bad image count = \" + bad_image_count);\n    drawLoopCount = frameCount; // Reset value ready for counting in step 6\n    state = 6;\n    break;\n\n  case 6: // Fade the old image if enabled\n    if ( fadedImage() == true ) state = 0; // Go to next state when image has faded\n    break;\n\n  case 99: // Draw image viewer window\n    drawWindow();\n    delay(50); // Delay here seems to be required for the IDE console to get ready\n    state = 0;\n    break;\n\n  default:\n    println(\"\");\n    System.err.println(\"Error state reached - check sketch!\");\n    break;\n  }\n}\n\nvoid drawWindow()\n{\n  // Graded background in Arduino colours\n  for (int i = 0; i < height - 25; i++) {\n    float inter = map(i, 0, height - 25, 0, 1);\n    color c = lerpColor(bgcolor1, bgcolor2, inter);\n    stroke(c);\n    line(0, i, 500, i);\n  }\n  fill(bgcolor2);\n  rect( 0, height-25, width-1, 24);\n  textAlign(CENTER);\n  textSize(20);\n  fill(0);\n  text(\"Bodmer's TFT image viewer\", width/2, height-6);\n\n  if (running) drawButton(buttonRunning);\n  else drawButton(buttonStopped);\n}\n\nvoid flushBuffer()\n{\n  //println(\"Clearing serial pipe after a time-out\");\n  int clearTime = millis() + 50;\n  while ( millis() < clearTime ) serial.clear();\n}\n\nboolean getSize()\n{\n  if ( serial.available() > 6 ) {\n    println();\n    char code = (char)serial.read();\n    if (code == 'W') {\n      tft_width = serial.read()<<8 | serial.read();\n    }\n    code = (char)serial.read();\n    if (code == 'H') {\n      tft_height = serial.read()<<8 | serial.read();\n    }\n    code = (char)serial.read();\n    if (code == 'Y') {\n      int bits_per_pixel = (char)serial.read();\n      if (bits_per_pixel == 24) color_bytes = 3;\n      else color_bytes = 2;\n    }\n    code = (char)serial.read();\n    if (code == '?') {\n      drawWindow();\n\n      x_offset = (500 - tft_width) /2;\n      tint(0, 0, 0, 255);\n      noStroke();\n      fill(frameColor);\n      rect((width - tft_width)/2 - border, y_offset - border, tft_width + 2 * border, tft_height + 2 * border);\n      return true;\n    }\n  }\n  return false;\n}\n\nvoid saveScreenshot()\n{\n  println();\n  if (saved_image_count < max_allowed)\n  {\n  if (filename == \"\") filename = \"tft_screen_\" + (n++);\n  filename = filename  + image_type;\n  println(\"Saving image as \\\"\" + filename + \"\\\"\");\n  if (save_border)\n  {\n    PImage partialSave = get(x_offset - border, y_offset - border, tft_width + 2*border, tft_height + 2*border);\n    partialSave.save(filename);\n  } else {\n    PImage partialSave = get(x_offset, y_offset, tft_width, tft_height);\n    partialSave.save(filename);\n  }\n\n  if (n>=max_images) n = 0;\n  }\n  else\n  {\n    System.err.println(max_allowed + \" saved image count exceeded, restart the sketch\");\n  }\n}\n\nvoid getFilename()\n{\n  int readTime = millis() + 20;\n  int inByte = 0;\n  filename = \"\";\n  while ( serial.available() > 0 && millis() < readTime && inByte != '.')\n  {\n    inByte = serial.read();\n    if (inByte == ' ') inByte = '_';\n    if ( unicodeCheck(inByte) ) filename += (char)inByte;\n  }\n\n  inByte = serial.read();\n       if (inByte == '@') filename += \"_\" + timeCode();\n  else if (inByte == '#') filename += \"_\" + saved_image_count%100;\n  else if (inByte == '%') filename += \"_\" + millis();\n  else if (inByte != '*') filename  = \"\";\n\n  inByte = serial.read();\n       if (inByte == 'j') image_type =\".jpg\";\n  else if (inByte == 'b') image_type =\".bmp\";\n  else if (inByte == 'p') image_type =\".png\";\n  else if (inByte == 't') image_type =\".tif\";\n}\n\nboolean unicodeCheck(int unicode)\n{\n  if (  unicode >= '0' && unicode <= '9' ) return true;\n  if ( (unicode >= 'A' && unicode <= 'Z' ) || (unicode >= 'a' && unicode <= 'z')) return true;\n  if (  unicode == '_' || unicode == '/' ) return true;\n  return false;\n}\n\nString timeCode()\n{\n String timeCode  = (int)year() + \"_\" + (int)month()  + \"_\" + (int)day() + \"_\";\n        timeCode += (int)hour() + \"_\" + (int)minute() + \"_\" + (int)second(); \n return timeCode;\n}\n\nint renderPixels()\n{\n  if ( serial.available() > 0 ) {\n\n    // Add the latest byte from the serial port to array:\n    while (serial.available()>0)\n    {\n      rgb[serialCount++] = serial.read();\n\n      // If we have 3 colour bytes:\n      if ( serialCount >= color_bytes ) {\n        serialCount = 0;\n        pixel_count++;\n        if (color_bytes == 3)\n        {\n          stroke(rgb[indexRed], rgb[indexGreen], rgb[indexBlue], 1000);\n        } else\n        { // Can cater for various byte orders\n          //stroke( (rgb[0] & 0x1F)<<3, (rgb[0] & 0xE0)>>3 | (rgb[1] & 0x07)<<5, (rgb[1] & 0xF8));\n          //stroke( (rgb[1] & 0x1F)<<3, (rgb[1] & 0xE0)>>3 | (rgb[0] & 0x07)<<5, (rgb[0] & 0xF8));\n          stroke( (rgb[0] & 0xF8), (rgb[1] & 0xE0)>>3 | (rgb[0] & 0x07)<<5, (rgb[1] & 0x1F)<<3);\n          //stroke( (rgb[1] & 0xF8), (rgb[0] & 0xE0)>>3 | (rgb[1] & 0x07)<<5, (rgb[0] & 0x1F)<<3);\n        }\n        // We get some pixel merge aliasing if smooth() is defined, so draw pixel twice\n        point(xpos + x_offset, ypos + y_offset);\n        //point(xpos + x_offset, ypos + y_offset);\n\n        lastPixelTime = millis();\n        xpos++;\n        if (xpos >= tft_width) {\n          xpos = 0; \n          progressBar();\n          ypos++;\n          if (ypos>=tft_height) {\n            ypos = 0;\n            if ((int)percentage <100) {\n              while (progress_bar++ < 64) print(\" \");\n              percent(100);\n            }\n            println(\"Image fetch time = \" + (millis()-beginTime)/1000.0 + \" s\");\n            return 5;\n          }\n        }\n      }\n    }\n  } else\n  {\n    if (millis() > (lastPixelTime + pixelWaitTime))\n    {\n      println(\"\");\n      System.err.println(pixelWaitTime + \"ms time-out for pixels exceeded...\");\n      if (pixel_count > 0) {\n        bad_image_count++;\n        System.err.print(\"Pixels missing = \" + (tft_width * tft_height - pixel_count));\n        System.err.println(\", corrupted image not saved\");\n        System.err.println(\"Good image count = \" + saved_image_count);\n        System.err.println(\" Bad image count = \" + bad_image_count);\n      }\n      return 4;\n    }\n  }\n  return 3;\n}\n\nvoid progressBar()\n{\n  progress_bar++;\n  print(\".\");\n  if (progress_bar >63)\n  {\n    progress_bar = 0;\n    percentage = 0.5 + 100 * pixel_count/(0.001 + tft_width * tft_height);\n    percent(percentage);\n  }\n}\n\nvoid percent(float percentage)\n{\n  if (percentage > 100) percentage = 100;\n  println(\" [ \" + (int)percentage + \"% ]\");\n  textAlign(LEFT);\n  textSize(16);\n  noStroke();\n  fill(bgcolor2);\n  rect(10, height - 25, 70, 20);\n  fill(0);\n  text(\" [ \" + (int)percentage + \"% ]\", 10, height-8);\n}\n\nboolean fadedImage()\n{\n  int opacity = frameCount - drawLoopCount;  // So we get increasing fade\n  if (fade)\n  {\n    tint(255, opacity);\n    //image(tft_img, x_offset, y_offset);\n    noStroke();\n    fill(50, 50, 50, opacity);\n    rect( (width - tft_width)/2, y_offset, tft_width, tft_height);\n    delay(10);\n  }\n  if (opacity > 50)       // End fade after 50 cycles\n  {\n    return true;\n  }\n  return false;\n}\n\nvoid drawButton(color buttonColor)\n{\n  stroke(0);\n  fill(buttonColor);\n  rect(500 - 100, 540 - 26, 80, 24);\n  textAlign(CENTER);\n  textSize(20);\n  fill(0);\n  if (running) text(\" Pause \", 500 - 60, height-7);\n  else text(\" Run \", 500 - 60, height-7);\n}\n\nvoid buttonClicked()\n{\n  mouseClick = false;\n  if (running) {\n    running = false;\n    drawButton(buttonStopped);\n    System.err.println(\"\");\n    System.err.println(\"Stopped - click 'Run' button: \");\n    //noStroke();\n    //fill(50);\n    //rect( (width - tft_width)/2, y_offset, tft_width, tft_height);\n    beginTime = millis() + 500;\n    dimmed = false;\n    state = 4;\n  } else {\n    running = true;\n    drawButton(buttonRunning);\n  }\n}\n\nvoid mousePressed() {\n  if (mouseX > (500 - 100) && mouseX < (500 - 20) && mouseY > (540 - 26) && mouseY < (540 - 2)) {\n    mouseClick = true;\n  }\n}\n\n*/\n"
  },
  {
    "path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather_LittleFS/ScreenGrabServer.ino",
    "content": "// Reads a screen image off the TFT and send it to a processing client sketch\n// over the serial port. Use a high baud rate, e.g. for an ESP8266:\n// Serial.begin(921600);\n\n// ONLY works if TFT CGRAM can be read back, not all displays support this!\n// Tested with ILI9341 display\n\n// At 921600 baud a 320 x 240 image with 16 bit colour transfers can be sent to the\n// PC client in ~1.67s and 24 bit colour in ~2.5s which is close to the theoretical\n// minimum transfer time.\n\n// This sketch has been created to work with the TFT_eSPI library here:\n// https://github.com/Bodmer/TFT_eSPI\n\n// Created by: Bodmer 27/1/17\n// Updated by: Bodmer 10/3/17\n// Version: 0.07\n\n// MIT licence applies, all text above must be included in derivative works\n\n//====================================================================================\n//                                  Definitions\n//====================================================================================\n\n#define PIXEL_TIMEOUT 100     // 100ms Time-out between pixel requests\n#define START_TIMEOUT 10000   // 10s Maximum time to wait at start transfer\n\n#define BITS_PER_PIXEL 16     // 24 for RGB colour format, 16 for 565 colour format\n\n// File names must be alpha-numeric characters (0-9, a-z, A-Z) or \"/\" underscore \"_\"\n// other ascii characters are stripped out by client, including / generates\n// sub-directories\n#define DEFAULT_FILENAME \"tft_screenshots/screenshot\" // In case none is specified\n#define FILE_TYPE \"png\"       // jpg, bmp, png, tif are valid\n\n// Filename extension\n// '#' = add 0-9, '@' = add timestamp, '%' add millis() timestamp, '*' = add nothing\n// '@' and '%' will generate new unique filenames, so beware of cluttering up your\n// hard drive with lots of images! The PC client sketch is set to limit the number of\n// saved images to 1000 and will then prompt for a restart.\n#define FILE_EXT  '%'         \n\n// Number of pixels to send in a burst (minimum of 1), no benefit above 8\n// NPIXELS values and render times: 1 = 5.0s, 2 = 1.75s, 4 = 1.68s, 8 = 1.67s\n#define NPIXELS 8  // Must be integer division of both TFT width and TFT height\n\nboolean screenServer(void);\nboolean screenServer(String filename);\nboolean serialScreenServer(String filename);\nvoid sendParameters(String filename);\n\n//====================================================================================\n//                           Screen server call with no filename\n//====================================================================================\n// Start a screen dump server (serial or network) - no filename specified\nboolean screenServer(void)\n{\n  // With no filename the screenshot will be saved with a default name e.g. tft_screen_#.xxx\n  // where # is a number 0-9 and xxx is a file type specified below\n  return screenServer(DEFAULT_FILENAME);\n}\n\n//====================================================================================\n//                           Screen server call with filename\n//====================================================================================\n// Start a screen dump server (serial or network) - filename specified\nboolean screenServer(String filename)\n{\n  boolean result = serialScreenServer(filename); // Screenshot serial port server\n  //boolean result = wifiScreenServer(filename);   // Screenshot WiFi UDP port server (WIP)\n\n  delay(0); // Equivalent to yield() for ESP8266;\n\n  //Serial.println();\n  //if (result) Serial.println(F(\"Screen dump passed :-)\"));\n  //else        Serial.println(F(\"Screen dump failed :-(\"));\n\n  return result;\n}\n\n//====================================================================================\n//                Serial server function that sends the data to the client\n//====================================================================================\nboolean serialScreenServer(String filename)\n{\n  // Precautionary receive buffer garbage flush for 50ms\n  uint32_t clearTime = millis() + 50;\n  while ( millis() < clearTime && Serial.read() >= 0) delay(0); // Equivalent to yield() for ESP8266;\n\n  boolean wait = true;\n  uint32_t lastCmdTime = millis();     // Initialise start of command time-out\n\n  // Wait for the starting flag with a start time-out\n  while (wait)\n  {\n    delay(0); // Equivalent to yield() for ESP8266;\n    // Check serial buffer\n    if (Serial.available() > 0) {\n      // Read the command byte\n      uint8_t cmd = Serial.read();\n      // If it is 'S' (start command) then clear the serial buffer for 100ms and stop waiting\n      if ( cmd == 'S' ) {\n        // Precautionary receive buffer garbage flush for 50ms\n        clearTime = millis() + 50;\n        while ( millis() < clearTime && Serial.read() >= 0) delay(0); // Equivalent to yield() for ESP8266;\n\n        wait = false;           // No need to wait anymore\n        lastCmdTime = millis(); // Set last received command time\n\n        // Send screen size etc using a simple header with delimiters for client checks\n        sendParameters(filename);\n      }\n    }\n    else\n    {\n      // Check for time-out\n      if ( millis() > lastCmdTime + START_TIMEOUT) return false;\n    }\n  }\n\n  uint8_t color[3 * NPIXELS]; // RGB and 565 format color buffer for N pixels\n\n  // Send all the pixels on the whole screen\n  for ( int32_t y = 0; y < tft.height(); y++)\n  {\n    // Increment x by NPIXELS as we send NPIXELS for every byte received\n    for ( int32_t x = 0; x < tft.width(); x += NPIXELS)\n    {\n      delay(0); // Equivalent to yield() for ESP8266;\n\n      // Wait here for serial data to arrive or a time-out elapses\n      while ( Serial.available() == 0 )\n      {\n        if ( millis() > lastCmdTime + PIXEL_TIMEOUT) return false;\n        delay(0); // Equivalent to yield() for ESP8266;\n      }\n\n      // Serial data must be available to get here, read 1 byte and\n      // respond with N pixels, i.e. N x 3 RGB bytes or N x 2 565 format bytes\n      if ( Serial.read() == 'X' ) {\n        // X command byte means abort, so clear the buffer and return\n        clearTime = millis() + 50;\n        while ( millis() < clearTime && Serial.read() >= 0) delay(0); // Equivalent to yield() for ESP8266;\n        return false;\n      }\n      // Save arrival time of the read command (for later time-out check)\n      lastCmdTime = millis();\n\n#if defined BITS_PER_PIXEL && BITS_PER_PIXEL >= 24\n      // Fetch N RGB pixels from x,y and put in buffer\n      tft.readRectRGB(x, y, NPIXELS, 1, color);\n      // Send buffer to client\n      Serial.write(color, 3 * NPIXELS); // Write all pixels in the buffer\n#else\n      // Fetch N 565 format pixels from x,y and put in buffer\n      tft.readRect(x, y, NPIXELS, 1, (uint16_t *)color);\n      // Send buffer to client\n      Serial.write(color, 2 * NPIXELS); // Write all pixels in the buffer\n#endif\n    }\n  }\n\n  Serial.flush(); // Make sure all pixel bytes have been despatched\n\n  return true;\n}\n\n//====================================================================================\n//    Send screen size etc using a simple header with delimiters for client checks\n//====================================================================================\nvoid sendParameters(String filename)\n{\n  Serial.write('W'); // Width\n  Serial.write(tft.width()  >> 8);\n  Serial.write(tft.width()  & 0xFF);\n\n  Serial.write('H'); // Height\n  Serial.write(tft.height() >> 8);\n  Serial.write(tft.height() & 0xFF);\n\n  Serial.write('Y'); // Bits per pixel (16 or 24)\n  Serial.write(BITS_PER_PIXEL);\n\n  Serial.write('?'); // Filename next\n  Serial.print(filename);\n\n  Serial.write('.'); // End of filename marker\n\n  Serial.write(FILE_EXT); // Filename extension identifier\n\n  Serial.write(*FILE_TYPE); // First character defines file type j,b,p,t\n}\n"
  },
  {
    "path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather_LittleFS/TFT_eSPI_OpenWeather_LittleFS.ino",
    "content": "//  Example from OpenWeather library: https://github.com/Bodmer/OpenWeather\n//  Adapted by Bodmer to use the TFT_eSPI library:  https://github.com/Bodmer/TFT_eSPI\n\n//  This sketch is compatible with the Pico W, RP2040 Nano Connect, ESP32 and ESP32 S2, it may\n//  also work on ESP8266 but this has not been tested.\n\n//                           >>>  IMPORTANT  <<<\n//         Modify setup in All_Settings.h tab to configure your location etc\n\n//                >>>  EVEN MORE IMPORTANT TO PREVENT CRASHES <<<\n//>>>>>>  For ESP8266 set LittleFS to at least 2Mbytes before uploading files  <<<<<<\n\n//  ESP8266/ESP32 pin connections to the TFT are defined in the TFT_eSPI library.\n\n//  Original by Daniel Eichhorn, see license at end of file.\n\n//#define SERIAL_MESSAGES // For serial output weather reports\n//#define SCREEN_SERVER   // For dumping screen shots from TFT\n//#define RANDOM_LOCATION // Test only, selects random weather location every refresh\n//#define FORMAT_LittleFS   // Wipe LittleFS and all files!\n\n// This sketch uses font files created from the Noto family of fonts as bitmaps\n// generated from these fonts may be freely distributed:\n// https://www.google.com/get/noto/\n\n// A processing sketch to create new fonts can be found in the Tools folder of TFT_eSPI\n// https://github.com/Bodmer/TFT_eSPI/tree/master/Tools/Create_Smooth_Font/Create_font\n// New fonts can be generated to include language specific characters. The Noto family\n// of fonts has an extensive character set coverage.\n\n// Json streaming parser (do not use IDE library manager version) to use is here:\n// https://github.com/Bodmer/JSON_Decoder\n\n#include <FS.h>\n#include <LittleFS.h>\n\n#define AA_FONT_SMALL \"fonts/NSBold15\" // 15 point Noto sans serif bold\n#define AA_FONT_LARGE \"fonts/NSBold36\" // 36 point Noto sans serif bold\n/***************************************************************************************\n**                          Load the libraries and settings\n***************************************************************************************/\n#include <Arduino.h>\n\n#include <SPI.h>\n#include <TFT_eSPI.h> // https://github.com/Bodmer/TFT_eSPI\n\n// Additional functions\n#include \"GfxUi.h\"          // Attached to this sketch\n\n// Choose library to load\n#ifdef ESP8266\n  #include <ESP8266WiFi.h>\n#elif defined(ARDUINO_ARCH_MBED) || defined(ARDUINO_ARCH_RP2040)\n  #if defined(ARDUINO_RASPBERRY_PI_PICO_W)\n    #include <WiFi.h>\n  #else\n    #include <WiFiNINA.h>\n  #endif\n#else // ESP32\n  #include <WiFi.h>\n#endif\n\n\n// check All_Settings.h for adapting to your needs\n#include \"All_Settings.h\"\n\n#include <JSON_Decoder.h> // https://github.com/Bodmer/JSON_Decoder\n\n#include <OpenWeather.h>  // Latest here: https://github.com/Bodmer/OpenWeather\n\n#include \"NTP_Time.h\"     // Attached to this sketch, see that tab for library needs\n\n/***************************************************************************************\n**                          Define the globals and class instances\n***************************************************************************************/\n\nTFT_eSPI tft = TFT_eSPI();             // Invoke custom library\n\nOW_Weather ow;      // Weather forecast library instance\n\nOW_current *current; // Pointers to structs that temporarily holds weather data\nOW_hourly  *hourly;  // Not used\nOW_daily   *daily;\n\nboolean booted = true;\n\nGfxUi ui = GfxUi(&tft); // Jpeg and bmpDraw functions TODO: pull outside of a class\n\nlong lastDownloadUpdate = millis();\n\n/***************************************************************************************\n**                          Declare prototypes\n***************************************************************************************/\nvoid updateData();\nvoid drawProgress(uint8_t percentage, String text);\nvoid drawTime();\nvoid drawCurrentWeather();\nvoid drawForecast();\nvoid drawForecastDetail(uint16_t x, uint16_t y, uint8_t dayIndex);\nconst char* getMeteoconIcon(uint16_t id, bool today);\nvoid drawAstronomy();\nvoid drawSeparator(uint16_t y);\nvoid fillSegment(int x, int y, int start_angle, int sub_angle, int r, unsigned int colour);\nString strDate(time_t unixTime);\nString strTime(time_t unixTime);\nvoid printWeather(void);\nint leftOffset(String text, String sub);\nint rightOffset(String text, String sub);\nint splitIndex(String text);\n\nbool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t* bitmap)\n{\n   // Stop further decoding as image is running off bottom of screen\n  if ( y >= tft.height() ) return 0;\n\n  // This function will clip the image block rendering automatically at the TFT boundaries\n  tft.pushImage(x, y, w, h, bitmap);\n\n  // Return 1 to decode next block\n  return 1;\n}\n\n/***************************************************************************************\n**                          Setup\n***************************************************************************************/\nvoid setup() {\n  Serial.begin(250000);\n\n  tft.begin();\n  tft.setRotation(0); // For 320x480 screen\n  tft.fillScreen(TFT_BLACK);\n\n  if (!LittleFS.begin()) {\n    Serial.println(\"Flash FS initialisation failed!\");\n    while (1) yield(); // Stay here twiddling thumbs waiting\n  }\n  Serial.println(\"\\nFlash FS available!\");\n\n  // Enable if you want to erase LittleFS, this takes some time!\n  // then disable and reload sketch to avoid reformatting on every boot!\n  #ifdef FORMAT_LittleFS\n    tft.setTextDatum(BC_DATUM); // Bottom Centre datum\n    tft.drawString(\"Formatting LittleFS, so wait!\", 120, 195); LittleFS.format();\n  #endif\n\n  TJpgDec.setJpgScale(1);\n  TJpgDec.setCallback(tft_output);\n  TJpgDec.setSwapBytes(true); // May need to swap the jpg colour bytes (endianess)\n\n  // Draw splash screen\n  if (LittleFS.exists(\"/splash/OpenWeather.jpg\")   == true) {\n    TJpgDec.drawFsJpg(0, 40, \"/splash/OpenWeather.jpg\", LittleFS);\n  }\n\n  delay(2000);\n\n  // Clear bottom section of screen\n  tft.fillRect(0, 206, 240, 320 - 206, TFT_BLACK);\n\n  tft.loadFont(AA_FONT_SMALL, LittleFS);\n  tft.setTextDatum(BC_DATUM); // Bottom Centre datum\n  tft.setTextColor(TFT_LIGHTGREY, TFT_BLACK);\n\n  tft.drawString(\"Original by: blog.squix.org\", 120, 260);\n  tft.drawString(\"Adapted by: Bodmer\", 120, 280);\n\n  tft.setTextColor(TFT_YELLOW, TFT_BLACK);\n\n  delay(2000);\n\n  tft.fillRect(0, 206, 240, 320 - 206, TFT_BLACK);\n\n  tft.drawString(\"Connecting to WiFi\", 120, 240);\n  tft.setTextPadding(240); // Pad next drawString() text to full width to over-write old text\n\n  // Call once for ESP32 and ESP8266\n  #if !defined(ARDUINO_ARCH_MBED)\n    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);\n  #endif\n\n  while (WiFi.status() != WL_CONNECTED) {\n    Serial.print(\".\");\n    #if defined(ARDUINO_ARCH_MBED) || defined(ARDUINO_ARCH_RP2040)\n      if (WiFi.status() != WL_CONNECTED) WiFi.begin(WIFI_SSID, WIFI_PASSWORD);\n    #endif\n    delay(500);\n  }\n  Serial.println();\n\n  tft.setTextDatum(BC_DATUM);\n  tft.setTextPadding(240); // Pad next drawString() text to full width to over-write old text\n  tft.drawString(\" \", 120, 220);  // Clear line above using set padding width\n  tft.drawString(\"Fetching weather data...\", 120, 240);\n\n  // Fetch the time\n  udp.begin(localPort);\n  syncTime();\n\n  tft.unloadFont();\n\n  ow.partialDataSet(true); // Collect a subset of the data available\n}\n\n/***************************************************************************************\n**                          Loop\n***************************************************************************************/\nvoid loop() {\n\n  // Check if we should update weather information\n  if (booted || (millis() - lastDownloadUpdate > 1000UL * UPDATE_INTERVAL_SECS))\n  {\n    updateData();\n    lastDownloadUpdate = millis();\n  }\n\n  // If minute has changed then request new time from NTP server\n  if (booted || minute() != lastMinute)\n  {\n    // Update displayed time first as we may have to wait for a response\n    drawTime();\n    lastMinute = minute();\n\n    // Request and synchronise the local clock\n    syncTime();\n\n    #ifdef SCREEN_SERVER\n      screenServer();\n    #endif\n  }\n\n  booted = false;\n}\n\n/***************************************************************************************\n**                          Fetch the weather data  and update screen\n***************************************************************************************/\n// Update the Internet based information and update screen\nvoid updateData() {\n  // booted = true;  // Test only\n  // booted = false; // Test only\n\n  if (booted) drawProgress(20, \"Updating time...\");\n  else fillSegment(22, 22, 0, (int) (20 * 3.6), 16, TFT_NAVY);\n\n  if (booted) drawProgress(50, \"Updating conditions...\");\n  else fillSegment(22, 22, 0, (int) (50 * 3.6), 16, TFT_NAVY);\n\n  // Create the structures that hold the retrieved weather\n  current = new OW_current;\n  daily =   new OW_daily;\n  hourly =  new OW_hourly;\n\n#ifdef RANDOM_LOCATION // Randomly choose a place on Earth to test icons etc\n  String latitude = \"\";\n  latitude = (random(180) - 90);\n  String longitude = \"\";\n  longitude = (random(360) - 180);\n  Serial.print(\"Lat = \"); Serial.print(latitude);\n  Serial.print(\", Lon = \"); Serial.println(longitude);\n#endif\n\n  bool parsed = ow.getForecast(current, hourly, daily, api_key, latitude, longitude, units, language, true);\n\n  if (parsed) Serial.println(\"Data points received\");\n  else Serial.println(\"Failed to get data points\");\n\n  //Serial.print(\"Free heap = \"); Serial.println(ESP.getFreeHeap(), DEC);\n\n  printWeather(); // For debug, turn on output with #define SERIAL_MESSAGES\n\n  if (booted)\n  {\n    drawProgress(100, \"Done...\");\n    delay(2000);\n    tft.fillScreen(TFT_BLACK);\n  }\n  else\n  {\n    fillSegment(22, 22, 0, 360, 16, TFT_NAVY);\n    fillSegment(22, 22, 0, 360, 22, TFT_BLACK);\n  }\n\n  if (parsed)\n  {\n    tft.loadFont(AA_FONT_SMALL, LittleFS);\n    drawCurrentWeather();\n    drawForecast();\n    drawAstronomy();\n    tft.unloadFont();\n\n    // Update the temperature here so we don't need to keep\n    // loading and unloading font which takes time\n    tft.loadFont(AA_FONT_LARGE, LittleFS);\n    tft.setTextDatum(TR_DATUM);\n    tft.setTextColor(TFT_YELLOW, TFT_BLACK);\n\n    // Font ASCII code 0xB0 is a degree symbol, but o used instead in small font\n    tft.setTextPadding(tft.textWidth(\" -88\")); // Max width of values\n\n    String weatherText = \"\";\n    weatherText = String(current->temp, 0);  // Make it integer temperature\n    tft.drawString(weatherText, 215, 95); //  + \"°\" symbol is big... use o in small font\n    tft.unloadFont();\n  }\n  else\n  {\n    Serial.println(\"Failed to get weather\");\n  }\n\n  // Delete to free up space\n  delete current;\n  delete hourly;\n  delete daily;\n}\n\n/***************************************************************************************\n**                          Update progress bar\n***************************************************************************************/\nvoid drawProgress(uint8_t percentage, String text) {\n  tft.loadFont(AA_FONT_SMALL, LittleFS);\n  tft.setTextDatum(BC_DATUM);\n  tft.setTextColor(TFT_ORANGE, TFT_BLACK);\n  tft.setTextPadding(240);\n  tft.drawString(text, 120, 260);\n\n  ui.drawProgressBar(10, 269, 240 - 20, 15, percentage, TFT_WHITE, TFT_BLUE);\n\n  tft.setTextPadding(0);\n  tft.unloadFont();\n}\n\n/***************************************************************************************\n**                          Draw the clock digits\n***************************************************************************************/\nvoid drawTime() {\n  tft.loadFont(AA_FONT_LARGE, LittleFS);\n\n  // Convert UTC to local time, returns zone code in tz1_Code, e.g \"GMT\"\n  time_t local_time = TIMEZONE.toLocal(now(), &tz1_Code);\n\n  String timeNow = \"\";\n\n  if (hour(local_time) < 10) timeNow += \"0\";\n  timeNow += hour(local_time);\n  timeNow += \":\";\n  if (minute(local_time) < 10) timeNow += \"0\";\n  timeNow += minute(local_time);\n\n  tft.setTextDatum(BC_DATUM);\n  tft.setTextColor(TFT_YELLOW, TFT_BLACK);\n  tft.setTextPadding(tft.textWidth(\" 44:44 \"));  // String width + margin\n  tft.drawString(timeNow, 120, 53);\n\n  drawSeparator(51);\n\n  tft.setTextPadding(0);\n\n  tft.unloadFont();\n}\n\n/***************************************************************************************\n**                          Draw the current weather\n***************************************************************************************/\nvoid drawCurrentWeather() {\n  String date = \"Updated: \" + strDate(current->dt);\n  String weatherText = \"None\";\n\n  tft.setTextDatum(BC_DATUM);\n  tft.setTextColor(TFT_ORANGE, TFT_BLACK);\n  tft.setTextPadding(tft.textWidth(\" Updated: Mmm 44 44:44 \"));  // String width + margin\n  tft.drawString(date, 120, 16);\n\n  String weatherIcon = \"\";\n\n  String currentSummary = current->main;\n  currentSummary.toLowerCase();\n\n  weatherIcon = getMeteoconIcon(current->id, true);\n\n  //uint32_t dt = millis();\n  ui.drawBmp(\"/icon/\" + weatherIcon + \".bmp\", 0, 53);\n\n\n  //Serial.print(\"Icon draw time = \"); Serial.println(millis()-dt);\n\n  // Weather Text\n  if (language == \"en\")\n    weatherText = current->main;\n  else\n    weatherText = current->description;\n\n  tft.setTextDatum(BR_DATUM);\n  tft.setTextColor(TFT_ORANGE, TFT_BLACK);\n\n  int splitPoint = 0;\n  int xpos = 235;\n  splitPoint =  splitIndex(weatherText);\n\n  tft.setTextPadding(xpos - 100);  // xpos - icon width\n  if (splitPoint) tft.drawString(weatherText.substring(0, splitPoint), xpos, 69);\n  else tft.drawString(\" \", xpos, 69);\n  tft.drawString(weatherText.substring(splitPoint), xpos, 86);\n\n  tft.setTextColor(TFT_YELLOW, TFT_BLACK);\n  tft.setTextDatum(TR_DATUM);\n  tft.setTextPadding(0);\n  if (units == \"metric\") tft.drawString(\"oC\", 237, 95);\n  else  tft.drawString(\"oF\", 237, 95);\n\n  //Temperature large digits added in updateData() to save swapping font here\n \n  tft.setTextColor(TFT_ORANGE, TFT_BLACK);\n  weatherText = String(current->wind_speed, 0);\n\n  if (units == \"metric\") weatherText += \" m/s\";\n  else weatherText += \" mph\";\n\n  tft.setTextDatum(TC_DATUM);\n  tft.setTextPadding(tft.textWidth(\"888 m/s\")); // Max string length?\n  tft.drawString(weatherText, 124, 136);\n\n  if (units == \"imperial\")\n  {\n    weatherText = current->pressure;\n    weatherText += \" in\";\n  }\n  else\n  {\n    weatherText = String(current->pressure, 0);\n    weatherText += \" hPa\";\n  }\n\n  tft.setTextDatum(TR_DATUM);\n  tft.setTextPadding(tft.textWidth(\" 8888hPa\")); // Max string length?\n  tft.drawString(weatherText, 230, 136);\n\n  int windAngle = (current->wind_deg + 22.5) / 45;\n  if (windAngle > 7) windAngle = 0;\n  String wind[] = {\"N\", \"NE\", \"E\", \"SE\", \"S\", \"SW\", \"W\", \"NW\" };\n  ui.drawBmp(\"/wind/\" + wind[windAngle] + \".bmp\", 101, 86);\n\n  drawSeparator(153);\n\n  tft.setTextDatum(TL_DATUM); // Reset datum to normal\n  tft.setTextPadding(0);      // Reset padding width to none\n}\n\n/***************************************************************************************\n**                          Draw the 4 forecast columns\n***************************************************************************************/\n// draws the three forecast columns\nvoid drawForecast() {\n  int8_t dayIndex = 1;\n\n  drawForecastDetail(  8, 171, dayIndex++);\n  drawForecastDetail( 66, 171, dayIndex++); // was 95\n  drawForecastDetail(124, 171, dayIndex++); // was 180\n  drawForecastDetail(182, 171, dayIndex  ); // was 180\n  drawSeparator(171 + 69);\n}\n\n/***************************************************************************************\n**                          Draw 1 forecast column at x, y\n***************************************************************************************/\n// helper for the forecast columns\nvoid drawForecastDetail(uint16_t x, uint16_t y, uint8_t dayIndex) {\n\n  if (dayIndex >= MAX_DAYS) return;\n\n  String day  = shortDOW[weekday(TIMEZONE.toLocal(daily->dt[dayIndex], &tz1_Code))];\n  day.toUpperCase();\n\n  tft.setTextDatum(BC_DATUM);\n\n  tft.setTextColor(TFT_ORANGE, TFT_BLACK);\n  tft.setTextPadding(tft.textWidth(\"WWW\"));\n  tft.drawString(day, x + 25, y);\n\n  tft.setTextColor(TFT_WHITE, TFT_BLACK);\n  tft.setTextPadding(tft.textWidth(\"-88   -88\"));\n  String highTemp = String(daily->temp_max[dayIndex], 0);\n  String lowTemp  = String(daily->temp_min[dayIndex], 0);\n  tft.drawString(highTemp + \" \" + lowTemp, x + 25, y + 17);\n\n  String weatherIcon = getMeteoconIcon(daily->id[dayIndex], false);\n\n  ui.drawBmp(\"/icon50/\" + weatherIcon + \".bmp\", x, y + 18);\n\n  tft.setTextPadding(0); // Reset padding width to none\n}\n\n/***************************************************************************************\n**                          Draw Sun rise/set, Moon, cloud cover and humidity\n***************************************************************************************/\nvoid drawAstronomy() {\n\n  tft.setTextDatum(BC_DATUM);\n  tft.setTextColor(TFT_WHITE, TFT_BLACK);\n  tft.setTextPadding(tft.textWidth(\" Last qtr \"));\n\n  time_t local_time = TIMEZONE.toLocal(current->dt, &tz1_Code);\n  uint16_t y = year(local_time);\n  uint8_t  m = month(local_time);\n  uint8_t  d = day(local_time);\n  uint8_t  h = hour(local_time);\n  int      ip;\n  uint8_t icon = moon_phase(y, m, d, h, &ip);\n\n  tft.drawString(moonPhase[ip], 120, 319);\n  ui.drawBmp(\"/moon/moonphase_L\" + String(icon) + \".bmp\", 120 - 30, 318 - 16 - 60);\n\n  tft.setTextDatum(BC_DATUM);\n  tft.setTextColor(TFT_ORANGE, TFT_BLACK);\n  tft.setTextPadding(0); // Reset padding width to none\n  tft.drawString(sunStr, 40, 270);\n\n  tft.setTextDatum(BR_DATUM);\n  tft.setTextColor(TFT_WHITE, TFT_BLACK);\n  tft.setTextPadding(tft.textWidth(\" 88:88 \"));\n\n  String rising = strTime(current->sunrise) + \" \";\n  int dt = rightOffset(rising, \":\"); // Draw relative to colon to them aligned\n  tft.drawString(rising, 40 + dt, 290);\n\n  String setting = strTime(current->sunset) + \" \";\n  dt = rightOffset(setting, \":\");\n  tft.drawString(setting, 40 + dt, 305);\n\n  tft.setTextDatum(BC_DATUM);\n  tft.setTextColor(TFT_ORANGE, TFT_BLACK);\n  tft.drawString(cloudStr, 195, 260);\n\n  String cloudCover = \"\";\n  cloudCover += current->clouds;\n  cloudCover += \"%\";\n\n  tft.setTextDatum(BR_DATUM);\n  tft.setTextColor(TFT_WHITE, TFT_BLACK);\n  tft.setTextPadding(tft.textWidth(\" 100%\"));\n  tft.drawString(cloudCover, 210, 277);\n\n  tft.setTextDatum(BC_DATUM);\n  tft.setTextColor(TFT_ORANGE, TFT_BLACK);\n  tft.drawString(humidityStr, 195, 300 - 2);\n\n  String humidity = \"\";\n  humidity += current->humidity;\n  humidity += \"%\";\n\n  tft.setTextDatum(BR_DATUM);\n  tft.setTextColor(TFT_WHITE, TFT_BLACK);\n  tft.setTextPadding(tft.textWidth(\"100%\"));\n  tft.drawString(humidity, 210, 315);\n\n  tft.setTextPadding(0); // Reset padding width to none\n}\n\n/***************************************************************************************\n**                          Get the icon file name from the index number\n***************************************************************************************/\nconst char* getMeteoconIcon(uint16_t id, bool today)\n{\n  if ( today && id/100 == 8 && (current->dt < current->sunrise || current->dt > current->sunset)) id += 1000; \n\n  if (id/100 == 2) return \"thunderstorm\";\n  if (id/100 == 3) return \"drizzle\";\n  if (id/100 == 4) return \"unknown\";\n  if (id == 500) return \"lightRain\";\n  else if (id == 511) return \"sleet\";\n  else if (id/100 == 5) return \"rain\";\n  if (id >= 611 && id <= 616) return \"sleet\";\n  else if (id/100 == 6) return \"snow\";\n  if (id/100 == 7) return \"fog\";\n  if (id == 800) return \"clear-day\";\n  if (id == 801) return \"partly-cloudy-day\";\n  if (id == 802) return \"cloudy\";\n  if (id == 803) return \"cloudy\";\n  if (id == 804) return \"cloudy\";\n  if (id == 1800) return \"clear-night\";\n  if (id == 1801) return \"partly-cloudy-night\";\n  if (id == 1802) return \"cloudy\";\n  if (id == 1803) return \"cloudy\";\n  if (id == 1804) return \"cloudy\";\n\n  return \"unknown\";\n}\n\n/***************************************************************************************\n**                          Draw screen section separator line\n***************************************************************************************/\n// if you don't want separators, comment out the tft-line\nvoid drawSeparator(uint16_t y) {\n  tft.drawFastHLine(10, y, 240 - 2 * 10, 0x4228);\n}\n\n/***************************************************************************************\n**                          Determine place to split a line line\n***************************************************************************************/\n// determine the \"space\" split point in a long string\nint splitIndex(String text)\n{\n  uint16_t index = 0;\n  while ( (text.indexOf(' ', index) >= 0) && ( index <= text.length() / 2 ) ) {\n    index = text.indexOf(' ', index) + 1;\n  }\n  if (index) index--;\n  return index;\n}\n\n/***************************************************************************************\n**                          Right side offset to a character\n***************************************************************************************/\n// Calculate coord delta from end of text String to start of sub String contained within that text\n// Can be used to vertically right align text so for example a colon \":\" in the time value is always\n// plotted at same point on the screen irrespective of different proportional character widths,\n// could also be used to align decimal points for neat formatting\nint rightOffset(String text, String sub)\n{\n  int index = text.indexOf(sub);\n  return tft.textWidth(text.substring(index));\n}\n\n/***************************************************************************************\n**                          Left side offset to a character\n***************************************************************************************/\n// Calculate coord delta from start of text String to start of sub String contained within that text\n// Can be used to vertically left align text so for example a colon \":\" in the time value is always\n// plotted at same point on the screen irrespective of different proportional character widths,\n// could also be used to align decimal points for neat formatting\nint leftOffset(String text, String sub)\n{\n  int index = text.indexOf(sub);\n  return tft.textWidth(text.substring(0, index));\n}\n\n/***************************************************************************************\n**                          Draw circle segment\n***************************************************************************************/\n// Draw a segment of a circle, centred on x,y with defined start_angle and subtended sub_angle\n// Angles are defined in a clockwise direction with 0 at top\n// Segment has radius r and it is plotted in defined colour\n// Can be used for pie charts etc, in this sketch it is used for wind direction\n#define DEG2RAD 0.0174532925 // Degrees to Radians conversion factor\n#define INC 2 // Minimum segment subtended angle and plotting angle increment (in degrees)\nvoid fillSegment(int x, int y, int start_angle, int sub_angle, int r, unsigned int colour)\n{\n  // Calculate first pair of coordinates for segment start\n  float sx = cos((start_angle - 90) * DEG2RAD);\n  float sy = sin((start_angle - 90) * DEG2RAD);\n  uint16_t x1 = sx * r + x;\n  uint16_t y1 = sy * r + y;\n\n  // Draw colour blocks every INC degrees\n  for (int i = start_angle; i < start_angle + sub_angle; i += INC) {\n\n    // Calculate pair of coordinates for segment end\n    int x2 = cos((i + 1 - 90) * DEG2RAD) * r + x;\n    int y2 = sin((i + 1 - 90) * DEG2RAD) * r + y;\n\n    tft.fillTriangle(x1, y1, x2, y2, x, y, colour);\n\n    // Copy segment end to segment start for next segment\n    x1 = x2;\n    y1 = y2;\n  }\n}\n\n/***************************************************************************************\n**                          Print the weather info to the Serial Monitor\n***************************************************************************************/\nvoid printWeather(void)\n{\n#ifdef SERIAL_MESSAGES\n  Serial.println(\"Weather from OpenWeather\\n\");\n\n  Serial.println(\"############### Current weather ###############\\n\");\n  Serial.print(\"dt (time)          : \"); Serial.println(strDate(current->dt));\n  Serial.print(\"sunrise            : \"); Serial.println(strDate(current->sunrise));\n  Serial.print(\"sunset             : \"); Serial.println(strDate(current->sunset));\n  Serial.print(\"main               : \"); Serial.println(current->main);\n  Serial.print(\"temp               : \"); Serial.println(current->temp);\n  Serial.print(\"humidity           : \"); Serial.println(current->humidity);\n  Serial.print(\"pressure           : \"); Serial.println(current->pressure);\n  Serial.print(\"wind_speed         : \"); Serial.println(current->wind_speed);\n  Serial.print(\"wind_deg           : \"); Serial.println(current->wind_deg);\n  Serial.print(\"clouds             : \"); Serial.println(current->clouds);\n  Serial.print(\"id                 : \"); Serial.println(current->id);\n  Serial.println();\n\n  Serial.println(\"###############  Daily weather  ###############\\n\");\n  Serial.println();\n\n  for (int i = 0; i < 5; i++)\n  {\n    Serial.print(\"dt (time)          : \"); Serial.println(strDate(daily->dt[i]));\n    Serial.print(\"id                 : \"); Serial.println(daily->id[i]);\n    Serial.print(\"temp_max           : \"); Serial.println(daily->temp_max[i]);\n    Serial.print(\"temp_min           : \"); Serial.println(daily->temp_min[i]);\n    Serial.println();\n  }\n\n#endif\n}\n/***************************************************************************************\n**             Convert Unix time to a \"local time\" time string \"12:34\"\n***************************************************************************************/\nString strTime(time_t unixTime)\n{\n  time_t local_time = TIMEZONE.toLocal(unixTime, &tz1_Code);\n\n  String localTime = \"\";\n\n  if (hour(local_time) < 10) localTime += \"0\";\n  localTime += hour(local_time);\n  localTime += \":\";\n  if (minute(local_time) < 10) localTime += \"0\";\n  localTime += minute(local_time);\n\n  return localTime;\n}\n\n/***************************************************************************************\n**  Convert Unix time to a local date + time string \"Oct 16 17:18\", ends with newline\n***************************************************************************************/\nString strDate(time_t unixTime)\n{\n  time_t local_time = TIMEZONE.toLocal(unixTime, &tz1_Code);\n\n  String localDate = \"\";\n\n  localDate += monthShortStr(month(local_time));\n  localDate += \" \";\n  localDate += day(local_time);\n  localDate += \" \" + strTime(unixTime);\n\n  return localDate;\n}\n\n/**The MIT License (MIT)\n  Copyright (c) 2015 by Daniel Eichhorn\n  Permission is hereby granted, free of charge, to any person obtaining a copy\n  of this software and associated documentation files (the \"Software\"), to deal\n  in the Software without restriction, including without limitation the rights\n  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n  copies of the Software, and to permit persons to whom the Software is\n  furnished to do so, subject to the following conditions:\n  The above copyright notice and this permission notice shall be included in all\n  copies or substantial portions of the Software.\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n  AUTHORS OR COPYBR_DATUM HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n  SOFTWARE.\n  See more at http://blog.squix.ch\n*/\n\n//  Changes made by Bodmer:\n\n//  Minor changes to text placement and auto-blanking out old text with background colour padding\n//  Moon phase text added (not provided by OpenWeather)\n//  Forecast text lines are automatically split onto two lines at a central space (some are long!)\n//  Time is printed with colons aligned to tidy display\n//  Min and max forecast temperatures spaced out\n//  New smart splash startup screen and updated progress messages\n//  Display does not need to be blanked between updates\n//  Icons nudged about slightly to add wind direction + speed\n//  Barometric pressure added\n\n//  Adapted to use the OpenWeather library: https://github.com/Bodmer/OpenWeather\n//  Moon phase/rise/set (not provided by OpenWeather) replace with  and cloud cover humidity\n//  Created and added new 100x100 and 50x50 pixel weather icons, these are in the\n//  sketch data folder, press Ctrl+K to view\n//  Add moon icons, eliminate all downloads of icons (may lose server!)\n//  Adapted to use anti-aliased fonts, tweaked coords\n//  Added forecast for 4th day\n//  Added cloud cover and humidity in lieu of Moon rise/set\n//  Adapted to be compatible with ESP32\n"
  },
  {
    "path": "examples/OpenWeather_Forecast_Test/Notes.ino",
    "content": "/*\n\n    [units] should be one of the following:\n\n    metric\n    imperial\n\n    lang=[language] optional\n\n    Return summary properties in the desired language.\n    (Note that units in the summary will be set according to the units parameter,\n    so be sure to set both parameters appropriately.) language may be:\n\n    af: Africans\n    ar: Arabic\n    az: Azerbaijani\n    bg: Bulgarian\n    ca: Catalan\n    cs: Czech\n    da: Danish\n    de: German\n    el: Greek\n    en: English (which is the default)\n    eu: Basque\n    fa: Persian (Farsi)\n    fi: Finnish\n    fr: French\n    gl: Galician\n    he: Hebrew\n    hi: Hindi\n    hr: Croatian\n    hu: Hungarian\n    id: Indonesian\n    it: Italian\n    ja: Japanese\n    kr: Korean\n    la: Latvian\n    lt: Lithuanian\n    mk: Macedonian\n    no: Norwegian Bokmål\n    nl: Dutch\n    pl: Polish\n    pt: Portuguese\n    pt_br: Português Brasil\n    ro: Romanian\n    ru: Russian\n    sk: Slovak\n    sl: Slovenian\n    sp, es: Spanish\n    sr: Serbian\n    sv, se: Swedish\n    th: Thai\n    tr: Turkish\n    ua, uk: Ukrainian\n    vi: Vietnamese\n    zh_cn: Chinese Simplified\n    zh-tw: Chinese Traditional\n    zu: Zulu\n\n*/\n"
  },
  {
    "path": "examples/OpenWeather_Forecast_Test/OpenWeather_Forecast_Test.ino",
    "content": "// Sketch for ESP32, ESP8266, RP2040 Pico W, RP2040 Nano Connect\n// it will run on a \"bare\" board ans reports via Serial messages.\n\n// It fetches the Weather Forecast from OpenWeather and is\n// an example from the library here:\n// https://github.com/Bodmer/OpenWeather\n\n// Sign up for a key and read API configuration info here:\n// https://openweathermap.org/\n\n// You can change the \"User_Setup.h\" file inside the OpenWeather\n// to shows the data stream from the server\n\n// Choose library to load\n#ifdef ESP8266\n#include <ESP8266WiFi.h>\n#include <WiFiClientSecure.h>\n#else // ESP32, Pico W, RP2040 Nano Connect\n#include <WiFi.h>\n#endif\n\n#include <JSON_Decoder.h>\n\n#include <OpenWeather.h>\n\n// Just using this library for unix time conversion\n#include <Time.h>\n\n// =====================================================\n// ========= User configured stuff starts here =========\n// Further configuration settings can be found in the\n// OpenWeather library \"User_Setup.h\" file\n\n#define TIME_OFFSET 1UL * 3600UL // UTC + 0 hour\n\n// Change to suit your WiFi router\n#define WIFI_SSID     \"Your_SSID\"\n#define WIFI_PASSWORD \"Your_password\"\n\n// OpenWeather API Details, replace x's with your API key\nString api_key = \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"; // Obtain this from your OpenWeather account\n\n// Set both your longitude and latitude to at least 4 decimal places\nString latitude =  \"27.9881\"; // 90.0000 to -90.0000 negative for Southern hemisphere\nString longitude = \"86.9250\"; // 180.000 to -180.000 negative for West\n\nString units = \"metric\";  // or \"imperial\"\nString language = \"en\";   // See notes tab\n\n// =========  User configured stuff ends here  =========\n// =====================================================\n\nOW_Weather ow; // Weather forecast library instance\n\nvoid setup() {\n  Serial.begin(250000); // Fast to stop it holding up the stream\n\n  Serial.printf(\"\\n\\nConnecting to %s\\n\", WIFI_SSID);\n\n  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);\n\n  while (WiFi.status() != WL_CONNECTED) {\n    delay(500);\n    Serial.print(\".\");\n  }\n\n  Serial.println();\n  Serial.print(\"Connected\\n\");\n}\n\nvoid loop() {\n\n  printForecast();\n  // We can make 1000 requests a day\n  delay(5 * 60 * 1000); // Every 5 minutes = 288 requests per day\n}\n\n/***************************************************************************************\n**                          Send weather info to serial port\n***************************************************************************************/\nvoid printForecast()\n{\n  // Create the structures that hold the retrieved weather\n  OW_forecast  *forecast = new OW_forecast;\n\n  Serial.print(\"\\nRequesting weather information from OpenWeather... \");\n\n  ow.getForecast(forecast, api_key, latitude, longitude, units, language);\n\n  Serial.println(\"Weather from OpenWeather\\n\");\n\n  Serial.print(\"city_name           : \"); Serial.println(forecast->city_name);\n  Serial.print(\"sunrise             : \"); Serial.println(strTime(forecast->sunrise));\n  Serial.print(\"sunset              : \"); Serial.println(strTime(forecast->sunset));\n  Serial.print(\"Latitude            : \"); Serial.println(ow.lat);\n  Serial.print(\"Longitude           : \"); Serial.println(ow.lon);\n  Serial.print(\"Timezone            : \"); Serial.println(forecast->timezone);\n  Serial.println();\n\n  if (forecast)\n  {\n    Serial.println(\"###############  Forecast weather  ###############\\n\");\n    for (int i = 0; i < (MAX_DAYS * 8); i++)\n    {\n      Serial.print(\"3 hourly forecast   \"); if (i < 10) Serial.print(\" \"); Serial.print(i);\n      Serial.println();\n      Serial.print(\"dt (time)        : \"); Serial.print(strTime(forecast->dt[i]));\n\n      Serial.print(\"temp             : \"); Serial.println(forecast->temp[i]);\n      Serial.print(\"temp.min         : \"); Serial.println(forecast->temp_min[i]);\n      Serial.print(\"temp.max         : \"); Serial.println(forecast->temp_max[i]);\n\n      Serial.print(\"pressure         : \"); Serial.println(forecast->pressure[i]);\n      Serial.print(\"sea_level        : \"); Serial.println(forecast->sea_level[i]);\n      Serial.print(\"grnd_level       : \"); Serial.println(forecast->grnd_level[i]);\n      Serial.print(\"humidity         : \"); Serial.println(forecast->humidity[i]);\n\n      Serial.print(\"clouds           : \"); Serial.println(forecast->clouds_all[i]);\n      Serial.print(\"wind_speed       : \"); Serial.println(forecast->wind_speed[i]);\n      Serial.print(\"wind_deg         : \"); Serial.println(forecast->wind_deg[i]);\n      Serial.print(\"wind_gust        : \"); Serial.println(forecast->wind_gust[i]);\n\n      Serial.print(\"visibility       : \"); Serial.println(forecast->visibility[i]);\n      Serial.print(\"pop              : \"); Serial.println(forecast->pop[i]);\n      Serial.println();\n\n      Serial.print(\"dt_txt           : \"); Serial.println(forecast->dt_txt[i]);\n      Serial.print(\"id               : \"); Serial.println(forecast->id[i]);\n      Serial.print(\"main             : \"); Serial.println(forecast->main[i]);\n      Serial.print(\"description      : \"); Serial.println(forecast->description[i]);\n      Serial.print(\"icon             : \"); Serial.println(forecast->icon[i]);\n\n      Serial.println();\n    }\n  }\n  // Delete to free up space and prevent fragmentation as strings change in length\n  delete forecast;\n}\n\n/***************************************************************************************\n**                          Convert unix time to a time string\n***************************************************************************************/\nString strTime(time_t unixTime)\n{\n  unixTime += TIME_OFFSET;\n  return ctime(&unixTime);\n}\n"
  },
  {
    "path": "examples/TFT_eSPI_OpenWeather_LittleFS/All_Settings.h",
    "content": "//  Use the OpenWeather library: https://github.com/Bodmer/OpenWeather\n\n//  The weather icons and fonts are in the sketch data folder, press Ctrl+K\n//  to view.\n\n// The ESP32 board support package 2.0.0 or later must be loaded in the\n// Arduino boards manager to provide LittleFS support.\n\n//            >>>       IMPORTANT TO PREVENT CRASHES      <<<\n//>>>>>>  Set LittleFS to at least 1.5Mbytes before uploading files  <<<<<<\n\n//            >>>           DON'T FORGET THIS             <<<\n//  Upload the fonts and icons to LittleFS using the \"Tools\" menu option.\n\n// You can change the \"User_Setup.h\" file inside the OpenWeather\n// to shows the data stream from the server.\n\n//////////////////////////////\n// Settings defined below\n\n#define WIFI_SSID      \"Your_SSID\"\n#define WIFI_PASSWORD  \"Your_password\"\n\n#define TIMEZONE UK // See NTP_Time.h tab for other \"Zone references\", UK, usMT etc\n\n// Update every 15 minutes, up to 1000 request per day are free (viz average of ~40 per hour)\nconst int UPDATE_INTERVAL_SECS = 15UL * 60UL; // 15 minutes\n\n// Pins for the TFT interface are defined in the User_Config.h file inside the TFT_eSPI library\n\n// For units use \"metric\" or \"imperial\"\nconst String units = \"metric\";\n\n// Sign up for a key and read API configuration info here:\n// https://openweathermap.org/, change x's to your API key\nconst String api_key = \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\";\n\n// Set the forecast longitude and latitude to at least 4 decimal places\nconst String latitude =  \"27.9881\"; // 90.0000 to -90.0000 negative for Southern hemisphere\nconst String longitude = \"86.9250\"; // 180.000 to -180.000 negative for West\n\n// For language codes see https://openweathermap.org/current#multi\nconst String language = \"en\"; // Default language = en = English\n\n// Short day of week abbreviations used in 4 day forecast (change to your language)\nconst String shortDOW [8] = {\"???\", \"SUN\", \"MON\", \"TUE\", \"WED\", \"THU\", \"FRI\", \"SAT\"};\n\n// Change the labels to your language here:\nconst char sunStr[]        = \"Sun\";\nconst char cloudStr[]      = \"Cloud\";\nconst char humidityStr[]   = \"Humidity\";\nconst String moonPhase [8] = {\"New\", \"Waxing\", \"1st qtr\", \"Waxing\", \"Full\", \"Waning\", \"Last qtr\", \"Waning\"};\n\n// End of user settings\n//////////////////////////////\n"
  },
  {
    "path": "examples/TFT_eSPI_OpenWeather_LittleFS/GfxUi.cpp",
    "content": "/**The MIT License (MIT)\nCopyright (c) 2015 by Daniel Eichhorn\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\nSee more at http://blog.squix.ch\n*/\n\n// Adapted by Bodmer to use the TFT_eSPI library: https://github.com/Bodmer/TFT_eSPI\n// Functions no longer needed weeded out, Jpeg decoder functions added and updated\n// drawBMP() updated to buffer input and output pixels and avoid slow seeks\n\n#include \"GfxUi.h\"\n\nGfxUi::GfxUi(TFT_eSPI *tft) {\n  _tft = tft;\n}\n\nvoid GfxUi::drawProgressBar(uint16_t x0, uint16_t y0, uint16_t w, uint16_t h, uint8_t percentage, uint16_t frameColor, uint16_t barColor) {\n  if (percentage == 0) {\n    _tft->fillRoundRect(x0, y0, w, h, 3, TFT_BLACK);\n  }\n  uint8_t margin = 2;\n  uint16_t barHeight = h - 2 * margin;\n  uint16_t barWidth = w - 2 * margin;\n  _tft->drawRoundRect(x0, y0, w, h, 3, frameColor);\n  _tft->fillRect(x0 + margin, y0 + margin, barWidth * percentage / 100.0, barHeight, barColor);\n}\n\n// Bodmer's streamlined x2 faster \"no seek\" version\nvoid GfxUi::drawBmp(String filename, uint16_t x, uint16_t y)\n{\n\n  if ((x >= _tft->width()) || (y >= _tft->height())) return;\n\n  fs::File bmpFS;\n\n  // Check file exists and open it\n  // Serial.println(filename);\n\n  // Note: ESP32 passes \"open\" test even if file does not exist, whereas ESP8266 returns NULL\n  if ( !LittleFS.exists(filename) )\n  {\n    Serial.println(F(\" File not found\")); // Can comment out if not needed\n    return;\n  }\n\n  // Open requested file\n  bmpFS = LittleFS.open(filename, \"r\");\n\n  uint32_t seekOffset;\n  uint16_t w, h, row;\n  uint8_t  r, g, b;\n  bool     oldSwap = false;\n\n  if (read16(bmpFS) == 0x4D42)\n  {\n    read32(bmpFS);\n    read32(bmpFS);\n    seekOffset = read32(bmpFS);\n    read32(bmpFS);\n    w = read32(bmpFS);\n    h = read32(bmpFS);\n\n    if ((read16(bmpFS) == 1) && (read16(bmpFS) == 24) && (read32(bmpFS) == 0))\n    {\n      y += h - 1;\n\n      oldSwap = _tft->getSwapBytes();\n      _tft->setSwapBytes(true);\n      bmpFS.seek(seekOffset);\n\n      // Calculate padding to avoid seek\n      uint16_t padding = (4 - ((w * 3) & 3)) & 3;\n      uint8_t lineBuffer[w * 3 + padding];\n\n      for (row = 0; row < h; row++) {\n        \n        bmpFS.read(lineBuffer, sizeof(lineBuffer));\n        uint8_t*  bptr = lineBuffer;\n        uint16_t* tptr = (uint16_t*)lineBuffer;\n        // Convert 24 to 16 bit colours using the same line buffer for results\n        for (uint16_t col = 0; col < w; col++)\n        {\n          b = *bptr++;\n          g = *bptr++;\n          r = *bptr++;\n          *tptr++ = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);\n        }\n\n        // Push the pixel row to screen, pushImage will crop the line if needed\n        // y is decremented as the BMP image is drawn bottom up\n        _tft->pushImage(x, y--, w, 1, (uint16_t*)lineBuffer);\n      }\n    }\n    else Serial.println(\"BMP format not recognized.\");\n  }\n  _tft->setSwapBytes(oldSwap);\n  bmpFS.close();\n}\n\n// These read 16- and 32-bit types from the SD card file.\n// BMP data is stored little-endian, Arduino is little-endian too.\n// May need to reverse subscript order if porting elsewhere.\n\nuint16_t GfxUi::read16(fs::File &f) {\n  uint16_t result;\n  ((uint8_t *)&result)[0] = f.read(); // LSB\n  ((uint8_t *)&result)[1] = f.read(); // MSB\n  return result;\n}\n\nuint32_t GfxUi::read32(fs::File &f) {\n  uint32_t result;\n  ((uint8_t *)&result)[0] = f.read(); // LSB\n  ((uint8_t *)&result)[1] = f.read();\n  ((uint8_t *)&result)[2] = f.read();\n  ((uint8_t *)&result)[3] = f.read(); // MSB\n  return result;\n}\n\n//====================================================================================\n"
  },
  {
    "path": "examples/TFT_eSPI_OpenWeather_LittleFS/GfxUi.h",
    "content": "/**The MIT License (MIT)\nCopyright (c) 2015 by Daniel Eichhorn\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\nSee more at http://blog.squix.ch\n*/\n\n// Adapted by Bodmer to use the TFT_eSPI library:\n// https://github.com/Bodmer/TFT_eSPI\n\n\n\n#include <TFT_eSPI.h> // Hardware-specific library\n\n#include <FS.h>\n#include <LittleFS.h>\n\n// JPEG decoder library\n#include <TJpg_Decoder.h>\n\n#ifndef _GFX_UI_H\n#define _GFX_UI_H\n\n// Maximum of 85 for BUFFPIXEL as 3 x this value is stored in an 8 bit variable!\n// 32 is an efficient size for LittleFS due to SPI hardware pipeline buffer size\n// A larger value of 80 is better for SD cards\n#define BUFFPIXEL 32\n\nclass GfxUi {\n  public:\n    GfxUi(TFT_eSPI * tft);\n    void drawBmp(String filename, uint16_t x, uint16_t y);\n    void drawProgressBar(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t percentage, uint16_t frameColor, uint16_t barColor);\n    \n  private:\n    TFT_eSPI * _tft;\n    uint16_t read16(fs::File &f);\n    uint32_t read32(fs::File &f);\n\n};\n\n#endif\n"
  },
  {
    "path": "examples/TFT_eSPI_OpenWeather_LittleFS/MoonPhase.ino",
    "content": "/* Moon phase calculation for OpenWeather library*/\n// Adapted by Bodmer from code here:\n// http://www.voidware.com/moon_phase.htm\n\n#include <stdio.h>\n#include <math.h>\n\n#define PI  3.1415926535897932384626433832795\n#define RAD (PI/180.0)\n#define SMALL_FLOAT (1e-12)\n\ndouble Julian(int year, int month, double day)\n{\n  int a, b = 0, c, e;\n  if (month < 3) {\n    year--;\n    month += 12;\n  }\n  if (year > 1582 || (year == 1582 && month > 10) ||\n      (year == 1582 && month == 10 && day > 15)) {\n    a = year / 100;\n    b = 2 - a + a / 4;\n  }\n  c = 365.25 * year;\n  e = 30.6001 * (month + 1);\n  return b + c + e + day + 1720994.5;\n}\n\ndouble sun_position(double j)\n{\n  double n, x, e, l, dl, v;\n  int i;\n\n  n = 360 / 365.2422 * j;\n  i = n / 360;\n  n = n - i * 360.0;\n  x = n - 3.762863;\n  if (x < 0) x += 360;\n  x *= RAD;\n  e = x;\n  do {\n    dl = e - .016718 * sin(e) - x;\n    e = e - dl / (1 - .016718 * cos(e));\n  } while (fabs(dl) >= SMALL_FLOAT);\n  v = 360 / PI * atan(1.01686011182 * tan(e / 2));\n  l = v + 282.596403;\n  i = l / 360;\n  l = l - i * 360.0;\n  return l;\n}\n\ndouble moon_position(double j, double ls)\n{\n  double ms, l, mm, n, ev, sms, ae, ec;\n  int i;\n\n  /* ls = sun_position(j) */\n  ms = 0.985647332099 * j - 3.762863;\n  if (ms < 0) ms += 360.0;\n  l = 13.176396 * j + 64.975464;\n  i = l / 360;\n  l = l - i * 360.0;\n  if (l < 0) l += 360.0;\n  mm = l - 0.1114041 * j - 349.383063;\n  i = mm / 360;\n  mm -= i * 360.0;\n  n = 151.950429 - 0.0529539 * j;\n  i = n / 360;\n  n -= i * 360.0;\n  ev = 1.2739 * sin((2 * (l - ls) - mm) * RAD);\n  sms = sin(ms * RAD);\n  ae = 0.1858 * sms;\n  mm += ev - ae - 0.37 * sms;\n  ec = 6.2886 * sin(mm * RAD);\n  l += ev + ec - ae + 0.214 * sin(2 * mm * RAD);\n  l = 0.6583 * sin(2 * (l - ls) * RAD) + l;\n  return l;\n}\n\nuint8_t moon_phase(int year, int month, int day, double hour, int* ip)\n{\n  double j = Julian(year, month, (double)day + hour / 24.0) - 2444238.5;\n  double ls = sun_position(j);\n  double lm = moon_position(j, ls);\n\n  double t = lm - ls;\n  if (t < 0) t += 360;\n\n  *ip = (int)((t + 22.5)/45) & 0x7;        // Moon state 0-7 for moonPhase[] index\n  return ((int)((t + 7.5)/15) + 23) % 24;  // Moon state 0-23 for icon bitmap\n\n  //return 100.0 * ((1.0 - cos((lm - ls) * RAD)) / 2) + 0.5; // percent illuminated\n}\n"
  },
  {
    "path": "examples/TFT_eSPI_OpenWeather_LittleFS/NTP_Time.h",
    "content": "//====================================================================================\n//                                  Libraries\n//====================================================================================\n\n// Time library:\n// https://github.com/PaulStoffregen/Time\n#include <Time.h>\n\n// Time zone correction library:\n// https://github.com/JChristensen/Timezone\n#include <Timezone.h>\n\n// Libraries built into IDE\n#ifdef ESP8266\n#include <ESP8266WiFi.h>\n#else\n#include <WiFi.h>\n#endif\n\n#include <WiFiUdp.h>\n\n// A UDP instance to let us send and receive packets over UDP\nWiFiUDP udp;\n\n//====================================================================================\n//                                  Settings\n//====================================================================================\n\n#ifdef ESP32 // Temporary fix, ESP8266 fails to communicate with some servers...\n// Try to use pool url instead so the server IP address is looked up from those available\n// (use a pool server in your own country to improve response time and reliability)\n//const char* ntpServerName = \"time.nist.gov\";\n//const char* ntpServerName = \"pool.ntp.org\";\nconst char* ntpServerName = \"time.google.com\";\n#else\n// Try to use pool url instead so the server IP address is looked up from those available\n// (use a pool server in your own country to improve response time and reliability)\n// const char* ntpServerName = \"time.nist.gov\";\nconst char* ntpServerName = \"pool.ntp.org\";\n//const char* ntpServerName = \"time.google.com\";\n#endif\n\n// Try not to use hard-coded IP addresses which might change, you can if you want though...\n//IPAddress timeServerIP(129, 6, 15, 30);   // time-c.nist.gov NTP server\n//IPAddress timeServerIP(24, 56, 178, 140); // wwv.nist.gov NTP server\nIPAddress timeServerIP;                     // Use server pool\n\n// Example time zone and DST rules, see Timezone library documents to see how\n// to add more time zones https://github.com/JChristensen/Timezone\n\n// Zone reference \"UK\" United Kingdom (London, Belfast)\nTimeChangeRule BST = {\"BST\", Last, Sun, Mar, 1, 60};        //British Summer (Daylight saving) Time\nTimeChangeRule GMT = {\"GMT\", Last, Sun, Oct, 2, 0};         //Standard Time\nTimezone UK(BST, GMT);\n\n// Zone reference \"euCET\" Central European Time (Frankfurt, Paris)\nTimeChangeRule CEST = {\"CEST\", Last, Sun, Mar, 2, 120};     //Central European Summer Time\nTimeChangeRule  CET = {\"CET \", Last, Sun, Oct, 3, 60};      //Central European Standard Time\nTimezone euCET(CEST, CET);\n\n// Zone reference \"ausET\" Australia Eastern Time Zone (Sydney, Melbourne)\nTimeChangeRule aEDT = {\"AEDT\", First, Sun, Oct, 2, 660};    //UTC + 11 hours\nTimeChangeRule aEST = {\"AEST\", First, Sun, Apr, 3, 600};    //UTC + 10 hours\nTimezone ausET(aEDT, aEST);\n\n// Zone reference \"usET US Eastern Time Zone (New York, Detroit)\nTimeChangeRule usEDT = {\"EDT\", Second, Sun, Mar, 2, -240};  //Eastern Daylight Time = UTC - 4 hours\nTimeChangeRule usEST = {\"EST\", First, Sun, Nov, 2, -300};   //Eastern Standard Time = UTC - 5 hours\nTimezone usET(usEDT, usEST);\n\n// Zone reference \"usCT\" US Central Time Zone (Chicago, Houston)\nTimeChangeRule usCDT = {\"CDT\", Second, dowSunday, Mar, 2, -300};\nTimeChangeRule usCST = {\"CST\", First, dowSunday, Nov, 2, -360};\nTimezone usCT(usCDT, usCST);\n\n// Zone reference \"usMT\" US Mountain Time Zone (Denver, Salt Lake City)\nTimeChangeRule usMDT = {\"MDT\", Second, dowSunday, Mar, 2, -360};\nTimeChangeRule usMST = {\"MST\", First, dowSunday, Nov, 2, -420};\nTimezone usMT(usMDT, usMST);\n\n// Zone reference \"usAZ\" Arizona is US Mountain Time Zone but does not use DST\nTimezone usAZ(usMST, usMST);\n\n// Zone reference \"usPT\" US Pacific Time Zone (Las Vegas, Los Angeles)\nTimeChangeRule usPDT = {\"PDT\", Second, dowSunday, Mar, 2, -420};\nTimeChangeRule usPST = {\"PST\", First, dowSunday, Nov, 2, -480};\nTimezone usPT(usPDT, usPST);\n\n\n//====================================================================================\n//                                  Variables\n//====================================================================================\nTimeChangeRule *tz1_Code;   // Pointer to the time change rule, use to get the TZ abbrev, e.g. \"GMT\"\n\ntime_t utc = 0;\n\nbool timeValid = false;\n\nunsigned int localPort = 2390;      // local port to listen for UDP packets\n\nconst int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message\n\nbyte packetBuffer[ NTP_PACKET_SIZE ]; //buffer to hold incoming and outgoing packets\n\nuint8_t lastMinute = 0;\n\nuint32_t nextSendTime = 0;\nuint32_t newRecvTime = 0;\nuint32_t lastRecvTime = 0;\n\nuint32_t newTickTime = 0;\nuint32_t lastTickTime = 0;\n\nbool rebooted = 1;\n\nuint32_t no_packet_count = 0;\n\n\n//====================================================================================\n//                                    Function prototype\n//====================================================================================\n\nvoid syncTime(void);\nvoid displayTime(void);\nvoid printTime(time_t zone, char *tzCode);\nvoid decodeNTP(void);\nvoid sendNTPpacket(IPAddress& address);\n\n//====================================================================================\n//                                    Update Time\n//====================================================================================\nvoid syncTime(void)\n{\n  // Don't send too often so we don't trigger Denial of Service\n  if (nextSendTime < millis()) {\n    // Get a random server from the pool\n    WiFi.hostByName(ntpServerName, timeServerIP);\n    nextSendTime = millis() + 5000;\n\n    // Flush old late packets\n    while  (udp.parsePacket() > 0)  {                // Is a packet there?\n      Serial.println(\"Reading delayed NTP packet.\"); // Yes\n      udp.read(packetBuffer, NTP_PACKET_SIZE);       // read the packet into the buffer\n    }\n\n    sendNTPpacket(timeServerIP); // send an NTP packet to a time server\n    decodeNTP();\n  }\n}\n\n//====================================================================================\n// Send an NTP request to the time server at the given address\n//====================================================================================\nvoid sendNTPpacket(IPAddress& address)\n{\n  // Serial.println(\"sending NTP packet...\");\n  // set all bytes in the buffer to 0\n  memset(packetBuffer, 0, NTP_PACKET_SIZE);\n  // Initialize values needed to form NTP request\n  // (see URL above for details on the packets)\n  packetBuffer[0] = 0b11100011;   // LI, Version, Mode\n  packetBuffer[1] = 0;            // Stratum, or type of clock\n  packetBuffer[2] = 6;            // Polling Interval\n  packetBuffer[3] = 0xEC;         // Peer Clock Precision\n\n  // 8 bytes of zero for Root Delay & Root Dispersion\n\n  packetBuffer[12]  = 49;\n  packetBuffer[13]  = 0x4E;\n  packetBuffer[14]  = 49;\n  packetBuffer[15]  = 52;\n\n  // all NTP fields have been given values, now\n  // you can send a packet requesting a timestamp:\n  udp.beginPacket(address, 123); //NTP requests are to port 123\n  udp.write(packetBuffer, NTP_PACKET_SIZE);\n  udp.endPacket();\n}\n\n//====================================================================================\n// Decode the NTP message and print status to serial port\n//====================================================================================\nvoid decodeNTP(void)\n{\n  timeValid = false;\n  uint32_t waitTime = millis() + 500;\n  while (millis() < waitTime && !timeValid)\n  {\n    yield();\n    if (udp.parsePacket())\n    {\n      newRecvTime = millis();\n\n      // We've received a packet, read the data from it\n      udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer\n\n      Serial.print(\"\\nNTP response time was  : \");\n      Serial.print(500 - (waitTime - newRecvTime));\n      Serial.println(\" ms\");\n\n      Serial.print(\"Time since last sync is: \");\n      Serial.print((newRecvTime - lastRecvTime) / 1000.0);\n      Serial.println(\" s\");\n      lastRecvTime = newRecvTime;\n\n      // The timestamp starts at byte 40 of the received packet and is four bytes,\n      // or two words, long. First, extract the two words:\n      unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);\n      unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);\n\n      // Combine the four bytes (two words) into a long integer\n      // this is NTP time (seconds since Jan 1 1900):\n      unsigned long secsSince1900 = highWord << 16 | lowWord;\n\n      // Now convert NTP Unix time (Seconds since Jan 1 1900) into everyday time:\n      // UTC time starts on Jan 1 1970. In seconds the difference is 2208988800:\n      utc = secsSince1900 - 2208988800UL;\n\n      setTime(utc);      // Set system clock to utc time (not time zone compensated)\n\n      timeValid = true;\n\n      // Print the hour, minute and second:\n      Serial.print(\"Received NTP UTC time  : \");\n\n      uint8_t hh = hour(utc);\n      Serial.print(hh); // print the hour (86400 equals secs per day)\n\n      Serial.print(':');\n      uint8_t mm = minute(utc);\n      if (mm < 10 ) Serial.print('0');\n      Serial.print(mm); // print the minute (3600 equals secs per minute)\n\n      Serial.print(':');\n      uint8_t ss = second(utc);\n      if ( ss < 10 ) Serial.print('0');\n      Serial.println(ss); // print the second\n    }\n  }\n\n  // Keep a count of missing or bad NTP replies\n\n  if ( timeValid ) {\n    no_packet_count = 0;\n  }\n  else\n  {\n    Serial.println(\"\\nNo NTP reply, trying again in 1 minute...\");\n    no_packet_count++;\n  }\n\n  if (no_packet_count >= 10) {\n    no_packet_count = 0;\n    // TODO: Flag the lack of sync on the display\n    Serial.println(\"\\nNo NTP packet in last 10 minutes\");\n  }\n}\n\n//====================================================================================\n//                                  Debug use only\n//====================================================================================\nvoid printTime(time_t t, char *tzCode)\n{\n  String dateString = dayStr(weekday(t));\n  dateString += \" \";\n  dateString += day(t);\n  if (day(t) == 1 || day(t) == 21 || day(t) == 31) dateString += \"st\";\n  else if (day(t) == 2 || day(t) == 22) dateString += \"nd\";\n  else if (day(t) == 3 || day(t) == 23) dateString += \"rd\";\n  else dateString += \"th\";\n\n  dateString += \" \";\n  dateString += monthStr(month(t));\n  dateString += \" \";\n  dateString += year(t);\n\n  // Print time to serial port\n  Serial.print(hour(t));\n  Serial.print(\":\");\n  Serial.print(minute(t));\n  Serial.print(\":\");\n  Serial.print(second(t));\n  Serial.print(\" \");\n  // Print time t\n  Serial.print(tzCode);\n  Serial.print(\" \");\n\n  // Print date\n  Serial.print(day(t));\n  Serial.print(\"/\");\n  Serial.print(month(t));\n  Serial.print(\"/\");\n  Serial.print(year(t));\n  Serial.print(\"  \");\n\n  // Now test some other functions that might be useful one day!\n  Serial.print(dayStr(weekday(t)));\n  Serial.print(\" \");\n  Serial.print(monthStr(month(t)));\n  Serial.print(\" \");\n  Serial.print(dayShortStr(weekday(t)));\n  Serial.print(\" \");\n  Serial.print(monthShortStr(month(t)));\n  Serial.println();\n}\n\n//====================================================================================\n"
  },
  {
    "path": "examples/TFT_eSPI_OpenWeather_LittleFS/ScreenGrabClient.ino",
    "content": "// This is a copy of the processing sketch that can be used to capture the images\n// Not needed by this sketch, used during development with screenSaver() functions.\n\n// Copy the sketch below into the Processing IDE and remove the /* and */ at the beginning and\n// end.\n\n// The sketch runs in Processing version 3.3 (or later) on a PC, it can be downloaded here:\n// https://processing.org/download/\n\n/*\n\n// This is a Processing sketch, see https://processing.org/ to download the IDE\n\n// The sketch is a client that requests TFT screen-shots from an Arduino board.\n// The Arduino must call a screen-shot server function to respond with pixels.\n\n// It has been created to work with the TFT_eSPI library here:\n// https://github.com/Bodmer/TFT_eSPI\n\n// The sketch must only be run when the designated serial port is available and enumerated\n// otherwise the screen-shot window may freeze and that process will need to be terminated\n// This is a limitation of the Processing environment and not the sketch.\n// If anyone knows how to determine if a serial port is available at start up the PM me\n// on (Bodmer) the Arduino forum.\n\n// The block below contains variables that the user may need to change for a particular setup\n// As a minimum set the serial port and baud rate must be defined. The capture window is\n// automatically resized for landscape, portrait and different TFT resolutions.\n\n// Captured images are stored in the sketch folder, use the Processing IDE \"Sketch\" menu\n// option \"Show Sketch Folder\" or press Ctrl+K\n\n// Created by: Bodmer  5/3/17\n// Updated by: Bodmer 12/3/17\n// Version: 0.07\n\n// MIT licence applies, all text above must be included in derivative works\n\n\n// ###########################################################################################\n// #                  These are the values to change for a particular setup                  #\n//                                                                                           #\nint serial_port = 0;     // Use enumerated value from list provided when sketch is run       #\n//                                                                                           #\n// On an Arduino Due Programming Port use a baud rate of:115200)                             #\n// On an Arduino Due Native USB Port use a baud rate of any value                            #\nint serial_baud_rate = 250000; //                                                            #\n//                                                                                           #\n// Change the image file type saved here, comment out all but one                            #\n//String image_type = \".jpg\"; //                                                             #\nString image_type = \".png\";   // Lossless compression                                        #\n//String image_type = \".bmp\"; //                                                             #\n//String image_type = \".tif\"; //                                                             #\n//                                                                                           #\nboolean save_border = true;   // Save the image with a border                                #\nint border = 5;               // Border pixel width                                          #\nboolean fade = false;         // Fade out image after saving                                 #\n//                                                                                           #\nint max_images = 100; // Maximum of numbered file images before over-writing files           #\n//                                                                                           #\nint max_allowed  = 1000; // Maximum number of save images allowed before a restart           #\n//                                                                                           #\n// #                   End of the values to change for a particular setup                    #\n// ###########################################################################################\n\n// These are default values, this sketch obtains the actual values from the Arduino board\nint tft_width  = 480;    // default TFT width  (automatic - sent by Arduino)\nint tft_height = 480;    // default TFT height (automatic - sent by Arduino)\nint color_bytes = 2;     // 2 for 16 bit, 3 for three RGB bytes (automatic - sent by Arduino)\n\nimport processing.serial.*;\n\nSerial serial;           // Create an instance called serial\n\nint serialCount = 0;     // Count of colour bytes arriving\n\n// Stage window graded background colours\ncolor bgcolor1 = color(0, 100, 104);      // Arduino IDE style background color 1\ncolor bgcolor2 = color(77, 183, 187);     // Arduino IDE style background color 2\n//color bgcolor2 = color(255, 255, 255);  // White\n\n// TFT image frame greyscale value (dark grey)\ncolor frameColor = 42;\n\ncolor buttonStopped = color(255, 0, 0);\ncolor buttonRunning = color(128, 204, 206);\ncolor buttonDimmed  = color(180, 0, 0);\nboolean dimmed   = false;\nboolean running  = true;\nboolean mouseClick = false;\n\nint[] rgb = new int[3]; // Buffer for the colour bytes\nint indexRed   = 0;     // Colour byte index in the array\nint indexGreen = 1;\nint indexBlue  = 2;\n\nint n = 0;\n\nint x_offset = (500 - tft_width) /2; // Image offsets in the window\nint y_offset = 20;\n\nint xpos = 0, ypos = 0; // Current pixel position\n\nint beginTime     = 0;\nint pixelWaitTime = 1000;  // Maximum 1000ms wait for image pixels to arrive\nint lastPixelTime = 0;     // Time that \"image send\" command was sent\n\nint requestTime = 0;\nint requestCount = 0;\n\nint state = 0;  // State machine current state\n\nint   progress_bar = 0; // Console progress bar dot count\nint   pixel_count  = 0; // Number of pixels read for 1 screen\nfloat percentage   = 0; // Percentage of pixels received\n\nint  saved_image_count = 0; // Stats - number of images processed\nint  bad_image_count  = 0;  // Stats - number of images that had lost pixels\nString filename = \"\";\n\nint drawLoopCount = 0;      // Used for the fade out\n\nvoid setup() {\n\n  size(500, 540);  // Stage size, can handle 480 pixels wide screen\n  noStroke();      // No border on the next thing drawn\n  noSmooth();      // No anti-aliasing to avoid adjacent pixel colour merging\n\n  // Graded background and title\n  drawWindow();\n\n  frameRate(2000); // High frame rate so draw() loops fast\n\n  // Print a list of the available serial ports\n  println(\"-----------------------\");\n  println(\"Available Serial Ports:\");\n  println(\"-----------------------\");\n  printArray(Serial.list());\n  println(\"-----------------------\");\n\n  print(\"Port currently used: [\");\n  print(serial_port);\n  println(\"]\");\n\n  String portName = Serial.list()[serial_port];\n\n  serial = new Serial(this, portName, serial_baud_rate);\n\n  state = 99;\n}\n\nvoid draw() {\n\n  if (mouseClick) buttonClicked();\n\n  switch(state) {\n\n  case 0: // Init varaibles, send start request\n    if (running) {\n      tint(0, 0, 0, 255);\n      flushBuffer();\n      println(\"\");\n      print(\"Ready: \");\n\n      xpos = 0;\n      ypos = 0;\n      serialCount = 0;\n      progress_bar = 0;\n      pixel_count = 0;\n      percentage   = 0;\n      drawLoopCount = frameCount;\n      lastPixelTime = millis() + 1000;\n\n      state = 1;\n    } else {\n      if (millis() > beginTime) {\n        beginTime = millis() + 500;\n        dimmed = !dimmed;\n        if (dimmed) drawButton(buttonDimmed);\n        else drawButton(buttonStopped);\n      }\n    }\n    break;\n\n  case 1: // Console message, give server some time\n    print(\"requesting image \");\n    serial.write(\"S\");\n    delay(10);\n    beginTime = millis();\n    requestTime = millis() + 1000;\n    requestCount = 1;\n    state = 2;\n    break;\n\n  case 2: // Get size and set start time for rendering duration report\n    if (millis() > requestTime) {\n      requestCount++;\n      print(\"*\");\n      serial.clear();\n      serial.write(\"S\");\n      if (requestCount > 32) {\n        requestCount = 0;\n        System.err.println(\" - no response!\");\n        state = 0;\n      }\n      requestTime = millis() + 1000;\n    }\n    if ( getSize() == true ) { // Go to next state when we have the size and bits per pixel\n      getFilename();\n      flushBuffer(); // Precaution in case image header size increases in later versions\n      lastPixelTime = millis() + 1000;\n      beginTime = millis();\n      state = 3;\n    }\n    break;\n\n  case 3: // Request pixels and render returned RGB values\n    state = renderPixels(); // State will change when all pixels are rendered\n\n    // Request more pixels, changing the number requested allows the average transfer rate to\n    // be controlled. The pixel transfer rate is dependant on four things:\n    //    1. The frame rate defined in this Processing sketch in setup()\n    //    2. The baud rate of the serial link (~10 bit periods per byte)\n    //    3. The number of request bytes 'R' sent in the lines below\n    //    4. The number of pixels sent in a burst by the server sketch (defined via NPIXELS)\n\n    //serial.write(\"RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR\"); // 32 x NPIXELS more\n    serial.write(\"RRRRRRRRRRRRRRRR\"); // 16 x NPIXELS more\n    //serial.write(\"RRRRRRRR\"); // 8 x NPIXELS more\n    //serial.write(\"RRRR\"); // 4 x NPIXELS more\n    //serial.write(\"RR\"); // 2 x NPIXELS more\n    //serial.write(\"R\"); // 1 x NPIXELS more\n    if (!running) state = 4;\n    break;\n\n  case 4: // Pixel receive time-out, flush serial buffer\n    flushBuffer();\n    state = 6;\n    break;\n\n  case 5: // Save the image to the sketch folder (Ctrl+K to access)\n    saveScreenshot();\n    saved_image_count++;\n    println(\"Saved image count = \" + saved_image_count);\n    if (bad_image_count > 0) System.err.println(\" Bad image count = \" + bad_image_count);\n    drawLoopCount = frameCount; // Reset value ready for counting in step 6\n    state = 6;\n    break;\n\n  case 6: // Fade the old image if enabled\n    if ( fadedImage() == true ) state = 0; // Go to next state when image has faded\n    break;\n\n  case 99: // Draw image viewer window\n    drawWindow();\n    delay(50); // Delay here seems to be required for the IDE console to get ready\n    state = 0;\n    break;\n\n  default:\n    println(\"\");\n    System.err.println(\"Error state reached - check sketch!\");\n    break;\n  }\n}\n\nvoid drawWindow()\n{\n  // Graded background in Arduino colours\n  for (int i = 0; i < height - 25; i++) {\n    float inter = map(i, 0, height - 25, 0, 1);\n    color c = lerpColor(bgcolor1, bgcolor2, inter);\n    stroke(c);\n    line(0, i, 500, i);\n  }\n  fill(bgcolor2);\n  rect( 0, height-25, width-1, 24);\n  textAlign(CENTER);\n  textSize(20);\n  fill(0);\n  text(\"Bodmer's TFT image viewer\", width/2, height-6);\n\n  if (running) drawButton(buttonRunning);\n  else drawButton(buttonStopped);\n}\n\nvoid flushBuffer()\n{\n  //println(\"Clearing serial pipe after a time-out\");\n  int clearTime = millis() + 50;\n  while ( millis() < clearTime ) serial.clear();\n}\n\nboolean getSize()\n{\n  if ( serial.available() > 6 ) {\n    println();\n    char code = (char)serial.read();\n    if (code == 'W') {\n      tft_width = serial.read()<<8 | serial.read();\n    }\n    code = (char)serial.read();\n    if (code == 'H') {\n      tft_height = serial.read()<<8 | serial.read();\n    }\n    code = (char)serial.read();\n    if (code == 'Y') {\n      int bits_per_pixel = (char)serial.read();\n      if (bits_per_pixel == 24) color_bytes = 3;\n      else color_bytes = 2;\n    }\n    code = (char)serial.read();\n    if (code == '?') {\n      drawWindow();\n\n      x_offset = (500 - tft_width) /2;\n      tint(0, 0, 0, 255);\n      noStroke();\n      fill(frameColor);\n      rect((width - tft_width)/2 - border, y_offset - border, tft_width + 2 * border, tft_height + 2 * border);\n      return true;\n    }\n  }\n  return false;\n}\n\nvoid saveScreenshot()\n{\n  println();\n  if (saved_image_count < max_allowed)\n  {\n  if (filename == \"\") filename = \"tft_screen_\" + (n++);\n  filename = filename  + image_type;\n  println(\"Saving image as \\\"\" + filename + \"\\\"\");\n  if (save_border)\n  {\n    PImage partialSave = get(x_offset - border, y_offset - border, tft_width + 2*border, tft_height + 2*border);\n    partialSave.save(filename);\n  } else {\n    PImage partialSave = get(x_offset, y_offset, tft_width, tft_height);\n    partialSave.save(filename);\n  }\n\n  if (n>=max_images) n = 0;\n  }\n  else\n  {\n    System.err.println(max_allowed + \" saved image count exceeded, restart the sketch\");\n  }\n}\n\nvoid getFilename()\n{\n  int readTime = millis() + 20;\n  int inByte = 0;\n  filename = \"\";\n  while ( serial.available() > 0 && millis() < readTime && inByte != '.')\n  {\n    inByte = serial.read();\n    if (inByte == ' ') inByte = '_';\n    if ( unicodeCheck(inByte) ) filename += (char)inByte;\n  }\n\n  inByte = serial.read();\n       if (inByte == '@') filename += \"_\" + timeCode();\n  else if (inByte == '#') filename += \"_\" + saved_image_count%100;\n  else if (inByte == '%') filename += \"_\" + millis();\n  else if (inByte != '*') filename  = \"\";\n\n  inByte = serial.read();\n       if (inByte == 'j') image_type =\".jpg\";\n  else if (inByte == 'b') image_type =\".bmp\";\n  else if (inByte == 'p') image_type =\".png\";\n  else if (inByte == 't') image_type =\".tif\";\n}\n\nboolean unicodeCheck(int unicode)\n{\n  if (  unicode >= '0' && unicode <= '9' ) return true;\n  if ( (unicode >= 'A' && unicode <= 'Z' ) || (unicode >= 'a' && unicode <= 'z')) return true;\n  if (  unicode == '_' || unicode == '/' ) return true;\n  return false;\n}\n\nString timeCode()\n{\n String timeCode  = (int)year() + \"_\" + (int)month()  + \"_\" + (int)day() + \"_\";\n        timeCode += (int)hour() + \"_\" + (int)minute() + \"_\" + (int)second(); \n return timeCode;\n}\n\nint renderPixels()\n{\n  if ( serial.available() > 0 ) {\n\n    // Add the latest byte from the serial port to array:\n    while (serial.available()>0)\n    {\n      rgb[serialCount++] = serial.read();\n\n      // If we have 3 colour bytes:\n      if ( serialCount >= color_bytes ) {\n        serialCount = 0;\n        pixel_count++;\n        if (color_bytes == 3)\n        {\n          stroke(rgb[indexRed], rgb[indexGreen], rgb[indexBlue], 1000);\n        } else\n        { // Can cater for various byte orders\n          //stroke( (rgb[0] & 0x1F)<<3, (rgb[0] & 0xE0)>>3 | (rgb[1] & 0x07)<<5, (rgb[1] & 0xF8));\n          //stroke( (rgb[1] & 0x1F)<<3, (rgb[1] & 0xE0)>>3 | (rgb[0] & 0x07)<<5, (rgb[0] & 0xF8));\n          stroke( (rgb[0] & 0xF8), (rgb[1] & 0xE0)>>3 | (rgb[0] & 0x07)<<5, (rgb[1] & 0x1F)<<3);\n          //stroke( (rgb[1] & 0xF8), (rgb[0] & 0xE0)>>3 | (rgb[1] & 0x07)<<5, (rgb[0] & 0x1F)<<3);\n        }\n        // We get some pixel merge aliasing if smooth() is defined, so draw pixel twice\n        point(xpos + x_offset, ypos + y_offset);\n        //point(xpos + x_offset, ypos + y_offset);\n\n        lastPixelTime = millis();\n        xpos++;\n        if (xpos >= tft_width) {\n          xpos = 0; \n          progressBar();\n          ypos++;\n          if (ypos>=tft_height) {\n            ypos = 0;\n            if ((int)percentage <100) {\n              while (progress_bar++ < 64) print(\" \");\n              percent(100);\n            }\n            println(\"Image fetch time = \" + (millis()-beginTime)/1000.0 + \" s\");\n            return 5;\n          }\n        }\n      }\n    }\n  } else\n  {\n    if (millis() > (lastPixelTime + pixelWaitTime))\n    {\n      println(\"\");\n      System.err.println(pixelWaitTime + \"ms time-out for pixels exceeded...\");\n      if (pixel_count > 0) {\n        bad_image_count++;\n        System.err.print(\"Pixels missing = \" + (tft_width * tft_height - pixel_count));\n        System.err.println(\", corrupted image not saved\");\n        System.err.println(\"Good image count = \" + saved_image_count);\n        System.err.println(\" Bad image count = \" + bad_image_count);\n      }\n      return 4;\n    }\n  }\n  return 3;\n}\n\nvoid progressBar()\n{\n  progress_bar++;\n  print(\".\");\n  if (progress_bar >63)\n  {\n    progress_bar = 0;\n    percentage = 0.5 + 100 * pixel_count/(0.001 + tft_width * tft_height);\n    percent(percentage);\n  }\n}\n\nvoid percent(float percentage)\n{\n  if (percentage > 100) percentage = 100;\n  println(\" [ \" + (int)percentage + \"% ]\");\n  textAlign(LEFT);\n  textSize(16);\n  noStroke();\n  fill(bgcolor2);\n  rect(10, height - 25, 70, 20);\n  fill(0);\n  text(\" [ \" + (int)percentage + \"% ]\", 10, height-8);\n}\n\nboolean fadedImage()\n{\n  int opacity = frameCount - drawLoopCount;  // So we get increasing fade\n  if (fade)\n  {\n    tint(255, opacity);\n    //image(tft_img, x_offset, y_offset);\n    noStroke();\n    fill(50, 50, 50, opacity);\n    rect( (width - tft_width)/2, y_offset, tft_width, tft_height);\n    delay(10);\n  }\n  if (opacity > 50)       // End fade after 50 cycles\n  {\n    return true;\n  }\n  return false;\n}\n\nvoid drawButton(color buttonColor)\n{\n  stroke(0);\n  fill(buttonColor);\n  rect(500 - 100, 540 - 26, 80, 24);\n  textAlign(CENTER);\n  textSize(20);\n  fill(0);\n  if (running) text(\" Pause \", 500 - 60, height-7);\n  else text(\" Run \", 500 - 60, height-7);\n}\n\nvoid buttonClicked()\n{\n  mouseClick = false;\n  if (running) {\n    running = false;\n    drawButton(buttonStopped);\n    System.err.println(\"\");\n    System.err.println(\"Stopped - click 'Run' button: \");\n    //noStroke();\n    //fill(50);\n    //rect( (width - tft_width)/2, y_offset, tft_width, tft_height);\n    beginTime = millis() + 500;\n    dimmed = false;\n    state = 4;\n  } else {\n    running = true;\n    drawButton(buttonRunning);\n  }\n}\n\nvoid mousePressed() {\n  if (mouseX > (500 - 100) && mouseX < (500 - 20) && mouseY > (540 - 26) && mouseY < (540 - 2)) {\n    mouseClick = true;\n  }\n}\n\n*/\n"
  },
  {
    "path": "examples/TFT_eSPI_OpenWeather_LittleFS/ScreenGrabServer.ino",
    "content": "// Reads a screen image off the TFT and send it to a processing client sketch\n// over the serial port. Use a high baud rate, e.g. for an ESP8266:\n// Serial.begin(921600);\n\n// ONLY works if TFT CGRAM can be read back, not all displays support this!\n// Tested with ILI9341 display\n\n// At 921600 baud a 320 x 240 image with 16 bit colour transfers can be sent to the\n// PC client in ~1.67s and 24 bit colour in ~2.5s which is close to the theoretical\n// minimum transfer time.\n\n// This sketch has been created to work with the TFT_eSPI library here:\n// https://github.com/Bodmer/TFT_eSPI\n\n// Created by: Bodmer 27/1/17\n// Updated by: Bodmer 10/3/17\n// Version: 0.07\n\n// MIT licence applies, all text above must be included in derivative works\n\n//====================================================================================\n//                                  Definitions\n//====================================================================================\n\n#define PIXEL_TIMEOUT 100     // 100ms Time-out between pixel requests\n#define START_TIMEOUT 10000   // 10s Maximum time to wait at start transfer\n\n#define BITS_PER_PIXEL 16     // 24 for RGB colour format, 16 for 565 colour format\n\n// File names must be alpha-numeric characters (0-9, a-z, A-Z) or \"/\" underscore \"_\"\n// other ascii characters are stripped out by client, including / generates\n// sub-directories\n#define DEFAULT_FILENAME \"tft_screenshots/screenshot\" // In case none is specified\n#define FILE_TYPE \"png\"       // jpg, bmp, png, tif are valid\n\n// Filename extension\n// '#' = add 0-9, '@' = add timestamp, '%' add millis() timestamp, '*' = add nothing\n// '@' and '%' will generate new unique filenames, so beware of cluttering up your\n// hard drive with lots of images! The PC client sketch is set to limit the number of\n// saved images to 1000 and will then prompt for a restart.\n#define FILE_EXT  '%'         \n\n// Number of pixels to send in a burst (minimum of 1), no benefit above 8\n// NPIXELS values and render times: 1 = 5.0s, 2 = 1.75s, 4 = 1.68s, 8 = 1.67s\n#define NPIXELS 8  // Must be integer division of both TFT width and TFT height\n\nboolean screenServer(void);\nboolean screenServer(String filename);\nboolean serialScreenServer(String filename);\nvoid sendParameters(String filename);\n\n//====================================================================================\n//                           Screen server call with no filename\n//====================================================================================\n// Start a screen dump server (serial or network) - no filename specified\nboolean screenServer(void)\n{\n  // With no filename the screenshot will be saved with a default name e.g. tft_screen_#.xxx\n  // where # is a number 0-9 and xxx is a file type specified below\n  return screenServer(DEFAULT_FILENAME);\n}\n\n//====================================================================================\n//                           Screen server call with filename\n//====================================================================================\n// Start a screen dump server (serial or network) - filename specified\nboolean screenServer(String filename)\n{\n  boolean result = serialScreenServer(filename); // Screenshot serial port server\n  //boolean result = wifiScreenServer(filename);   // Screenshot WiFi UDP port server (WIP)\n\n  delay(0); // Equivalent to yield() for ESP8266;\n\n  //Serial.println();\n  //if (result) Serial.println(F(\"Screen dump passed :-)\"));\n  //else        Serial.println(F(\"Screen dump failed :-(\"));\n\n  return result;\n}\n\n//====================================================================================\n//                Serial server function that sends the data to the client\n//====================================================================================\nboolean serialScreenServer(String filename)\n{\n  // Precautionary receive buffer garbage flush for 50ms\n  uint32_t clearTime = millis() + 50;\n  while ( millis() < clearTime && Serial.read() >= 0) delay(0); // Equivalent to yield() for ESP8266;\n\n  boolean wait = true;\n  uint32_t lastCmdTime = millis();     // Initialise start of command time-out\n\n  // Wait for the starting flag with a start time-out\n  while (wait)\n  {\n    delay(0); // Equivalent to yield() for ESP8266;\n    // Check serial buffer\n    if (Serial.available() > 0) {\n      // Read the command byte\n      uint8_t cmd = Serial.read();\n      // If it is 'S' (start command) then clear the serial buffer for 100ms and stop waiting\n      if ( cmd == 'S' ) {\n        // Precautionary receive buffer garbage flush for 50ms\n        clearTime = millis() + 50;\n        while ( millis() < clearTime && Serial.read() >= 0) delay(0); // Equivalent to yield() for ESP8266;\n\n        wait = false;           // No need to wait anymore\n        lastCmdTime = millis(); // Set last received command time\n\n        // Send screen size etc using a simple header with delimiters for client checks\n        sendParameters(filename);\n      }\n    }\n    else\n    {\n      // Check for time-out\n      if ( millis() > lastCmdTime + START_TIMEOUT) return false;\n    }\n  }\n\n  uint8_t color[3 * NPIXELS]; // RGB and 565 format color buffer for N pixels\n\n  // Send all the pixels on the whole screen\n  for ( int32_t y = 0; y < tft.height(); y++)\n  {\n    // Increment x by NPIXELS as we send NPIXELS for every byte received\n    for ( int32_t x = 0; x < tft.width(); x += NPIXELS)\n    {\n      delay(0); // Equivalent to yield() for ESP8266;\n\n      // Wait here for serial data to arrive or a time-out elapses\n      while ( Serial.available() == 0 )\n      {\n        if ( millis() > lastCmdTime + PIXEL_TIMEOUT) return false;\n        delay(0); // Equivalent to yield() for ESP8266;\n      }\n\n      // Serial data must be available to get here, read 1 byte and\n      // respond with N pixels, i.e. N x 3 RGB bytes or N x 2 565 format bytes\n      if ( Serial.read() == 'X' ) {\n        // X command byte means abort, so clear the buffer and return\n        clearTime = millis() + 50;\n        while ( millis() < clearTime && Serial.read() >= 0) delay(0); // Equivalent to yield() for ESP8266;\n        return false;\n      }\n      // Save arrival time of the read command (for later time-out check)\n      lastCmdTime = millis();\n\n#if defined BITS_PER_PIXEL && BITS_PER_PIXEL >= 24\n      // Fetch N RGB pixels from x,y and put in buffer\n      tft.readRectRGB(x, y, NPIXELS, 1, color);\n      // Send buffer to client\n      Serial.write(color, 3 * NPIXELS); // Write all pixels in the buffer\n#else\n      // Fetch N 565 format pixels from x,y and put in buffer\n      tft.readRect(x, y, NPIXELS, 1, (uint16_t *)color);\n      // Send buffer to client\n      Serial.write(color, 2 * NPIXELS); // Write all pixels in the buffer\n#endif\n    }\n  }\n\n  Serial.flush(); // Make sure all pixel bytes have been despatched\n\n  return true;\n}\n\n//====================================================================================\n//    Send screen size etc using a simple header with delimiters for client checks\n//====================================================================================\nvoid sendParameters(String filename)\n{\n  Serial.write('W'); // Width\n  Serial.write(tft.width()  >> 8);\n  Serial.write(tft.width()  & 0xFF);\n\n  Serial.write('H'); // Height\n  Serial.write(tft.height() >> 8);\n  Serial.write(tft.height() & 0xFF);\n\n  Serial.write('Y'); // Bits per pixel (16 or 24)\n  Serial.write(BITS_PER_PIXEL);\n\n  Serial.write('?'); // Filename next\n  Serial.print(filename);\n\n  Serial.write('.'); // End of filename marker\n\n  Serial.write(FILE_EXT); // Filename extension identifier\n\n  Serial.write(*FILE_TYPE); // First character defines file type j,b,p,t\n}\n"
  },
  {
    "path": "examples/TFT_eSPI_OpenWeather_LittleFS/TFT_eSPI_OpenWeather_LittleFS.ino",
    "content": "//  Example from OpenWeather library: https://github.com/Bodmer/OpenWeather\n//  Adapted by Bodmer to use the TFT_eSPI library:  https://github.com/Bodmer/TFT_eSPI\n\n//  This sketch is compatible with the RP2040 Nano Connect, ESP32 and ESP32 S2/3, it may\n//  also work on ESP8266 but this has not been tested.\n\n//                           >>>  IMPORTANT  <<<\n//         Modify setup in All_Settings.h tab to configure your location etc\n\n//                >>>  EVEN MORE IMPORTANT TO PREVENT CRASHES <<<\n//>>>>>>  For ESP8266 set LittleFS to at least 2Mbytes before uploading files  <<<<<<\n\n//  ESP8266/ESP32/RP2040 pin connections to the TFT are defined in the TFT_eSPI library.\n\n//  Original by Daniel Eichhorn, see license at end of file.\n\n#define SERIAL_MESSAGES // For serial output weather reports\n//#define SCREEN_SERVER   // For dumping screen shots from TFT\n//#define RANDOM_LOCATION // Test only, selects random weather location every refresh\n//#define FORMAT_LittleFS   // Wipe LittleFS and all files!\n\n// This sketch uses font files created from the Noto family of fonts as bitmaps\n// generated from these fonts may be freely distributed:\n// https://www.google.com/get/noto/\n\n// A processing sketch to create new fonts can be found in the Tools folder of TFT_eSPI\n// https://github.com/Bodmer/TFT_eSPI/tree/master/Tools/Create_Smooth_Font/Create_font\n// New fonts can be generated to include language specific characters. The Noto family\n// of fonts has an extensive character set coverage.\n\n// Json streaming parser (do not use IDE library manager version) to use is here:\n// https://github.com/Bodmer/JSON_Decoder\n\n#include <FS.h>\n#include <LittleFS.h>\n\n#define AA_FONT_SMALL \"fonts/NSBold15\" // 15 point Noto sans serif bold\n#define AA_FONT_LARGE \"fonts/NSBold36\" // 36 point Noto sans serif bold\n/***************************************************************************************\n**                          Load the libraries and settings\n***************************************************************************************/\n#include <Arduino.h>\n\n#include <SPI.h>\n#include <TFT_eSPI.h> // https://github.com/Bodmer/TFT_eSPI\n\n// Additional functions\n#include \"GfxUi.h\"          // Attached to this sketch\n\n// Choose library to load\n#ifdef ESP8266\n  #include <ESP8266WiFi.h>\n#elif defined(ARDUINO_ARCH_MBED) || defined(ARDUINO_ARCH_RP2040)\n  #if defined(ARDUINO_RASPBERRY_PI_PICO_W)\n    #include <WiFi.h>\n  #else\n    #include <WiFiNINA.h>\n  #endif\n#else // ESP32\n  #include <WiFi.h>\n#endif\n\n\n// check All_Settings.h for adapting to your needs\n#include \"All_Settings.h\"\n\n#include <JSON_Decoder.h> // https://github.com/Bodmer/JSON_Decoder\n\n#include <OpenWeather.h>  // Latest here: https://github.com/Bodmer/OpenWeather\n\n#include \"NTP_Time.h\"     // Attached to this sketch, see that tab for library needs\n\n/***************************************************************************************\n**                          Define the globals and class instances\n***************************************************************************************/\n\nTFT_eSPI tft = TFT_eSPI();             // Invoke custom library\n\nOW_Weather ow;      // Weather forecast library instance\n\nOW_forecast  *forecast;\n\nboolean booted = true;\n\nGfxUi ui = GfxUi(&tft); // Jpeg and bmpDraw functions\n\nlong lastDownloadUpdate = millis();\n\n/***************************************************************************************\n**                          Declare prototypes\n***************************************************************************************/\nvoid updateData();\nvoid drawProgress(uint8_t percentage, String text);\nvoid drawTime();\nvoid drawCurrentWeather();\nvoid drawForecast();\nvoid drawForecastDetail(uint16_t x, uint16_t y, uint8_t dayIndex);\nconst char* getMeteoconIcon(uint16_t id, bool today);\nvoid drawAstronomy();\nvoid drawSeparator(uint16_t y);\nvoid fillSegment(int x, int y, int start_angle, int sub_angle, int r, unsigned int colour);\nString strDate(time_t unixTime);\nString strTime(time_t unixTime);\nvoid printWeather(void);\nint leftOffset(String text, String sub);\nint rightOffset(String text, String sub);\nint splitIndex(String text);\nint getNextDayIndex(void);\n\nbool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t* bitmap)\n{\n   // Stop further decoding as image is running off bottom of screen\n  if ( y >= tft.height() ) return 0;\n\n  // This function will clip the image block rendering automatically at the TFT boundaries\n  tft.pushImage(x, y, w, h, bitmap);\n\n  // Return 1 to decode next block\n  return 1;\n}\n\n/***************************************************************************************\n**                          Setup\n***************************************************************************************/\nvoid setup() {\n  Serial.begin(250000);\n\n  tft.begin();\n  tft.setRotation(0); // For 320x480 screen\n  tft.fillScreen(TFT_BLACK);\n\n  if (!LittleFS.begin()) {\n    Serial.println(\"Flash FS initialisation failed!\");\n    while (1) yield(); // Stay here twiddling thumbs waiting\n  }\n  Serial.println(\"\\nFlash FS available!\");\n\n  // Enable if you want to erase LittleFS, this takes some time!\n  // then disable and reload sketch to avoid reformatting on every boot!\n  #ifdef FORMAT_LittleFS\n    tft.setTextDatum(BC_DATUM); // Bottom Centre datum\n    tft.drawString(\"Formatting LittleFS, so wait!\", 120, 195); LittleFS.format();\n  #endif\n\n  TJpgDec.setJpgScale(1);\n  TJpgDec.setCallback(tft_output);\n  TJpgDec.setSwapBytes(true); // May need to swap the jpg colour bytes (endianess)\n\n  // Draw splash screen\n  if (LittleFS.exists(\"/splash/OpenWeather.jpg\")   == true) {\n    TJpgDec.drawFsJpg(0, 40, \"/splash/OpenWeather.jpg\", LittleFS);\n  }\n\n  delay(2000);\n\n  // Clear bottom section of screen\n  tft.fillRect(0, 206, 240, 320 - 206, TFT_BLACK);\n\n  tft.loadFont(AA_FONT_SMALL, LittleFS);\n  tft.setTextDatum(BC_DATUM); // Bottom Centre datum\n  tft.setTextColor(TFT_LIGHTGREY, TFT_BLACK);\n\n  tft.drawString(\"Original by: blog.squix.org\", 120, 260);\n  tft.drawString(\"Adapted by: Bodmer\", 120, 280);\n\n  tft.setTextColor(TFT_YELLOW, TFT_BLACK);\n\n  delay(2000);\n\n  tft.fillRect(0, 206, 240, 320 - 206, TFT_BLACK);\n\n  tft.drawString(\"Connecting to WiFi\", 120, 240);\n  tft.setTextPadding(240); // Pad next drawString() text to full width to over-write old text\n\n  // Call once for ESP32 and ESP8266\n  #if !defined(ARDUINO_ARCH_MBED)\n    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);\n  #endif\n\n  while (WiFi.status() != WL_CONNECTED) {\n    Serial.print(\".\");\n    #if defined(ARDUINO_ARCH_MBED) || defined(ARDUINO_ARCH_RP2040)\n      if (WiFi.status() != WL_CONNECTED) WiFi.begin(WIFI_SSID, WIFI_PASSWORD);\n    #endif\n    delay(500);\n  }\n  Serial.println();\n\n  tft.setTextDatum(BC_DATUM);\n  tft.setTextPadding(240); // Pad next drawString() text to full width to over-write old text\n  tft.drawString(\" \", 120, 220);  // Clear line above using set padding width\n  tft.drawString(\"Fetching weather data...\", 120, 240);\n\n  // Fetch the time\n  udp.begin(localPort);\n  syncTime();\n\n  tft.unloadFont();\n}\n\n/***************************************************************************************\n**                          Loop\n***************************************************************************************/\nvoid loop() {\n\n  // Check if we should update weather information\n  if (booted || (millis() - lastDownloadUpdate > 1000UL * UPDATE_INTERVAL_SECS))\n  {\n    updateData();\n    lastDownloadUpdate = millis();\n  }\n\n  // If minute has changed then request new time from NTP server\n  if (booted || minute() != lastMinute)\n  {\n    // Update displayed time first as we may have to wait for a response\n    drawTime();\n    lastMinute = minute();\n\n    // Request and synchronise the local clock\n    syncTime();\n\n    #ifdef SCREEN_SERVER\n      screenServer();\n    #endif\n  }\n\n  booted = false;\n}\n\n/***************************************************************************************\n**                          Fetch the weather data  and update screen\n***************************************************************************************/\n// Update the Internet based information and update screen\nvoid updateData() {\n  // booted = true;  // Test only\n  // booted = false; // Test only\n\n  if (booted) drawProgress(20, \"Updating time...\");\n  else fillSegment(22, 22, 0, (int) (20 * 3.6), 16, TFT_NAVY);\n\n  if (booted) drawProgress(50, \"Updating conditions...\");\n  else fillSegment(22, 22, 0, (int) (50 * 3.6), 16, TFT_NAVY);\n\n  // Create the structure that holds the retrieved weather\n  forecast = new OW_forecast;\n\n#ifdef RANDOM_LOCATION // Randomly choose a place on Earth to test icons etc\n  String latitude = \"\";\n  latitude = (random(180) - 90);\n  String longitude = \"\";\n  longitude = (random(360) - 180);\n  Serial.print(\"Lat = \"); Serial.print(latitude);\n  Serial.print(\", Lon = \"); Serial.println(longitude);\n#endif\n\n  bool parsed = ow.getForecast(forecast, api_key, latitude, longitude, units, language);\n\n  if (parsed) Serial.println(\"Data points received\");\n  else Serial.println(\"Failed to get data points\");\n\n  //Serial.print(\"Free heap = \"); Serial.println(ESP.getFreeHeap(), DEC);\n\n  printWeather(); // For debug, turn on output with #define SERIAL_MESSAGES\n\n  if (booted)\n  {\n    drawProgress(100, \"Done...\");\n    delay(2000);\n    tft.fillScreen(TFT_BLACK);\n  }\n  else\n  {\n    fillSegment(22, 22, 0, 360, 16, TFT_NAVY);\n    fillSegment(22, 22, 0, 360, 22, TFT_BLACK);\n  }\n\n  if (parsed)\n  {\n    tft.loadFont(AA_FONT_SMALL, LittleFS);\n    drawCurrentWeather();\n    drawForecast();\n    drawAstronomy();\n    tft.unloadFont();\n\n    // Update the temperature here so we don't need to keep\n    // loading and unloading font which takes time\n    tft.loadFont(AA_FONT_LARGE, LittleFS);\n    tft.setTextDatum(TR_DATUM);\n    tft.setTextColor(TFT_YELLOW, TFT_BLACK);\n\n    // Font ASCII code 0xB0 is a degree symbol, but o used instead in small font\n    tft.setTextPadding(tft.textWidth(\" -88\")); // Max width of values\n\n    String weatherText = \"\";\n    weatherText = String(forecast->temp[0], 0);  // Make it integer temperature\n    tft.drawString(weatherText, 215, 95); //  + \"°\" symbol is big... use o in small font\n    tft.unloadFont();\n  }\n  else\n  {\n    Serial.println(\"Failed to get weather\");\n  }\n\n  // Delete to free up space\n  delete forecast;\n}\n\n/***************************************************************************************\n**                          Update progress bar\n***************************************************************************************/\nvoid drawProgress(uint8_t percentage, String text) {\n  tft.loadFont(AA_FONT_SMALL, LittleFS);\n  tft.setTextDatum(BC_DATUM);\n  tft.setTextColor(TFT_ORANGE, TFT_BLACK);\n  tft.setTextPadding(240);\n  tft.drawString(text, 120, 260);\n\n  ui.drawProgressBar(10, 269, 240 - 20, 15, percentage, TFT_WHITE, TFT_BLUE);\n\n  tft.setTextPadding(0);\n  tft.unloadFont();\n}\n\n/***************************************************************************************\n**                          Draw the clock digits\n***************************************************************************************/\nvoid drawTime() {\n  tft.loadFont(AA_FONT_LARGE, LittleFS);\n\n  // Convert UTC to local time, returns zone code in tz1_Code, e.g \"GMT\"\n  time_t local_time = TIMEZONE.toLocal(now(), &tz1_Code);\n\n  String timeNow = \"\";\n\n  if (hour(local_time) < 10) timeNow += \"0\";\n  timeNow += hour(local_time);\n  timeNow += \":\";\n  if (minute(local_time) < 10) timeNow += \"0\";\n  timeNow += minute(local_time);\n\n  tft.setTextDatum(BC_DATUM);\n  tft.setTextColor(TFT_YELLOW, TFT_BLACK);\n  tft.setTextPadding(tft.textWidth(\" 44:44 \"));  // String width + margin\n  tft.drawString(timeNow, 120, 53);\n\n  drawSeparator(51);\n\n  tft.setTextPadding(0);\n\n  tft.unloadFont();\n}\n\n/***************************************************************************************\n**                          Draw the current weather\n***************************************************************************************/\nvoid drawCurrentWeather() {\n  time_t local_time = TIMEZONE.toLocal(now(), &tz1_Code);\n  String date = \"Updated: \" + strDate(local_time);\n  String weatherText = \"None\";\n\n  tft.setTextDatum(BC_DATUM);\n  tft.setTextColor(TFT_ORANGE, TFT_BLACK);\n  tft.setTextPadding(tft.textWidth(\" Updated: Mmm 44 44:44 \"));  // String width + margin\n  tft.drawString(date, 120, 16);\n\n  String weatherIcon = \"\";\n\n  String currentSummary = forecast->main[0];\n  currentSummary.toLowerCase();\n\n  weatherIcon = getMeteoconIcon(forecast->id[0], true);\n\n  ui.drawBmp(\"/icon/\" + weatherIcon + \".bmp\", 0, 53);\n\n  // Weather Text\n  if (language == \"en\")\n    weatherText = forecast->main[0];\n  else\n    weatherText = forecast->description[0];\n\n  tft.setTextDatum(BR_DATUM);\n  tft.setTextColor(TFT_ORANGE, TFT_BLACK);\n\n  int splitPoint = 0;\n  int xpos = 235;\n  splitPoint =  splitIndex(weatherText);\n\n  tft.setTextPadding(xpos - 100);  // xpos - icon width\n  if (splitPoint) tft.drawString(weatherText.substring(0, splitPoint), xpos, 69);\n  else tft.drawString(\" \", xpos, 69);\n  tft.drawString(weatherText.substring(splitPoint), xpos, 86);\n\n  tft.setTextColor(TFT_YELLOW, TFT_BLACK);\n  tft.setTextDatum(TR_DATUM);\n  tft.setTextPadding(0);\n  if (units == \"metric\") tft.drawString(\"oC\", 237, 95);\n  else  tft.drawString(\"oF\", 237, 95);\n\n  //Temperature large digits added in updateData() to save swapping font here\n \n  tft.setTextColor(TFT_ORANGE, TFT_BLACK);\n  weatherText = String(forecast->wind_speed[0], 0);\n\n  if (units == \"metric\") weatherText += \" m/s\";\n  else weatherText += \" mph\";\n\n  tft.setTextDatum(TC_DATUM);\n  tft.setTextPadding(tft.textWidth(\"888 m/s\")); // Max string length?\n  tft.drawString(weatherText, 124, 136);\n\n  if (units == \"imperial\")\n  {\n    weatherText = forecast->pressure[0];\n    weatherText += \" in\";\n  }\n  else\n  {\n    weatherText = String(forecast->pressure[0], 0);\n    weatherText += \" hPa\";\n  }\n\n  tft.setTextDatum(TR_DATUM);\n  tft.setTextPadding(tft.textWidth(\" 8888hPa\")); // Max string length?\n  tft.drawString(weatherText, 230, 136);\n\n  int windAngle = (forecast->wind_deg[0] + 22.5) / 45;\n  if (windAngle > 7) windAngle = 0;\n  String wind[] = {\"N\", \"NE\", \"E\", \"SE\", \"S\", \"SW\", \"W\", \"NW\" };\n  ui.drawBmp(\"/wind/\" + wind[windAngle] + \".bmp\", 101, 86);\n\n  drawSeparator(153);\n\n  tft.setTextDatum(TL_DATUM); // Reset datum to normal\n  tft.setTextPadding(0);      // Reset padding width to none\n}\n\n/***************************************************************************************\n**                          Draw the 4 forecast columns\n***************************************************************************************/\n// draws the three forecast columns\nvoid drawForecast() {\n  int8_t dayIndex = getNextDayIndex();\n  \n  drawForecastDetail(  8, 171, dayIndex);\n  dayIndex += 8;\n  drawForecastDetail( 66, 171, dayIndex); // was 95\n  dayIndex += 8;\n  drawForecastDetail(124, 171, dayIndex); // was 180\n  dayIndex += 8;\n  drawForecastDetail(182, 171, dayIndex); // was 180\n  drawSeparator(171 + 69);\n}\n\n/***************************************************************************************\n**                          Draw 1 forecast column at x, y\n***************************************************************************************/\n// helper for the forecast columns\nvoid drawForecastDetail(uint16_t x, uint16_t y, uint8_t dayIndex) {\n\n  if (dayIndex >= MAX_DAYS * 8) return;\n\n  String day  = shortDOW[weekday(TIMEZONE.toLocal(forecast->dt[dayIndex + 4], &tz1_Code))];\n  day.toUpperCase();\n\n  tft.setTextDatum(BC_DATUM);\n\n  tft.setTextColor(TFT_ORANGE, TFT_BLACK);\n  tft.setTextPadding(tft.textWidth(\"WWW\"));\n  tft.drawString(day, x + 25, y);\n\n  tft.setTextColor(TFT_WHITE, TFT_BLACK);\n  tft.setTextPadding(tft.textWidth(\"-88   -88\"));\n\n  // Find the temperature min and max during the day\n  float tmax = -9999;\n  float tmin =  9999;\n  for (int i = 0; i < 8; i++) if (forecast->temp_max[dayIndex + i] > tmax) tmax = forecast->temp_max[dayIndex + i];\n  for (int i = 0; i < 8; i++) if (forecast->temp_min[dayIndex + i] < tmin) tmin = forecast->temp_min[dayIndex + i];\n\n  String highTemp = String(tmax, 0);\n  String lowTemp  = String(tmin, 0);\n  tft.drawString(highTemp + \" \" + lowTemp, x + 25, y + 17);\n\n  String weatherIcon = getMeteoconIcon(forecast->id[dayIndex + 4], false);\n\n  ui.drawBmp(\"/icon50/\" + weatherIcon + \".bmp\", x, y + 18);\n\n  tft.setTextPadding(0); // Reset padding width to none\n}\n\n/***************************************************************************************\n**                          Draw Sun rise/set, Moon, cloud cover and humidity\n***************************************************************************************/\nvoid drawAstronomy() {\n\n  tft.setTextDatum(BC_DATUM);\n  tft.setTextColor(TFT_WHITE, TFT_BLACK);\n  tft.setTextPadding(tft.textWidth(\" Last qtr \"));\n\n  time_t local_time = TIMEZONE.toLocal(forecast->dt[0], &tz1_Code);\n  uint16_t y = year(local_time);\n  uint8_t  m = month(local_time);\n  uint8_t  d = day(local_time);\n  uint8_t  h = hour(local_time);\n  int      ip;\n  uint8_t icon = moon_phase(y, m, d, h, &ip);\n\n  tft.drawString(moonPhase[ip], 120, 319);\n  ui.drawBmp(\"/moon/moonphase_L\" + String(icon) + \".bmp\", 120 - 30, 318 - 16 - 60);\n\n  tft.setTextDatum(BC_DATUM);\n  tft.setTextColor(TFT_ORANGE, TFT_BLACK);\n  tft.setTextPadding(0); // Reset padding width to none\n  tft.drawString(sunStr, 40, 270);\n\n  tft.setTextDatum(BR_DATUM);\n  tft.setTextColor(TFT_WHITE, TFT_BLACK);\n  tft.setTextPadding(tft.textWidth(\" 88:88 \"));\n\n  String rising = strTime(forecast->sunrise) + \" \";\n  int dt = rightOffset(rising, \":\"); // Draw relative to colon to them aligned\n  tft.drawString(rising, 40 + dt, 290);\n\n  String setting = strTime(forecast->sunset) + \" \";\n  dt = rightOffset(setting, \":\");\n  tft.drawString(setting, 40 + dt, 305);\n\n  tft.setTextDatum(BC_DATUM);\n  tft.setTextColor(TFT_ORANGE, TFT_BLACK);\n  tft.drawString(cloudStr, 195, 260);     // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ?\n\n  String cloudCover = \"\";\n  cloudCover += forecast->clouds_all[0];\n  cloudCover += \"%\";\n\n  tft.setTextDatum(BR_DATUM);\n  tft.setTextColor(TFT_WHITE, TFT_BLACK);\n  tft.setTextPadding(tft.textWidth(\" 100%\"));\n  tft.drawString(cloudCover, 210, 277);\n\n  tft.setTextDatum(BC_DATUM);\n  tft.setTextColor(TFT_ORANGE, TFT_BLACK);\n  tft.drawString(humidityStr, 195, 300 - 2);     // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ?\n\n  String humidity = \"\";\n  humidity += forecast->humidity[0];\n  humidity += \"%\";\n\n  tft.setTextDatum(BR_DATUM);\n  tft.setTextColor(TFT_WHITE, TFT_BLACK);\n  tft.setTextPadding(tft.textWidth(\"100%\"));\n  tft.drawString(humidity, 210, 315);\n\n  tft.setTextPadding(0); // Reset padding width to none\n}\n\n/***************************************************************************************\n**                          Get the icon file name from the index number\n***************************************************************************************/\nconst char* getMeteoconIcon(uint16_t id, bool today)\n{\n  if ( today && id/100 == 8 && (forecast->dt[0] < forecast->sunrise || forecast->dt[0] > forecast->sunset)) id += 1000; \n\n  if (id/100 == 2) return \"thunderstorm\";\n  if (id/100 == 3) return \"drizzle\";\n  if (id/100 == 4) return \"unknown\";\n  if (id == 500) return \"lightRain\";\n  else if (id == 511) return \"sleet\";\n  else if (id/100 == 5) return \"rain\";\n  if (id >= 611 && id <= 616) return \"sleet\";\n  else if (id/100 == 6) return \"snow\";\n  if (id/100 == 7) return \"fog\";\n  if (id == 800) return \"clear-day\";\n  if (id == 801) return \"partly-cloudy-day\";\n  if (id == 802) return \"cloudy\";\n  if (id == 803) return \"cloudy\";\n  if (id == 804) return \"cloudy\";\n  if (id == 1800) return \"clear-night\";\n  if (id == 1801) return \"partly-cloudy-night\";\n  if (id == 1802) return \"cloudy\";\n  if (id == 1803) return \"cloudy\";\n  if (id == 1804) return \"cloudy\";\n\n  return \"unknown\";\n}\n\n/***************************************************************************************\n**                          Draw screen section separator line\n***************************************************************************************/\n// if you don't want separators, comment out the tft-line\nvoid drawSeparator(uint16_t y) {\n  tft.drawFastHLine(10, y, 240 - 2 * 10, 0x4228);\n}\n\n/***************************************************************************************\n**                          Determine place to split a line line\n***************************************************************************************/\n// determine the \"space\" split point in a long string\nint splitIndex(String text)\n{\n  uint16_t index = 0;\n  while ( (text.indexOf(' ', index) >= 0) && ( index <= text.length() / 2 ) ) {\n    index = text.indexOf(' ', index) + 1;\n  }\n  if (index) index--;\n  return index;\n}\n\n/***************************************************************************************\n**                          Right side offset to a character\n***************************************************************************************/\n// Calculate coord delta from end of text String to start of sub String contained within that text\n// Can be used to vertically right align text so for example a colon \":\" in the time value is always\n// plotted at same point on the screen irrespective of different proportional character widths,\n// could also be used to align decimal points for neat formatting\nint rightOffset(String text, String sub)\n{\n  int index = text.indexOf(sub);\n  return tft.textWidth(text.substring(index));\n}\n\n/***************************************************************************************\n**                          Left side offset to a character\n***************************************************************************************/\n// Calculate coord delta from start of text String to start of sub String contained within that text\n// Can be used to vertically left align text so for example a colon \":\" in the time value is always\n// plotted at same point on the screen irrespective of different proportional character widths,\n// could also be used to align decimal points for neat formatting\nint leftOffset(String text, String sub)\n{\n  int index = text.indexOf(sub);\n  return tft.textWidth(text.substring(0, index));\n}\n\n/***************************************************************************************\n**                          Draw circle segment\n***************************************************************************************/\n// Draw a segment of a circle, centred on x,y with defined start_angle and subtended sub_angle\n// Angles are defined in a clockwise direction with 0 at top\n// Segment has radius r and it is plotted in defined colour\n// Can be used for pie charts etc, in this sketch it is used for wind direction\n#define DEG2RAD 0.0174532925 // Degrees to Radians conversion factor\n#define INC 2 // Minimum segment subtended angle and plotting angle increment (in degrees)\nvoid fillSegment(int x, int y, int start_angle, int sub_angle, int r, unsigned int colour)\n{\n  // Calculate first pair of coordinates for segment start\n  float sx = cos((start_angle - 90) * DEG2RAD);\n  float sy = sin((start_angle - 90) * DEG2RAD);\n  uint16_t x1 = sx * r + x;\n  uint16_t y1 = sy * r + y;\n\n  // Draw colour blocks every INC degrees\n  for (int i = start_angle; i < start_angle + sub_angle; i += INC) {\n\n    // Calculate pair of coordinates for segment end\n    int x2 = cos((i + 1 - 90) * DEG2RAD) * r + x;\n    int y2 = sin((i + 1 - 90) * DEG2RAD) * r + y;\n\n    tft.fillTriangle(x1, y1, x2, y2, x, y, colour);\n\n    // Copy segment end to segment start for next segment\n    x1 = x2;\n    y1 = y2;\n  }\n}\n\n/***************************************************************************************\n**                          Get 3 hourly index at start of next day\n***************************************************************************************/\nint getNextDayIndex(void)\n{\n  int index = 0;\n  String today = forecast->dt_txt[0].substring(8,10);\n  for (index = 0; index < 9; index++)\n  {\n    if (forecast->dt_txt[index].substring(8,10) != today) break;\n  }\n  return index;\n}\n\n/***************************************************************************************\n**                          Print the weather info to the Serial Monitor\n***************************************************************************************/\nvoid printWeather(void)\n{\n#ifdef SERIAL_MESSAGES\n  Serial.println(\"Weather from OpenWeather\\n\");\n\n  Serial.print(\"city_name           : \"); Serial.println(forecast->city_name);\n  Serial.print(\"sunrise             : \"); Serial.println(strTime(forecast->sunrise));\n  Serial.print(\"sunset              : \"); Serial.println(strTime(forecast->sunset));\n  Serial.print(\"Latitude            : \"); Serial.println(ow.lat);\n  Serial.print(\"Longitude           : \"); Serial.println(ow.lon);\n  // We can use the timezone to set the offset eventually...\n  Serial.print(\"Timezone            : \"); Serial.println(forecast->timezone);\n  Serial.println();\n\n  if (forecast)\n  {\n    Serial.println(\"###############  Forecast weather  ###############\\n\");\n    for (int i = 0; i < (MAX_DAYS * 8); i++)\n    {\n      Serial.print(\"3 hourly forecast   \"); if (i < 10) Serial.print(\" \"); Serial.print(i);\n      Serial.println();\n      Serial.print(\"dt (time)        : \"); Serial.println(strTime(forecast->dt[i]));\n\n      Serial.print(\"temp             : \"); Serial.println(forecast->temp[i]);\n      Serial.print(\"temp.min         : \"); Serial.println(forecast->temp_min[i]);\n      Serial.print(\"temp.max         : \"); Serial.println(forecast->temp_max[i]);\n\n      Serial.print(\"pressure         : \"); Serial.println(forecast->pressure[i]);\n      Serial.print(\"sea_level        : \"); Serial.println(forecast->sea_level[i]);\n      Serial.print(\"grnd_level       : \"); Serial.println(forecast->grnd_level[i]);\n      Serial.print(\"humidity         : \"); Serial.println(forecast->humidity[i]);\n\n      Serial.print(\"clouds           : \"); Serial.println(forecast->clouds_all[i]);\n      Serial.print(\"wind_speed       : \"); Serial.println(forecast->wind_speed[i]);\n      Serial.print(\"wind_deg         : \"); Serial.println(forecast->wind_deg[i]);\n      Serial.print(\"wind_gust        : \"); Serial.println(forecast->wind_gust[i]);\n\n      Serial.print(\"visibility       : \"); Serial.println(forecast->visibility[i]);\n      Serial.print(\"pop              : \"); Serial.println(forecast->pop[i]);\n      Serial.println();\n\n      Serial.print(\"dt_txt           : \"); Serial.println(forecast->dt_txt[i]);\n      Serial.print(\"id               : \"); Serial.println(forecast->id[i]);\n      Serial.print(\"main             : \"); Serial.println(forecast->main[i]);\n      Serial.print(\"description      : \"); Serial.println(forecast->description[i]);\n      Serial.print(\"icon             : \"); Serial.println(forecast->icon[i]);\n\n      Serial.println();\n    }\n  }\n#endif\n}\n/***************************************************************************************\n**             Convert Unix time to a \"local time\" time string \"12:34\"\n***************************************************************************************/\nString strTime(time_t unixTime)\n{\n  time_t local_time = TIMEZONE.toLocal(unixTime, &tz1_Code);\n\n  String localTime = \"\";\n\n  if (hour(local_time) < 10) localTime += \"0\";\n  localTime += hour(local_time);\n  localTime += \":\";\n  if (minute(local_time) < 10) localTime += \"0\";\n  localTime += minute(local_time);\n\n  return localTime;\n}\n\n/***************************************************************************************\n**  Convert Unix time to a local date + time string \"Oct 16 17:18\", ends with newline\n***************************************************************************************/\nString strDate(time_t unixTime)\n{\n  time_t local_time = TIMEZONE.toLocal(unixTime, &tz1_Code);\n\n  String localDate = \"\";\n\n  localDate += monthShortStr(month(local_time));\n  localDate += \" \";\n  localDate += day(local_time);\n  localDate += \" \" + strTime(unixTime);\n\n  return localDate;\n}\n\n/**The MIT License (MIT)\n  Copyright (c) 2015 by Daniel Eichhorn\n  Permission is hereby granted, free of charge, to any person obtaining a copy\n  of this software and associated documentation files (the \"Software\"), to deal\n  in the Software without restriction, including without limitation the rights\n  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n  copies of the Software, and to permit persons to whom the Software is\n  furnished to do so, subject to the following conditions:\n  The above copyright notice and this permission notice shall be included in all\n  copies or substantial portions of the Software.\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n  AUTHORS OR COPYBR_DATUM HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n  SOFTWARE.\n  See more at http://blog.squix.ch\n*/\n\n//  Changes made by Bodmer:\n\n//  Minor changes to text placement and auto-blanking out old text with background colour padding\n//  Moon phase text added (not provided by OpenWeather)\n//  Forecast text lines are automatically split onto two lines at a central space (some are long!)\n//  Time is printed with colons aligned to tidy display\n//  Min and max forecast temperatures spaced out\n//  New smart splash startup screen and updated progress messages\n//  Display does not need to be blanked between updates\n//  Icons nudged about slightly to add wind direction + speed\n//  Barometric pressure added\n\n//  Adapted to use the OpenWeather library: https://github.com/Bodmer/OpenWeather\n//  Moon phase/rise/set (not provided by OpenWeather) replace with  and cloud cover humidity\n//  Created and added new 100x100 and 50x50 pixel weather icons, these are in the\n//  sketch data folder, press Ctrl+K to view\n//  Add moon icons, eliminate all downloads of icons (may lose server!)\n//  Adapted to use anti-aliased fonts, tweaked coords\n//  Added forecast for 4th day\n//  Added cloud cover and humidity in lieu of Moon rise/set\n//  Adapted to be compatible with ESP32\n"
  },
  {
    "path": "keywords.txt",
    "content": "OpenWeather\tKEYWORD1\n\ngetForecast\tKEYWORD2\nparseRequest\tKEYWORD2\npartialDataSet\tKEYWORD2\n\nOW_current\tKEYWORD2\nOW_hourly\tKEYWORD2\nOW_daily\tKEYWORD2\nOW_forecast\tKEYWORD2"
  },
  {
    "path": "library.json",
    "content": "{\n  \"name\": \"OpenWeather\",\n  \"version\": \"0.3.0\",\n  \"keywords\": \"weather, TFT_eSPI, tft, display, ESP8266, NodeMCU, ESP32, M5Stack, ILI9341\",\n  \"description\": \"OpenWeather API client for ESP8266 and ESP32\",\n  \"repository\":\n  {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/Bodmer/OpenWeather\"\n  },\n  \"authors\":\n  [\n    {\n        \"name\": \"Bodmer\",\n        \"email\": \"bodmer@anola.net\",\n        \"maintainer\": true\n    }\n  ],\n  \"frameworks\": \"arduino\",\n  \"platforms\": \"raspberrypi, espressif8266, espressif32\"\n}\n"
  },
  {
    "path": "library.properties",
    "content": "name=OpenWeather\nversion=0.3.0\nauthor=Bodmer\nmaintainer=Bodmer\nsentence=OpenWeather client\nparagraph=A weather retrieval library for ESP8266 and ESP32\ncategory=Display\nurl=https://github.com/Bodmer/OpenWeather\narchitectures=esp8266,esp32,rp2040,mbed_nano,mbed_rp2040\nincludes=OpenWeather.h\n"
  },
  {
    "path": "license.txt",
    "content": "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvStartvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\nSoftware License Agreement (FreeBSD License)\n\nOpen Weather API client library\nCopyright (c) 2020 Bodmer (https://github.com/Bodmer)\n\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nThe views and conclusions contained in the software and documentation are those\nof the authors and should not be interpreted as representing official policies,\neither expressed or implied, of the FreeBSD Project.\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^End^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"
  }
]