├── .gitattributes ├── .gitignore ├── DarkSkyWeather.cpp ├── DarkSkyWeather.h ├── Data_Point_Set.h ├── README.md ├── User_Setup.h ├── examples ├── My_DarkSkyWeather_Test │ ├── My_DarkSkyWeather_Test.ino │ └── Notes.ino └── TFT_eSPI_weather │ ├── All_Settings.h │ ├── GfxUi.cpp │ ├── GfxUi.h │ ├── NTP_Time.h │ ├── SPIFFS_Support.h │ ├── ScreenGrabClient.ino │ ├── ScreenGrabServer.ino │ ├── TFT_eSPI_weather.ino │ └── data │ ├── fonts │ ├── NotoSansBold15.vlw │ └── NotoSansBold36.vlw │ ├── icon │ ├── clear-day.bmp │ ├── clear-night.bmp │ ├── cloudy.bmp │ ├── drizzle.bmp │ ├── fog.bmp │ ├── hail.bmp │ ├── lightRain.bmp │ ├── partly-cloudy-day.bmp │ ├── partly-cloudy-night.bmp │ ├── rain.bmp │ ├── sleet.bmp │ ├── snow.bmp │ ├── thunderstorm.bmp │ ├── unknown.bmp │ └── wind.bmp │ ├── icon50 │ ├── clear-day.bmp │ ├── clear-night.bmp │ ├── cloudy.bmp │ ├── drizzle.bmp │ ├── fog.bmp │ ├── hail.bmp │ ├── lightRain.bmp │ ├── partly-cloudy-day.bmp │ ├── partly-cloudy-night.bmp │ ├── rain.bmp │ ├── sleet.bmp │ ├── snow.bmp │ ├── thunderstorm.bmp │ ├── unknown.bmp │ └── wind.bmp │ ├── moon │ ├── moonphase_L0.bmp │ ├── moonphase_L1.bmp │ ├── moonphase_L10.bmp │ ├── moonphase_L11.bmp │ ├── moonphase_L12.bmp │ ├── moonphase_L13.bmp │ ├── moonphase_L14.bmp │ ├── moonphase_L15.bmp │ ├── moonphase_L16.bmp │ ├── moonphase_L17.bmp │ ├── moonphase_L18.bmp │ ├── moonphase_L19.bmp │ ├── moonphase_L2.bmp │ ├── moonphase_L20.bmp │ ├── moonphase_L21.bmp │ ├── moonphase_L22.bmp │ ├── moonphase_L23.bmp │ ├── moonphase_L3.bmp │ ├── moonphase_L4.bmp │ ├── moonphase_L5.bmp │ ├── moonphase_L6.bmp │ ├── moonphase_L7.bmp │ ├── moonphase_L8.bmp │ └── moonphase_L9.bmp │ ├── splash │ └── DarkSky.jpg │ └── wind │ ├── E.bmp │ ├── N.bmp │ ├── NE.bmp │ ├── NW.bmp │ ├── S.bmp │ ├── SE.bmp │ ├── SW.bmp │ └── W.bmp ├── keywords.txt ├── library.json ├── library.properties └── license.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows thumbnail cache files 2 | Thumbs.db 3 | ehthumbs.db 4 | ehthumbs_vista.db 5 | 6 | # Folder config file 7 | Desktop.ini 8 | 9 | # Recycle Bin used on file shares 10 | $RECYCLE.BIN/ 11 | 12 | # Windows Installer files 13 | *.cab 14 | *.msi 15 | *.msm 16 | *.msp 17 | 18 | # Windows shortcuts 19 | *.lnk 20 | 21 | # ========================= 22 | # Operating System Files 23 | # ========================= 24 | -------------------------------------------------------------------------------- /DarkSkyWeather.cpp: -------------------------------------------------------------------------------- 1 | // Client library for the Dark Sky weather datapoint server 2 | // https://darksky.net/dev 3 | 4 | // Created by Bodmer 24/9/2018 5 | // This is a beta test version and is subject to change! 6 | 7 | // See license.txt in root folder of library 8 | 9 | #ifdef ESP8266 10 | #include 11 | #else 12 | #include 13 | #endif 14 | 15 | #include 16 | 17 | // The streaming parser to use is not the Arduino IDE library manager default, 18 | // but this one which is slightly different and renamed to avoid conflicts: 19 | // https://github.com/Bodmer/JSON_Decoder 20 | 21 | 22 | #include 23 | #include 24 | 25 | #include "DarkSkyWeather.h" 26 | 27 | 28 | /*************************************************************************************** 29 | ** Function name: getForecast 30 | ** Description: Setup the weather forecast request from darksky.net 31 | ***************************************************************************************/ 32 | // The structures etc are created by the sketch and passed to this function. 33 | // Pass a nullptr for current, hourly or daily pointers to exclude in response. 34 | // Provided for backwards compatibility prior to adding minutely data request 35 | bool DS_Weather::getForecast(DSW_current *current, DSW_hourly *hourly, DSW_daily *daily, 36 | String api_key, String latitude, String longitude, 37 | String units, String language) { 38 | 39 | return getForecast(current, nullptr, hourly, daily, api_key, latitude, longitude, units, language); 40 | } 41 | 42 | /*************************************************************************************** 43 | ** Function name: getForecast 44 | ** Description: Setup the weather forecast request from darksky.net 45 | ***************************************************************************************/ 46 | // The structures etc are created by the sketch and passed to this function. 47 | // Pass a nullptr for current, minutely, hourly or daily pointers to exclude in response. 48 | bool DS_Weather::getForecast(DSW_current *current, DSW_minutely *minutely, DSW_hourly *hourly, DSW_daily *daily, 49 | String api_key, String latitude, String longitude, 50 | String units, String language) { 51 | 52 | data_set = ""; 53 | minutely_index = 0; 54 | hourly_index = 0; 55 | daily_index = 0; 56 | 57 | // Local copies of structure pointers, the structures are filled during parsing 58 | this->current = current; 59 | this->minutely = minutely; 60 | this->hourly = hourly; 61 | this->daily = daily; 62 | 63 | #if defined (MINIMISE_DATA_POINTS) // If defined in DarSkyWeather library "User_Setup.h" 64 | hourly = nullptr; 65 | #endif 66 | 67 | // Exclude some info by passing fn a NULL pointer to reduce memory needed 68 | String exclude = ""; 69 | if (!current) exclude += "currently,"; // summary, then current weather 70 | if (!minutely) exclude += "minutely,"; // summary, rain predictions every minute for next hour 71 | if (!hourly) exclude += "hourly,"; // summary, then weather every hour for 48 hours 72 | if (!daily) exclude += "daily,"; // summary, then daily detailed weather for one week (7 days) 73 | 74 | exclude += "alerts,"; // special warnings, typically none 75 | exclude += "flags"; // misc info 76 | 77 | String url = "https://api.darksky.net/forecast/" + api_key + "/" 78 | + latitude + "," + longitude + "?exclude=" + exclude 79 | + "&units=" + units + "&lang=" + language; 80 | 81 | // Send GET request and feed the parser 82 | bool result = parseRequest(url); 83 | 84 | // Null out pointers to prevent crashes 85 | this->current = nullptr; 86 | this->minutely = nullptr; 87 | this->hourly = nullptr; 88 | this->daily = nullptr; 89 | 90 | return result; 91 | } 92 | 93 | #ifdef ESP32 // Decide if ESP32 or ESP8266 parseRequest available 94 | 95 | /*************************************************************************************** 96 | ** Function name: parseRequest (for ESP32) 97 | ** Description: Fetches the JSON message and feeds to the parser 98 | ***************************************************************************************/ 99 | bool DS_Weather::parseRequest(String url) { 100 | 101 | uint32_t dt = millis(); 102 | 103 | // This certificate will expire in June 2019, but we can ignore it at line 111 104 | const char* dsw_ca_cert = \ 105 | "-----BEGIN CERTIFICATE-----\n" \ 106 | "MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF\n" \ 107 | "ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6\n" \ 108 | "b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL\n" \ 109 | "MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv\n" \ 110 | "b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj\n" \ 111 | "ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM\n" \ 112 | "9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw\n" \ 113 | "IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6\n" \ 114 | "VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L\n" \ 115 | "93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm\n" \ 116 | "jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\n" \ 117 | "AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA\n" \ 118 | "A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI\n" \ 119 | "U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs\n" \ 120 | "N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv\n" \ 121 | "o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU\n" \ 122 | "5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy\n" \ 123 | "rqXRfboQnoZsG4q5WTP468SQvvG5\n" \ 124 | "-----END CERTIFICATE-----\n"; 125 | 126 | WiFiClientSecure client; 127 | 128 | 129 | //client.setCACert(dsw_ca_cert); // Comment out to stop certificate check 130 | 131 | JSON_Decoder parser; 132 | parser.setListener(this); 133 | 134 | const char* host = "api.darksky.net"; 135 | 136 | if (!client.connect(host, 443)) 137 | { 138 | Serial.println("Connection failed."); 139 | return false; 140 | } 141 | 142 | uint32_t timeout = millis(); 143 | char c = 0; 144 | int ccount = 0; 145 | uint32_t readCount = 0; 146 | parseOK = false; 147 | 148 | // Send GET request 149 | Serial.println("\nSending GET request to api.darksky.net..."); 150 | client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n"); 151 | 152 | // Pull out any header, X-Forecast-API-Calls: reports current daily API call count 153 | while (client.connected()) 154 | { 155 | String line = client.readStringUntil('\n'); 156 | if (line == "\r") { 157 | Serial.println("Header end found"); 158 | break; 159 | } 160 | 161 | #ifdef SHOW_HEADER 162 | Serial.println(line); 163 | #else 164 | // Show the API call count 165 | if (line.indexOf("X-Forecast-API-Calls") >= 0) Serial.println(line); 166 | #endif 167 | 168 | if ((millis() - timeout) > 5000UL) 169 | { 170 | Serial.println ("HTTP header timeout"); 171 | client.stop(); 172 | return false; 173 | } 174 | } 175 | 176 | Serial.println("\nParsing JSON"); 177 | 178 | // Parse the JSON data, available() includes yields 179 | while ( client.available() > 0 || client.connected()) 180 | { 181 | while(client.available() > 0) 182 | { 183 | c = client.read(); 184 | parser.parse(c); 185 | #ifdef SHOW_JSON 186 | if (c == '{' || c == '[' || c == '}' || c == ']') Serial.println(); 187 | Serial.print(c); if (ccount++ > 100 && c == ',') {ccount = 0; Serial.println();} 188 | #endif 189 | 190 | if ((millis() - timeout) > 8000UL) 191 | { 192 | Serial.println ("JSON parse client timeout"); 193 | parser.reset(); 194 | client.stop(); 195 | return false; 196 | } 197 | yield(); 198 | } 199 | } 200 | 201 | Serial.println(""); 202 | Serial.print("Done in "); Serial.print(millis()-dt); Serial.println(" ms\n"); 203 | 204 | parser.reset(); 205 | 206 | client.stop(); 207 | 208 | // A message has been parsed but the datapoint correctness is unknown 209 | return parseOK; 210 | } 211 | 212 | #else // ESP8266 version 213 | 214 | /*************************************************************************************** 215 | ** Function name: parseRequest (for ESP8266) 216 | ** Description: Fetches the JSON message and feeds to the parser 217 | ***************************************************************************************/ 218 | bool DS_Weather::parseRequest(String url) { 219 | 220 | uint32_t dt = millis(); 221 | 222 | // SHA1 certificate fingerprint 223 | #if defined(AXTLS) 224 | const char* fingerprint = "EB:C2:67:D1:B1:C6:77:90:51:C1:4A:0A:BA:83:E1:F0:6D:73:DD:B8"; 225 | WiFiClientSecure client; 226 | #else 227 | // Must use namespace:: to select BearSSL 228 | BearSSL::WiFiClientSecure client; 229 | 230 | #ifdef SECURE_SSL 231 | // BearSSL requires a different fingerprint format and setFingerprint() must be called 232 | const uint8_t fp[20] = {0xEB,0xC2,0x67,0xD1,0xB1,0xC6,0x77,0x90,0x51,0xC1,0x4A,0x0A,0xBA,0x83,0xE1,0xF0,0x6D,0x73,0xDD,0xB8}; 233 | client.setFingerprint(fp); 234 | #else 235 | client.setInsecure(); 236 | #endif 237 | #endif 238 | 239 | JSON_Decoder parser; 240 | parser.setListener(this); 241 | 242 | const char* host = "api.darksky.net"; 243 | 244 | if (!client.connect(host, 443)) 245 | { 246 | Serial.println("Connection failed."); 247 | return false; 248 | } 249 | 250 | #if defined(AXTLS) 251 | // BearSSL does not support verify() and always returns false. 252 | if (client.verify(fingerprint, host)) 253 | { 254 | Serial.println("Certificate OK"); 255 | } 256 | else 257 | { 258 | Serial.println("Bad certificate"); 259 | client.stop(); 260 | return false; 261 | } 262 | #endif 263 | 264 | uint32_t timeout = millis(); 265 | char c = 0; 266 | int ccount = 0; 267 | uint32_t readCount = 0; 268 | parseOK = false; 269 | 270 | // Send GET request 271 | Serial.println("Sending GET request to api.darksky.net..."); 272 | client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n"); 273 | 274 | // Pull out any header, X-Forecast-API-Calls: reports current daily API call count 275 | while (client.available() || client.connected()) 276 | { 277 | String line = client.readStringUntil('\n'); 278 | if (line == "\r") { 279 | Serial.println("Header end found"); 280 | break; 281 | } 282 | 283 | #ifdef SHOW_HEADER 284 | Serial.println(line); 285 | #else 286 | // Show the API call count 287 | if (line.indexOf("X-Forecast-API-Calls") >= 0) Serial.println(line); 288 | #endif 289 | 290 | if ((millis() - timeout) > 5000UL) 291 | { 292 | Serial.println ("HTTP header timeout"); 293 | client.stop(); 294 | return false; 295 | } 296 | } 297 | 298 | Serial.println("Parsing JSON"); 299 | 300 | // Parse the JSON data, available() includes yields 301 | while (client.available() || client.connected()) 302 | { 303 | while (client.available()) 304 | { 305 | c = client.read(); 306 | parser.parse(c); 307 | #ifdef SHOW_JSON 308 | if (c == '{' || c == '[' || c == '}' || c == ']') Serial.println(); 309 | Serial.print(c); if (ccount++ > 100 && c == ',') {ccount = 0; Serial.println();} 310 | #endif 311 | } 312 | 313 | if ((millis() - timeout) > 8000UL) 314 | { 315 | Serial.println ("JSON client timeout"); 316 | parser.reset(); 317 | client.stop(); 318 | return false; 319 | } 320 | } 321 | 322 | Serial.println(""); 323 | Serial.print("Done in "); Serial.print(millis()-dt); Serial.println(" ms\n"); 324 | 325 | parser.reset(); 326 | 327 | client.stop(); 328 | 329 | // A message has been parsed without error but the datapoint correctness is unknown 330 | return parseOK; 331 | } 332 | 333 | #endif // ESP32 or ESP8266 parseRequest 334 | 335 | /*************************************************************************************** 336 | ** Function name: key etc 337 | ** Description: These functions are called while parsing the JSON message 338 | ***************************************************************************************/ 339 | void DS_Weather::key(const char *key) { 340 | 341 | currentKey = key; 342 | 343 | #ifdef SHOW_CALLBACK 344 | Serial.print("\n>>> Key >>>" + (String)key); 345 | #endif 346 | } 347 | 348 | void DS_Weather::startDocument() { 349 | 350 | currentParent = currentKey = ""; 351 | objectLevel = 0; 352 | valuePath = ""; 353 | arrayIndex = 0; 354 | parseOK = true; 355 | 356 | #ifdef SHOW_CALLBACK 357 | Serial.print("\n>>> Start document >>>"); 358 | #endif 359 | } 360 | 361 | void DS_Weather::endDocument() { 362 | 363 | currentParent = currentKey = ""; 364 | objectLevel = 0; 365 | valuePath = ""; 366 | arrayIndex = 0; 367 | 368 | #ifdef SHOW_CALLBACK 369 | Serial.print("\n<<< End document <<<"); 370 | #endif 371 | } 372 | 373 | void DS_Weather::startObject() { 374 | 375 | currentParent = currentKey; 376 | objectLevel++; 377 | 378 | #ifdef SHOW_CALLBACK 379 | Serial.print("\n>>> Start object level:" + (String) objectLevel + " index:" + (String) arrayIndex +" >>>"); 380 | #endif 381 | } 382 | 383 | void DS_Weather::endObject() { 384 | 385 | currentParent = ""; 386 | arrayIndex++; 387 | objectLevel--; 388 | 389 | #ifdef SHOW_CALLBACK 390 | Serial.print("\n<<< End object <<<"); 391 | #endif 392 | } 393 | 394 | void DS_Weather::startArray() { 395 | 396 | arrayIndex = 0; 397 | valuePath = currentParent + "/" + currentKey; // aka = current Object, e.g. "daily:data" 398 | 399 | #ifdef SHOW_CALLBACK 400 | Serial.print("\n>>> Start array " + valuePath + "/" + (String) arrayIndex +" >>>"); 401 | #endif 402 | } 403 | 404 | void DS_Weather::endArray() { 405 | 406 | valuePath = ""; 407 | 408 | #ifdef SHOW_CALLBACK 409 | Serial.print("\n<<< End array <<<"); 410 | #endif 411 | } 412 | 413 | void DS_Weather::whitespace(char c) { 414 | } 415 | 416 | void DS_Weather::error( const char *message ) { 417 | Serial.print("\nParse error message: "); 418 | Serial.print(message); 419 | parseOK = false; 420 | } 421 | 422 | /*************************************************************************************** 423 | ** Function name: iconIndex 424 | ** Description: Convert the icon name to an array index to save memory 425 | ***************************************************************************************/ 426 | uint8_t DS_Weather::iconIndex(const char *val) 427 | { 428 | if (*val == 0) return MAX_ICON_INDEX; // null so return index for none 429 | 430 | uint8_t i = 0; 431 | for( i = 0; i <= MAX_ICON_INDEX; i++) 432 | { 433 | if (strcmp(iconList[i], val) == 0) break; 434 | } 435 | if ( i >= MAX_ICON_INDEX) i = 0; 436 | return i; 437 | } 438 | 439 | /*************************************************************************************** 440 | ** Function name: iconFilename 441 | ** Description: Convert the icon array index to an icon filename 442 | ***************************************************************************************/ 443 | const char* DS_Weather::iconName(uint8_t index) 444 | { 445 | return iconList[index]; 446 | } 447 | 448 | /*************************************************************************************** 449 | ** Function name: value (full data set) 450 | ** Description: Stores the parsed data in the structures for sketch access 451 | ***************************************************************************************/ 452 | // Nested "if" with "return" reduces comparison count for each key 453 | 454 | #ifndef MINIMISE_DATA_POINTS // Collect full data point set if this is NOT defined <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 455 | 456 | void DS_Weather::value(const char *val) { 457 | 458 | String value = val; 459 | 460 | // Start of JSON 461 | //if (currentParent == "") { 462 | // if (currentKey == "timezone") current->timezone = value; 463 | //} 464 | 465 | // Current forecast - no array index - short path 466 | if (currentParent == "currently") { 467 | data_set = "currently"; 468 | if (currentKey == "time") current->time = (uint32_t)value.toInt(); 469 | else 470 | if (currentKey == "summary") current->summary = value; 471 | else 472 | if (currentKey == "icon") current->icon = iconIndex(val); 473 | else 474 | if (currentKey == "precipIntensity") current->precipIntensity = value.toFloat(); 475 | else 476 | if (currentKey == "precipType") current->precipType = iconIndex(val); 477 | else 478 | if (currentKey == "precipProbability") current->precipProbability = (uint8_t)(100 * (value.toFloat())); 479 | else 480 | if (currentKey == "temperature") current->temperature = value.toFloat(); 481 | else 482 | if (currentKey == "humidity") current->humidity = (uint8_t)(100 * (value.toFloat())); 483 | else 484 | if (currentKey == "pressure") current->pressure = value.toFloat(); 485 | else 486 | if (currentKey == "windSpeed") current->windSpeed = value.toFloat(); 487 | else 488 | if (currentKey == "windGust") current->windGust = value.toFloat(); 489 | else 490 | if (currentKey == "windBearing") current->windBearing = (uint16_t)value.toInt(); 491 | else 492 | if (currentKey == "cloudCover") current->cloudCover = (uint8_t)(100 * (value.toFloat())); 493 | //else 494 | //if (currentKey == "x") current->x = value; 495 | return; 496 | } 497 | 498 | // Minutely data collection 499 | if (currentParent == "minutely") { 500 | data_set = currentParent; // Save parent object to trigger the hourly array 501 | minutely->time[0] = 0; 502 | if (currentKey == "summary") minutely->overallSummary = value; 503 | //else 504 | //if (currentKey == "x") minutely->x = value; 505 | return; 506 | } 507 | 508 | // Hourly data collection 509 | if (currentParent == "hourly") { 510 | data_set = currentParent; // Save parent object to trigger the hourly array 511 | hourly->time[0] = 0; 512 | if (currentKey == "summary") hourly->overallSummary = value; 513 | //else 514 | //if (currentKey == "x") hourly->x = value; 515 | return; 516 | } 517 | 518 | // Daily data collection 519 | if (currentParent == "daily") { 520 | data_set = currentParent; // Save parent to trigger the daily array 521 | daily->time[0] = 0; 522 | if (currentKey == "summary") daily->overallSummary = value; 523 | //else 524 | //if (currentKey == "x") daily->x = value; 525 | return; 526 | } 527 | 528 | // Collect array data after "data_set" has been set by parent 529 | 530 | // minutely data[N] array 531 | if (data_set == "minutely") { 532 | if (minutely_index >= MAX_MINUTES) return; 533 | if (currentKey == "time") { 534 | // Only increment after the first entry 535 | if (minutely->time[0] > 0) 536 | { 537 | minutely_index++; 538 | if (minutely_index >= MAX_MINUTES) return; 539 | } 540 | minutely->time[minutely_index] = (uint32_t)value.toInt(); 541 | } 542 | else 543 | if (currentKey == "precipIntensity") minutely->precipIntensity[minutely_index] = value.toFloat(); 544 | else 545 | if (currentKey == "precipProbability") minutely->precipProbability[minutely_index] = (uint8_t)(100 * (value.toFloat())); 546 | //else 547 | //if (currentKey == "x") minutely->x[minutely_index] = value; 548 | return; 549 | } 550 | 551 | // Collect array data after "data_set" has been set by parent 552 | 553 | // Hourly data[N] array 554 | if (data_set == "hourly") { 555 | if (hourly_index >= MAX_HOURS) return; 556 | if (currentKey == "time") { 557 | // Only increment after the first entry 558 | if (hourly->time[0] > 0) 559 | { 560 | hourly_index++; 561 | if (hourly_index >= MAX_HOURS) return; 562 | } 563 | hourly->time[hourly_index] = (uint32_t)value.toInt(); 564 | } 565 | else 566 | if (currentKey == "summary") hourly->summary[hourly_index] = value; 567 | else 568 | if (currentKey == "precipIntensity") hourly->precipIntensity[hourly_index] = value.toFloat(); 569 | else 570 | if (currentKey == "precipType") hourly->precipType[hourly_index] = iconIndex(val); 571 | else 572 | if (currentKey == "precipProbability") hourly->precipProbability[hourly_index] = (uint8_t)(100 * (value.toFloat())); 573 | else 574 | if (currentKey == "precipAccumulation") hourly->precipAccumulation[hourly_index] = value.toFloat(); 575 | else 576 | if (currentKey == "temperature") hourly->temperature[hourly_index] = value.toFloat(); 577 | else 578 | if (currentKey == "pressure") hourly->pressure[hourly_index] = value.toFloat(); 579 | else 580 | if (currentKey == "cloudCover") hourly->cloudCover[hourly_index] = (uint8_t)(100 * (value.toFloat())); 581 | //else 582 | //if (currentKey == "x") hourly->x[hourly_index] = value; 583 | return; 584 | } 585 | 586 | 587 | // Daily data[N] array 588 | if (data_set == "daily") { 589 | if (daily_index >= MAX_DAYS) return; 590 | if (currentKey == "time") { 591 | // Only increment after the first entry 592 | if (daily->time[0] > 0) 593 | { 594 | daily_index++; 595 | if (daily_index >= MAX_DAYS) return; 596 | } 597 | daily->time[daily_index] = (uint32_t)value.toInt(); 598 | } 599 | else 600 | if (currentKey == "summary") daily->summary[daily_index] = value; 601 | else 602 | if (currentKey == "icon") daily->icon[daily_index] = iconIndex(val); 603 | else 604 | if (currentKey == "sunriseTime") daily->sunriseTime[daily_index] = (uint32_t)value.toInt(); 605 | else 606 | if (currentKey == "sunsetTime") daily->sunsetTime[daily_index] = (uint32_t)value.toInt(); 607 | else 608 | if (currentKey == "moonPhase") daily->moonPhase[daily_index] = (uint8_t)(100 * (value.toFloat())); 609 | else 610 | if (currentKey == "precipIntensity") daily->precipIntensity[daily_index] = value.toFloat(); 611 | else 612 | if (currentKey == "precipProbability") daily->precipProbability[daily_index] = (uint8_t)(100 * (value.toFloat())); 613 | else 614 | if (currentKey == "precipType") daily->precipType[daily_index] = iconIndex(val); 615 | else 616 | if (currentKey == "precipAccumulation") daily->precipAccumulation[daily_index] = value.toFloat(); 617 | else 618 | if (currentKey == "temperatureHigh") daily->temperatureHigh[daily_index] = value.toFloat(); 619 | else 620 | if (currentKey == "temperatureLow") daily->temperatureLow[daily_index] = value.toFloat(); 621 | else 622 | if (currentKey == "humidity") daily->humidity[daily_index] = (uint8_t)(100 * (value.toFloat())); 623 | else 624 | if (currentKey == "pressure") daily->pressure[daily_index] = value.toFloat(); 625 | else 626 | if (currentKey == "windSpeed") daily->windSpeed[daily_index] = value.toFloat(); 627 | else 628 | if (currentKey == "windGust") daily->windGust[daily_index] = value.toFloat(); 629 | else 630 | if (currentKey == "windBearing") daily->windBearing[daily_index] = (uint16_t)value.toInt(); 631 | else 632 | if (currentKey == "cloudCover") daily->cloudCover[daily_index] = (uint8_t)(100 * (value.toFloat())); 633 | //else 634 | //if (currentKey == "x") daily->x[daily_index] = value; 635 | //return; 636 | } 637 | 638 | } 639 | 640 | 641 | #else // MINIMISE_DATA_POINTS: Collect full data point set if this IS defined <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 642 | // data points reduced for TFT_eSPI example to reduce memory requirements 643 | 644 | /*************************************************************************************** 645 | ** Function name: value (partial data set) 646 | ** Description: Stores the parsed data in the structures for sketch access 647 | ***************************************************************************************/ 648 | void DS_Weather::value(const char *val) { 649 | 650 | String value = val; 651 | 652 | // Start of JSON 653 | //if (currentParent == "") { 654 | // if (currentKey == "timezone") current->timezone = value; 655 | //} 656 | 657 | // Current forecast - no array index 658 | if (currentParent == "currently") { 659 | data_set = "currently"; 660 | if (currentKey == "time") current->time = (uint32_t)value.toInt(); 661 | else 662 | if (currentKey == "summary") current->summary = value; 663 | else 664 | if (currentKey == "icon") current->icon = iconIndex(val); 665 | //else 666 | //if (currentKey == "precipIntensity") current->precipIntensity = value.toFloat(); 667 | //else 668 | //if (currentKey == "precipType") current->precipType = iconIndex(val); 669 | //else 670 | //if (currentKey == "precipProbability") current->precipProbability = (uint8_t)(100 * (value.toFloat())); 671 | else 672 | if (currentKey == "temperature") current->temperature = value.toFloat(); 673 | else 674 | if (currentKey == "humidity") current->humidity = (uint8_t)(100 * (value.toFloat())); 675 | else 676 | if (currentKey == "pressure") current->pressure = value.toFloat(); 677 | else 678 | if (currentKey == "windSpeed") current->windSpeed = value.toFloat(); 679 | //else 680 | //if (currentKey == "windGust") current->windGust = value.toFloat(); 681 | else 682 | if (currentKey == "windBearing") current->windBearing = (uint16_t)value.toInt(); 683 | else 684 | if (currentKey == "cloudCover") current->cloudCover = (uint8_t)(100 * (value.toFloat())); 685 | //else 686 | //if (currentKey == "x") current->x = value; 687 | return; 688 | } 689 | 690 | #if !defined (MINIMISE_DATA_POINTS) 691 | // Hourly data collection 692 | if (currentParent == "hourly") { 693 | data_set = currentParent; // Save parent to trigger the hourly array 694 | hourly->time[0] = 0; 695 | if (currentKey == "summary") hourly->overallSummary = value; 696 | //else 697 | //if (currentKey == "x") hourly->x = value; 698 | return; 699 | } 700 | #endif 701 | 702 | // Daily data collection 703 | if (currentParent == "daily") { 704 | data_set = currentParent; // Save parent to trigger the daily array 705 | daily->time[0] = 0; 706 | if (currentKey == "summary") daily->overallSummary = value; 707 | //else 708 | //if (currentKey == "x") daily->x = value; 709 | return; 710 | } 711 | 712 | // Collect array data after "data_set" has been set by parent 713 | #if !defined (MINIMISE_DATA_POINTS) 714 | // Hourly data[N] array 715 | if (data_set == "hourly") { 716 | if (hourly_index >= MAX_HOURS) return; 717 | if (currentKey == "time") { 718 | // Only increment after the first entry 719 | if (hourly->time[0] > 0) 720 | { 721 | hourly_index++; 722 | if (hourly_index >= MAX_HOURS) return; 723 | } 724 | hourly->time[hourly_index] = (uint32_t)value.toInt(); 725 | } 726 | else 727 | if (currentKey == "summary") hourly->summary[hourly_index] = value; 728 | else 729 | if (currentKey == "precipIntensity") hourly->precipIntensity[hourly_index] = value.toFloat(); 730 | else 731 | if (currentKey == "precipType") hourly->precipType[hourly_index] = iconIndex(val); 732 | else 733 | if (currentKey == "precipProbability") hourly->precipProbability[hourly_index] = (uint8_t)(100 * (value.toFloat())); 734 | else 735 | if (currentKey == "precipAccumulation") hourly->precipAccumulation[hourly_index] = value.toFloat(); 736 | else 737 | if (currentKey == "temperature") hourly->temperature[hourly_index] = value.toFloat(); 738 | else 739 | if (currentKey == "pressure") hourly->pressure[hourly_index] = value.toFloat(); 740 | else 741 | if (currentKey == "cloudCover") hourly->cloudCover[hourly_index] = (uint8_t)(100 * (value.toFloat())); 742 | //else 743 | //if (currentKey == "x") hourly->x[hourly_index] = value; 744 | return; 745 | } 746 | #endif 747 | 748 | // Daily data[N] array 749 | if (data_set == "daily") { 750 | if (daily_index >= MAX_DAYS) return; 751 | if (currentKey == "time") { 752 | // Only increment after the first entry 753 | if (daily->time[0] > 0) 754 | { 755 | daily_index++; 756 | if (daily_index >= MAX_DAYS) return; 757 | } 758 | daily->time[daily_index] = (uint32_t)value.toInt(); 759 | } 760 | else 761 | if (currentKey == "summary") daily->summary[daily_index] = value; 762 | else 763 | if (currentKey == "icon") daily->icon[daily_index] = iconIndex(val); 764 | else 765 | if (currentKey == "sunriseTime") daily->sunriseTime[daily_index] = (uint32_t)value.toInt(); 766 | else 767 | if (currentKey == "sunsetTime") daily->sunsetTime[daily_index] = (uint32_t)value.toInt(); 768 | else 769 | if (currentKey == "moonPhase") daily->moonPhase[daily_index] = (uint8_t)(100 * (value.toFloat())); 770 | //else 771 | //if (currentKey == "precipIntensity") daily->precipIntensity[daily_index] = value.toFloat(); 772 | //else 773 | //if (currentKey == "precipProbability") daily->precipProbability[daily_index] = (uint8_t)(100 * (value.toFloat())); 774 | //else 775 | //if (currentKey == "precipType") daily->precipType[daily_index] = iconIndex(val); 776 | //else 777 | //if (currentKey == "precipAccumulation") daily->precipAccumulation[daily_index] = value.toFloat(); 778 | else 779 | if (currentKey == "temperatureHigh") daily->temperatureHigh[daily_index] = value.toFloat(); 780 | else 781 | if (currentKey == "temperatureLow") daily->temperatureLow[daily_index] = value.toFloat(); 782 | //else 783 | //if (currentKey == "humidity") daily->humidity[daily_index] = (uint8_t)(100 * (value.toFloat())); 784 | //else 785 | //if (currentKey == "pressure") daily->pressure[daily_index] = value.toFloat(); 786 | //else 787 | //if (currentKey == "windSpeed") daily->windSpeed[daily_index] = value.toFloat(); 788 | //else 789 | //if (currentKey == "windGust") daily->windGust[daily_index] = value.toFloat(); 790 | //else 791 | //if (currentKey == "windBearing") daily->windBearing[daily_index] = (uint16_t)value.toInt(); 792 | //else 793 | //if (currentKey == "cloudCover") daily->cloudCover[daily_index] = (uint8_t)(100 * (value.toFloat())); 794 | //else 795 | //if (currentKey == "x") daily->x[daily_index] = value; 796 | //return; 797 | } 798 | 799 | } 800 | 801 | #endif // MINIMISE_DATA_POINTS -------------------------------------------------------------------------------- /DarkSkyWeather.h: -------------------------------------------------------------------------------- 1 | // Client library for the Dark Sky weather datapoint server 2 | // https://darksky.net/dev 3 | 4 | // The API server uses https, so a client library with secure support is needed 5 | 6 | // Created by Bodmer 24/9/2018 7 | // This is a beta test version and is subject to change! 8 | 9 | // See license.txt in root folder of library 10 | // iconList[] ndex default 11 | 12 | #define MAX_ICON_INDEX 11 // Maximum for weather icon index 13 | #define ICON_RAIN 1 // Index for the rain icon bitmap (bmp file) 14 | #define NO_VALUE 11 // for precipType default (none) 15 | 16 | #ifndef DarkSkyWeather_h 17 | #define DarkSkyWeather_h 18 | 19 | #include "User_Setup.h" 20 | #include "Data_Point_Set.h" 21 | 22 | 23 | /*************************************************************************************** 24 | ** Description: JSON interface class 25 | ***************************************************************************************/ 26 | class DS_Weather: public JsonListener { 27 | 28 | public: 29 | // Sketch calls this forecast request, it returns true if no parse errors encountered 30 | // Provided for backwards compatibility prior to adding minutely data request 31 | bool getForecast(DSW_current *current, DSW_hourly *hourly, DSW_daily *daily, 32 | String api_key, String latitude, String longitude, 33 | String units, String language); 34 | 35 | // Sketch calls this forecast request, it returns true if no parse errors encountered 36 | // This function requests minutely data in addtition to the others 37 | bool getForecast(DSW_current *current, DSW_minutely *minutely, DSW_hourly *hourly, DSW_daily *daily, 38 | String api_key, String latitude, String longitude, 39 | String units, String language); 40 | 41 | // Called by library (or user sketch), sends a GET request to a https (secure) url 42 | bool parseRequest(String url); // and parses response, returns true if no parse errors 43 | 44 | // Convert the icon index to a name e.g. "partly-cloudy" 45 | const char* iconName(uint8_t index); 46 | 47 | private: // Streaming parser callback functions, allow tracking and decisions 48 | 49 | void startDocument(); // JSON document has started, typically starts once 50 | // Initialises varaibles used, e.g. sets objectLayer = 0 51 | // and arrayIndex =0 52 | void endDocument(); // JSON document has ended, typically ends once 53 | 54 | void startObject(); // Called every time an Object start detected 55 | // may be called multiple times as object layers entered 56 | // Used to increment objectLayer 57 | void endObject(); // Called every time an object ends 58 | // Used to decrement objectLayer and zero arrayIndex 59 | 60 | 61 | void startArray(); // An array of name:value pairs entered 62 | void endArray(); // Array member ended, increments arrayIndex 63 | 64 | void key(const char *key); // The current "object" or "name for a name:value pair" 65 | void value(const char *value); // String value from name:value pair e.g. "1.23" or "rain" 66 | 67 | void whitespace(char c); // Whitespace character in JSON - not used 68 | 69 | void error( const char *message ); // Error message is sent to serial port 70 | 71 | 72 | uint8_t iconIndex(const char *val); // Convert the icon name e.g. "partly-cloudy" to an array 73 | // index to save memory, range 0 to MAX_ICON_INDEX 74 | 75 | private: // Variables used internal to library 76 | 77 | uint16_t minutely_index; // index into the DSW_hourly structure's data arrays 78 | uint16_t hourly_index; // index into the DSW_hourly structure's data arrays 79 | uint16_t daily_index; // index into the DSW_daily structure's data arrays 80 | 81 | // The value storage structures are created and deleted by the sketch and 82 | // a pointer passed via the library getForecast() call the value() function 83 | // is then used to populate the structs with values 84 | DSW_current *current; // pointer provided by sketch to the DSW_current struct 85 | DSW_minutely *minutely; // pointer provided by sketch to the DSW_minutely struct 86 | DSW_hourly *hourly; // pointer provided by sketch to the DSW_hourly struct 87 | DSW_daily *daily; // pointer provided by sketch to the DSW_daily struct 88 | 89 | String valuePath; // object (i.e. sequential key) path (like a "file path") 90 | // taken to the name:value pair in the form "hourly/data" 91 | // so values can be pulled from the correct array. 92 | // Needed since different objects contain "data" arrays. 93 | 94 | String data_set; // A copy of the last object name at the head of an array 95 | // short equivalent to path. 96 | 97 | bool parseOK; // true if the parse been completed 98 | // (does not mean data values gathered are good!) 99 | 100 | String currentParent; // Current object e.g. "daily" 101 | uint16_t objectLevel; // Object level, increments for new object, decrements at end 102 | String currentKey; // Name key of the name:value pair e.g "temperature" 103 | String arrayPath; // Path to name:value pair e.g. "daily/data" 104 | uint16_t arrayIndex; // Array index e.g. 5 for day 5 forecast, qualify with arrayPath 105 | 106 | // Lookup table to convert an array index to a weather icon bmp filename e.g. rain.bmp 107 | 108 | // A partly-cloudy-night means a clear day as noted in issue #7 109 | const char* iconList[MAX_ICON_INDEX + 1] = {"unknown", "rain", "sleet", "snow", "clear-day", 110 | "clear-night", "partly-cloudy-day", "partly-cloudy-night", "cloudy", "fog", 111 | "wind", "none" }; 112 | 113 | }; 114 | 115 | /*************************************************************************************** 116 | ***************************************************************************************/ 117 | #endif 118 | -------------------------------------------------------------------------------- /Data_Point_Set.h: -------------------------------------------------------------------------------- 1 | // The structures below are the repository for the data values extracted from the 2 | // JSON message. The structures are popolated with the extracted data by the "value()" 3 | // member function in the main DarkSkyWeather.cpp file. 4 | 5 | // Some structs contain arrays so watch out for memory consumption. With DarkSky you can 6 | // request a subset of the full weather report but this library grabs all values with 7 | // one GET request to avoid exceeding the 1000 free request count per day (count reset 8 | // at 00:00 UTC). 1000 per day means ~40 per hour. As the weather forcast changes slowly 9 | // the example requests the forecast every 15 minutes, so adapting to reduce memory 10 | // by requesting current, daily, hourly etc forescasts individually can be done. 11 | 12 | // The content is zero or "" when first created. 13 | 14 | #ifndef MINIMISE_DATA_POINTS // Full set of values populated if not defined 15 | /*************************************************************************************** 16 | ** Description: Structure for current weather 17 | ***************************************************************************************/ 18 | typedef struct DSW_current { 19 | 20 | String timezone; 21 | uint32_t time = 0; 22 | String summary; 23 | uint8_t icon = 0; 24 | float precipIntensity = 0; 25 | uint8_t precipType = NO_VALUE; 26 | uint8_t precipProbability = 0; 27 | float temperature = 0; 28 | uint8_t humidity = 0; 29 | float pressure = 0; 30 | float windSpeed = 0; 31 | float windGust = 0; 32 | uint16_t windBearing = 0; 33 | uint8_t cloudCover = 0; 34 | } DSW_current; 35 | 36 | /*************************************************************************************** 37 | ** Description: Structure for minutely weather 38 | ***************************************************************************************/ 39 | typedef struct DSW_minutely { 40 | 41 | String overallSummary; 42 | uint8_t icon = 0; 43 | uint32_t time[MAX_MINUTES] = { 0 }; // maybe store as a minute start time + minute 44 | // count uint8_t as 0-59 45 | float precipIntensity[MAX_MINUTES] = { 0 }; 46 | uint8_t precipProbability[MAX_MINUTES] = { 0 }; 47 | 48 | } DSW_minutely; 49 | 50 | /*************************************************************************************** 51 | ** Description: Structure for hourly weather 52 | ***************************************************************************************/ 53 | typedef struct DSW_hourly { 54 | 55 | String overallSummary; 56 | 57 | String summary[MAX_HOURS]; 58 | uint32_t time[MAX_HOURS] = { 0 }; 59 | float precipIntensity[MAX_HOURS] = { 0 }; 60 | uint8_t precipType[MAX_HOURS] = { NO_VALUE }; 61 | uint8_t precipProbability[MAX_HOURS] = { 0 }; 62 | float precipAccumulation[MAX_HOURS] = { 0 }; 63 | float temperature[MAX_HOURS] = { 0 }; 64 | float pressure[MAX_HOURS] = { 0 }; 65 | uint8_t cloudCover[MAX_HOURS] = { 0 }; 66 | 67 | } DSW_hourly; 68 | 69 | /*************************************************************************************** 70 | ** Description: Structure for daily weather 71 | ***************************************************************************************/ 72 | typedef struct DSW_daily { 73 | 74 | String overallSummary; 75 | 76 | String summary[MAX_DAYS]; 77 | uint32_t time[MAX_DAYS] = { 0 }; 78 | uint8_t icon[MAX_DAYS] = { 0 }; 79 | uint32_t sunriseTime[MAX_DAYS] = { 0 }; 80 | uint32_t sunsetTime[MAX_DAYS] = { 0 }; 81 | uint8_t moonPhase[MAX_DAYS] = { 0 }; 82 | float precipIntensity[MAX_DAYS] = { 0 }; 83 | uint8_t precipProbability[MAX_DAYS] = { 0 }; 84 | uint8_t precipType[MAX_DAYS] = { NO_VALUE }; 85 | float precipAccumulation[MAX_DAYS] = { 0 }; 86 | float temperatureHigh[MAX_DAYS] = { 0 }; 87 | float temperatureLow[MAX_DAYS] = { 0 }; 88 | uint8_t humidity[MAX_DAYS] = { 0 }; 89 | float pressure[MAX_DAYS] = { 0 }; 90 | float windSpeed[MAX_DAYS] = { 0 }; 91 | float windGust[MAX_DAYS] = { 0 }; 92 | uint16_t windBearing[MAX_DAYS] = { 0 }; 93 | uint8_t cloudCover[MAX_DAYS] = { 0 }; 94 | 95 | } DSW_daily; 96 | 97 | 98 | #else // Collect minimal set of data points for TFT_eSPI examples to reduce RAM needs 99 | 100 | /*************************************************************************************** 101 | ** Description: Structure for current weather 102 | ***************************************************************************************/ 103 | typedef struct TFT_current { 104 | 105 | //String timezone; 106 | uint32_t time = 0; 107 | String summary; 108 | uint8_t icon = 0; 109 | //float precipIntensity = 0; 110 | //uint8_t precipType = NO_VALUE; 111 | //uint8_t precipProbability = 0; 112 | float temperature = 0; 113 | uint8_t humidity = 0; 114 | float pressure = 0; 115 | float windSpeed = 0; 116 | //float windGust = 0; 117 | uint16_t windBearing = 0; 118 | uint8_t cloudCover = 0; 119 | } TFT_current; 120 | 121 | /*************************************************************************************** 122 | ** Description: Structure for minutely weather 123 | ***************************************************************************************/ 124 | #define MAX_MINUTES 60 // Can be up to 60 - not used by TFT_eSPI 125 | typedef struct TFT_minutely { 126 | 127 | //String overallSummary; 128 | //uint8_t icon = 0; 129 | //uint32_t time[MAX_MINUTES] = { 0 }; // seems like a waste to store these.... 130 | //float precipIntensity[MAX_MINUTES] = { 0 }; 131 | //uint8_t precipProbability[MAX_MINUTES] = { 0 }; 132 | 133 | } TFT_minutely; 134 | 135 | /*************************************************************************************** 136 | ** Description: Structure for hourly weather 137 | ***************************************************************************************/ 138 | #define MAX_HOURS 24 // Can be up to 48 - not used by TFT_eSPI 139 | typedef struct TFT_hourly { 140 | 141 | //String overallSummary; 142 | 143 | //String summary[MAX_HOURS]; 144 | //uint32_t time[MAX_HOURS] = { 0 }; 145 | //float precipIntensity[MAX_HOURS] = { 0 }; 146 | //uint8_t precipType[MAX_HOURS] = { NO_VALUE }; 147 | //uint8_t precipProbability[MAX_HOURS] = { 0 }; 148 | //float precipAccumulation[MAX_HOURS] = { 0 }; 149 | //float temperature[MAX_HOURS] = { 0 }; 150 | //float pressure[MAX_HOURS] = { 0 }; 151 | //uint8_t cloudCover[MAX_HOURS] = { 0 }; 152 | 153 | } TFT_hourly; 154 | 155 | /*************************************************************************************** 156 | ** Description: Structure for daily weather 157 | ***************************************************************************************/ 158 | #define MAX_DAYS 5 // Today + 7 days = 8 maximum, make it 5 for TFT_eSPI example 159 | typedef struct TFT_daily { 160 | 161 | String overallSummary; 162 | 163 | String summary[MAX_DAYS]; 164 | uint32_t time[MAX_DAYS] = { 0 }; 165 | uint8_t icon[MAX_DAYS] = { 0 }; 166 | uint32_t sunriseTime[MAX_DAYS] = { 0 }; 167 | uint32_t sunsetTime[MAX_DAYS] = { 0 }; 168 | uint8_t moonPhase[MAX_DAYS] = { 0 }; 169 | //float precipIntensity[MAX_DAYS] = { 0 }; 170 | //uint8_t precipProbability[MAX_DAYS] = { 0 }; 171 | //uint8_t precipType[MAX_DAYS] = { NO_VALUE }; 172 | //float precipAccumulation[MAX_DAYS] = { 0 }; 173 | float temperatureHigh[MAX_DAYS] = { 0 }; 174 | float temperatureLow[MAX_DAYS] = { 0 }; 175 | //uint8_t humidity[MAX_DAYS] = { 0 }; 176 | //float pressure[MAX_DAYS] = { 0 }; 177 | //float windSpeed[MAX_DAYS] = { 0 }; 178 | //float windGust[MAX_DAYS] = { 0 }; 179 | //uint16_t windBearing[MAX_DAYS] = { 0 }; 180 | //uint8_t cloudCover[MAX_DAYS] = { 0 }; 181 | 182 | } TFT_daily; 183 | 184 | #endif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # News: Replacement for DarkSkyWeather available 2 | 3 | I have created a new library which fetches weather from OpenWeather. The library is here: 4 | https://github.com/Bodmer/OpenWeather 5 | 6 | # News: Dark Sky has joined Apple... 7 | 8 | As reported in Issue #28 Apple has purchased Dark Sky and is shutting down the API. 9 | The API will continue to function through the end of 2021. 10 | More Information: 11 | https://blog.darksky.net/ 12 | 13 | # ESP8266 and ESP32 Dark Sky weather client 14 | 15 | Arduino client library for https://darksky.net/dev 16 | 17 | Collects current weather plus daily forecasts. 18 | 19 | Requires the JSON parse library here: 20 | https://github.com/Bodmer/JSON_Decoder 21 | 22 | The DarkSkyWeather_Test example sketch sends collected data to the Serial port for API test. It does not not require a TFT screen. 23 | 24 | The TFT_eSPI_Weather example works with the ESP8266 and ESP32, it displays the weather data on a TFT screen. These examples use anti-aliased fonts and newly created icons: 25 | 26 | ![Weather isons](https://i.imgur.com/luK7Vcj.jpg) 27 | 28 | Latest screen grabs: 29 | 30 | ![TFT splash screen](https://i.imgur.com/gh75gd6.png) 31 | 32 | ![TFT screenshot 1](https://i.imgur.com/ORovwNY.png) 33 | 34 | -------------------------------------------------------------------------------- /User_Setup.h: -------------------------------------------------------------------------------- 1 | 2 | // Configuration settings for DarkSkyWeather library 3 | 4 | 5 | // These parameters set the data point count stored in program memory (not the datapoint 6 | // count sent by the Dark Sky server). So they determine the memory used during collection 7 | // of the data points. 8 | 9 | #define MAX_MINUTES 60 // Maximum "minutely" forecast period, can be up 1 to 60 10 | 11 | #define MAX_HOURS 24 // Maximum "hourly" forecast period, can be up 1 to 48 12 | // // Hourly forecast not used by TFT_eSPI examples 13 | 14 | #define MAX_DAYS 8 // Maximum "daily" forecast periods can be 1 to 8 (Today + 7 days = 8 maximum) 15 | // TFT_eSPI example requires this to be >= 5 (today + 4 forecast days) 16 | 17 | // #define MINIMISE_DATA_POINTS // option to minimise stored values for TFT_eSPI_Weather example 18 | 19 | // Note: If MINIMISE_DATA_POINTS is defined and the "DarkSkyWeather_Test" example 20 | // compiled then a compile error "no member named..." will occur in since data points 21 | // will be missing! Unfortnately compile time options for a library cannot be set in a 22 | // sketch when using the Arduino IDE. 23 | 24 | //#define AXTLS // For ESP8266 only: use older axTLS secure client instead of BearSSL 25 | //#define SECURE_SSL // For ESP8266 only: use SHA1 fingerprint with BearSSL 26 | 27 | 28 | //#define SHOW_HEADER // Debug only - for checking response header via serial message 29 | //#define SHOW_JSON // Debug only - simple serial output formatting of whole JSON message 30 | //#define SHOW_CALLBACK // Debug only to show the decode tree 31 | 32 | // ############################################################################### 33 | // DO NOT tinker below, this is configuration checking that helps stop crashes: 34 | // ############################################################################### 35 | 36 | // Check and correct bad setting 37 | #if (MAX_MINUTES > 60) || (MAX_MINUTES < 1) 38 | #undef MAX_MINUTES 39 | #define MAX_MINUTES 60 // Ignore compiler warning! 40 | #endif 41 | 42 | // Check and correct bad setting 43 | #if (MAX_NOURS > 24) || (MAX_HOURS < 1) 44 | #undef MAX_HOURS 45 | #define MAX_HOURS 24 // Ignore compiler warning! 46 | #endif 47 | 48 | // Check and correct bad setting 49 | #if (MAX_DAYS > 8) || (MAX_DAYS < 1) 50 | #undef MAX_DAYS 51 | #define MAX_DAYS 8 // Ignore compiler warning! 52 | #endif 53 | -------------------------------------------------------------------------------- /examples/My_DarkSkyWeather_Test/My_DarkSkyWeather_Test.ino: -------------------------------------------------------------------------------- 1 | // Sketch for ESP32 to fetch the Weather Forecast from Dark Sky 2 | // an example from the library here: 3 | // https://github.com/Bodmer/DarkSkyWeather 4 | 5 | // Sign up for a key and read API configuration info here: 6 | // https://darksky.net/dev 7 | 8 | // Choose library to load 9 | #ifdef ESP8266 10 | #include 11 | #include 12 | #else // ESP32 13 | #include 14 | #endif 15 | 16 | #include 17 | 18 | #include 19 | 20 | // Just using this library for unix time conversion 21 | #include 22 | 23 | // ===================================================== 24 | // ========= User configured stuff starts here ========= 25 | // Further configuration settings can be found in the 26 | // DarkSkyWeather library "User_Setup.h" file 27 | 28 | #define TIME_OFFSET 0UL * 3600UL // UTC + 0 hour 29 | 30 | // Change to suit your WiFi router 31 | #define SSID "Your_SSID" 32 | #define SSID_PASSWORD "Your_password" 33 | 34 | // Dark Sky API Details, replace x's with your API key 35 | String api_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; // Obtain this from your Dark Sky account 36 | 37 | // Set both longitude and latitude to at least 4 decimal places 38 | String latitude = "27.9881"; // 90.0000 to -90.0000 negative for Southern hemisphere 39 | String longitude = "86.9250"; // 180.000 to -180.000 negative for West 40 | 41 | String units = "si"; // See notes tab 42 | String language = ""; // See notes tab 43 | 44 | // ========= User configured stuff ends here ========= 45 | // ===================================================== 46 | 47 | DS_Weather dsw; // Weather forecast library instance 48 | 49 | void setup() { 50 | Serial.begin(250000); // Fast to stop it holding up the stream 51 | 52 | Serial.printf("Connecting to %s\n", SSID); 53 | 54 | WiFi.begin(SSID, SSID_PASSWORD); 55 | 56 | while (WiFi.status() != WL_CONNECTED) { 57 | delay(500); 58 | Serial.print("."); 59 | } 60 | 61 | Serial.println(); 62 | Serial.print("Connected\n"); 63 | } 64 | 65 | void loop() { 66 | 67 | printCurrentWeather(); 68 | 69 | // We can make 1000 requests a day 70 | delay(5 * 60 * 1000); // Every 5 minutes = 288 requests per day 71 | } 72 | 73 | /*************************************************************************************** 74 | ** Send weather info to serial port 75 | ***************************************************************************************/ 76 | void printCurrentWeather() 77 | { 78 | // Create the structures that hold the retrieved weather 79 | DSW_current *current = new DSW_current; 80 | DSW_minutely *minutely = new DSW_minutely; 81 | DSW_hourly *hourly = new DSW_hourly; 82 | DSW_daily *daily = new DSW_daily; 83 | 84 | time_t time; 85 | 86 | Serial.print("\nRequesting weather information from DarkSky.net... "); 87 | 88 | dsw.getForecast(current, minutely, hourly, daily, api_key, latitude, longitude, units, language); 89 | 90 | Serial.println("Weather from Dark Sky\n"); 91 | 92 | // We can use the timezone to set the offset eventually... 93 | // Serial.print("Timezone : "); Serial.println(current->timezone); 94 | 95 | Serial.println("############### Current weather ###############\n"); 96 | Serial.print("Current time : "); Serial.print(strTime(current->time)); 97 | Serial.print("Current summary : "); Serial.println(current->summary); 98 | Serial.print("Current icon : "); Serial.println(getMeteoconIcon(current->icon)); 99 | Serial.print("Current precipInten : "); Serial.println(current->precipIntensity); 100 | Serial.print("Current precipType : "); Serial.println(getMeteoconIcon(current->precipType)); 101 | Serial.print("Current precipProbability: "); Serial.println(current->precipProbability); 102 | Serial.print("Current temperature : "); Serial.println(current->temperature); 103 | Serial.print("Current humidity : "); Serial.println(current->humidity); 104 | Serial.print("Current pressure : "); Serial.println(current->pressure); 105 | Serial.print("Current wind speed : "); Serial.println(current->windSpeed); 106 | Serial.print("Current wind gust : "); Serial.println(current->windGust); 107 | Serial.print("Current wind dirn : "); Serial.println(current->windBearing); 108 | 109 | Serial.println(); 110 | 111 | Serial.println("############### Minutely weather ###############\n"); 112 | Serial.print("Overall minutely summary : "); Serial.println(minutely->overallSummary); 113 | for (int i = 0; itime[i])); 117 | Serial.print("precipIntensity : "); Serial.println(minutely->precipIntensity[i]); 118 | Serial.print("precipProbability : "); Serial.println(minutely->precipProbability[i]); 119 | Serial.println(); 120 | } 121 | 122 | Serial.println("############### Hourly weather ###############\n"); 123 | Serial.print("Overall hourly summary : "); Serial.println(hourly->overallSummary); 124 | for (int i = 0; isummary[i]); 128 | Serial.print("Time : "); Serial.print(strTime(hourly->time[i])); 129 | Serial.print("precipIntensity : "); Serial.println(hourly->precipIntensity[i]); 130 | Serial.print("precipProbability : "); Serial.println(hourly->precipProbability[i]); 131 | Serial.print("precipType : "); Serial.println(hourly->precipType[i]); 132 | Serial.print("precipAccumulation : "); Serial.println(hourly->precipAccumulation[i]); 133 | Serial.print("temperature : "); Serial.println(hourly->temperature[i]); 134 | Serial.print("pressure : "); Serial.println(hourly->pressure[i]); 135 | Serial.print("cloudCover : "); Serial.println(hourly->cloudCover[i]); 136 | Serial.println(); 137 | } 138 | 139 | Serial.println("############### Daily weather ###############\n"); 140 | Serial.print("Daily summary : "); Serial.println(daily->overallSummary); 141 | Serial.println(); 142 | 143 | for (int i = 0; isummary[i]); 147 | Serial.print("time : "); Serial.print(strTime(daily->time[i])); 148 | Serial.print("Icon : "); Serial.println(getMeteoconIcon(daily->icon[i])); 149 | Serial.print("sunriseTime : "); Serial.print(strTime(daily->sunriseTime[i])); 150 | Serial.print("sunsetTime : "); Serial.print(strTime(daily->sunsetTime[i])); 151 | Serial.print("Moon phase : "); Serial.println(daily->moonPhase[i]); 152 | Serial.print("precipIntensity : "); Serial.println(daily->precipIntensity[i]); 153 | Serial.print("precipProbability : "); Serial.println(daily->precipProbability[i]); 154 | Serial.print("precipType : "); Serial.println(daily->precipType[i]); 155 | Serial.print("precipAccumulation: "); Serial.println(daily->precipAccumulation[i]); 156 | Serial.print("temperatureHigh : "); Serial.println(daily->temperatureHigh[i]); 157 | Serial.print("temperatureLow : "); Serial.println(daily->temperatureLow[i]); 158 | Serial.print("humidity : "); Serial.println(daily->humidity[i]); 159 | Serial.print("pressure : "); Serial.println(daily->pressure[i]); 160 | Serial.print("windSpeed : "); Serial.println(daily->windSpeed[i]); 161 | Serial.print("windGust : "); Serial.println(daily->windGust[i]); 162 | Serial.print("windBearing : "); Serial.println(daily->windBearing[i]); 163 | Serial.print("cloudCover : "); Serial.println(daily->cloudCover[i]); 164 | Serial.println(); 165 | } 166 | 167 | // Delete to free up space and prevent fragmentation as strings change in length 168 | delete current; 169 | delete minutely; 170 | delete hourly; 171 | delete daily; 172 | } 173 | 174 | /*************************************************************************************** 175 | ** Convert unix time to a time string 176 | ***************************************************************************************/ 177 | String strTime(time_t unixTime) 178 | { 179 | unixTime += TIME_OFFSET; 180 | return ctime(&unixTime); 181 | } 182 | 183 | /*************************************************************************************** 184 | ** Get the icon file name from the index number 185 | ***************************************************************************************/ 186 | const char* getMeteoconIcon(uint8_t index) 187 | { 188 | if (index > MAX_ICON_INDEX) index = 0; 189 | return dsw.iconName(index); 190 | } 191 | -------------------------------------------------------------------------------- /examples/My_DarkSkyWeather_Test/Notes.ino: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | [units] should be one of the following: 4 | 5 | auto: automatically select units based on geographic location 6 | ca: same as si, except that windSpeed and windGust are in kilometers per hour 7 | uk2: same as si, except that nearestStormDistance and visibility are in miles, and windSpeed and windGust in miles per hour 8 | us: Imperial units (the default) 9 | si: SI units 10 | 11 | 12 | SI units used: 13 | 14 | summary: Any summaries containing temperature or snow accumulation units will have 15 | their values in degrees Celsius or in centimeters (respectively). 16 | nearestStormDistance: Kilometers. 17 | precipIntensity: Millimeters per hour. 18 | precipIntensityMax: Millimeters per hour. 19 | precipAccumulation: Centimeters. 20 | temperature: Degrees Celsius. 21 | temperatureMin: Degrees Celsius. 22 | temperatureMax: Degrees Celsius. 23 | apparentTemperature: Degrees Celsius. 24 | dewPoint: Degrees Celsius. 25 | windSpeed: Meters per second. 26 | windGust: Meters per second. 27 | pressure: Hectopascals. 28 | visibility: Kilometers. 29 | 30 | moon phase is x 100 0-100% 31 | 32 | lang=[language] optional 33 | 34 | Return summary properties in the desired language. 35 | (Note that units in the summary will be set according to the units parameter, 36 | so be sure to set both parameters appropriately.) language may be: 37 | 38 | ar: Arabic 39 | az: Azerbaijani 40 | be: Belarusian 41 | bg: Bulgarian 42 | bs: Bosnian 43 | ca: Catalan 44 | cs: Czech 45 | da: Danish 46 | de: German 47 | el: Greek 48 | en: English (which is the default) 49 | es: Spanish 50 | et: Estonian 51 | fi: Finnish 52 | fr: French 53 | he: Hebrew 54 | hr: Croatian 55 | hu: Hungarian 56 | id: Indonesian 57 | is: Icelandic 58 | it: Italian 59 | ja: Japanese 60 | ka: Georgian 61 | ko: Korean 62 | kw: Cornish 63 | lv: Latvian 64 | nb: Norwegian Bokmål 65 | nl: Dutch 66 | no: Norwegian Bokmål (alias for nb) 67 | pl: Polish 68 | pt: Portuguese 69 | ro: Romanian 70 | ru: Russian 71 | sk: Slovak 72 | sl: Slovenian 73 | sr: Serbian 74 | sv: Swedish 75 | tet: Tetum 76 | tr: Turkish 77 | uk: Ukrainian 78 | x-pig-latin: Igpay Atinlay 79 | zh: simplified Chinese 80 | zh-tw: traditional Chinese 81 | 82 | */ 83 | -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/All_Settings.h: -------------------------------------------------------------------------------- 1 | // Use the DarkSkyWeather library: https://github.com/Bodmer/DarkSkyWeather 2 | 3 | // The weather icons and fonts are in the sketch data folder, press Ctrl+K 4 | // to view. 5 | 6 | 7 | // >>> IMPORTANT TO PREVENT CRASHES <<< 8 | //>>>>>> For ESP8266 set SPIFFS to at least 2Mbytes before uploading files <<<<<< 9 | 10 | 11 | // >>> DON'T FORGET THIS <<< 12 | // Upload the fonts and icons to SPIFFS using the "Tools" "ESP32 Sketch Data Upload" 13 | // or "ESP8266 Sketch Data Upload" menu option in the IDE. 14 | // To add this option follow instructions here for the ESP8266: 15 | // https://github.com/esp8266/arduino-esp8266fs-plugin 16 | // To add this option follow instructions here for the ESP32: 17 | // https://github.com/me-no-dev/arduino-esp32fs-plugin 18 | 19 | // Close the IDE and open again to see the new menu option. 20 | 21 | ////////////////////////////// 22 | // Setttings defined below 23 | 24 | #define WIFI_SSID "Your_SSID" 25 | #define PASSWORD "Your_password" 26 | 27 | #define TIMEZONE UK // See NTP_Time.h tab for other "Zone references", UK, usMT etc 28 | 29 | // Update every 15 minutes, up to 1000 request per day are free (viz average of ~40 per hour) 30 | const int UPDATE_INTERVAL_SECS = 15 * 60UL; // 15 minutes 31 | 32 | // Pins for the TFT interface are defined in the User_Config.h file inside the TFT_eSPI library 33 | 34 | // For units codes see https://darksky.net/dev/docs, not all supported by library at the moment! 35 | const String units = "si"; 36 | 37 | // Sign up for an account at Dark Sky, change x's to your API key 38 | const String api_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; 39 | 40 | // For language codes see https://darksky.net/dev/docs 41 | const String language = "en"; // Default language = en = English 42 | 43 | // Short day of week abbreviations used in 4 day forecast (change to your language) 44 | const String shortDOW [8] = {"???", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"}; 45 | 46 | // Change the labels to your language here: 47 | const char sunStr[] = "Sun"; 48 | const char cloudStr[] = "Cloud"; 49 | const char humidityStr[] = "Humidity"; 50 | const String moonPhase [8] = {"New", "Waxing", "1st qtr", "Waxing", "Full", "Waning", "Last qtr", "Waning"}; 51 | 52 | // Set the forecast longitude and latitude to at least 4 decimal places 53 | const String latitude = "27.9881"; // 90.0000 to -90.0000 negative for Southern hemisphere 54 | const String longitude = "86.9250"; // 180.000 to -180.000 negative for West 55 | 56 | // End of user settings 57 | ////////////////////////////// 58 | -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/GfxUi.cpp: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | Copyright (c) 2015 by Daniel Eichhorn 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | The above copyright notice and this permission notice shall be included in all 10 | copies or substantial portions of the Software. 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. 18 | See more at http://blog.squix.ch 19 | */ 20 | 21 | // Adapted by Bodmer to use the TFT_eSPI library: https://github.com/Bodmer/TFT_eSPI 22 | // Functions no longer needed weeded out, Jpeg decoder functions added and updated 23 | // drawBMP() updated to buffer input and output pixels and avoid slow seeks 24 | 25 | #include "GfxUi.h" 26 | 27 | GfxUi::GfxUi(TFT_eSPI *tft) { 28 | _tft = tft; 29 | } 30 | 31 | void GfxUi::drawProgressBar(uint16_t x0, uint16_t y0, uint16_t w, uint16_t h, uint8_t percentage, uint16_t frameColor, uint16_t barColor) { 32 | if (percentage == 0) { 33 | _tft->fillRoundRect(x0, y0, w, h, 3, TFT_BLACK); 34 | } 35 | uint8_t margin = 2; 36 | uint16_t barHeight = h - 2 * margin; 37 | uint16_t barWidth = w - 2 * margin; 38 | _tft->drawRoundRect(x0, y0, w, h, 3, frameColor); 39 | _tft->fillRect(x0 + margin, y0 + margin, barWidth * percentage / 100.0, barHeight, barColor); 40 | } 41 | 42 | // Bodmer's streamlined x2 faster "no seek" version 43 | void GfxUi::drawBmp(String filename, uint16_t x, uint16_t y) 44 | { 45 | 46 | if ((x >= _tft->width()) || (y >= _tft->height())) return; 47 | 48 | fs::File bmpFS; 49 | 50 | // Check file exists and open it 51 | // Serial.println(filename); 52 | 53 | // Note: ESP32 passes "open" test even if file does not exist, whereas ESP8266 returns NULL 54 | if ( !SPIFFS.exists(filename) ) 55 | { 56 | Serial.println(F(" File not found")); // Can comment out if not needed 57 | return; 58 | } 59 | 60 | // Open requested file 61 | bmpFS = SPIFFS.open(filename, "r"); 62 | 63 | uint32_t seekOffset; 64 | uint16_t w, h, row, col; 65 | uint8_t r, g, b; 66 | 67 | if (read16(bmpFS) == 0x4D42) 68 | { 69 | read32(bmpFS); 70 | read32(bmpFS); 71 | seekOffset = read32(bmpFS); 72 | read32(bmpFS); 73 | w = read32(bmpFS); 74 | h = read32(bmpFS); 75 | 76 | if ((read16(bmpFS) == 1) && (read16(bmpFS) == 24) && (read32(bmpFS) == 0)) 77 | { 78 | y += h - 1; 79 | 80 | _tft->setSwapBytes(true); 81 | bmpFS.seek(seekOffset); 82 | 83 | // Calculate padding to avoid seek 84 | uint16_t padding = (4 - ((w * 3) & 3)) & 3; 85 | uint8_t lineBuffer[w * 3 + padding]; 86 | 87 | for (row = 0; row < h; row++) { 88 | 89 | bmpFS.read(lineBuffer, sizeof(lineBuffer)); 90 | uint8_t* bptr = lineBuffer; 91 | uint16_t* tptr = (uint16_t*)lineBuffer; 92 | // Convert 24 to 16 bit colours using the same line buffer for results 93 | for (uint16_t col = 0; col < w; col++) 94 | { 95 | b = *bptr++; 96 | g = *bptr++; 97 | r = *bptr++; 98 | *tptr++ = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); 99 | } 100 | 101 | // Push the pixel row to screen, pushImage will crop the line if needed 102 | // y is decremented as the BMP image is drawn bottom up 103 | _tft->pushImage(x, y--, w, 1, (uint16_t*)lineBuffer); 104 | } 105 | } 106 | else Serial.println("BMP format not recognized."); 107 | } 108 | bmpFS.close(); 109 | } 110 | 111 | // These read 16- and 32-bit types from the SD card file. 112 | // BMP data is stored little-endian, Arduino is little-endian too. 113 | // May need to reverse subscript order if porting elsewhere. 114 | 115 | uint16_t GfxUi::read16(fs::File &f) { 116 | uint16_t result; 117 | ((uint8_t *)&result)[0] = f.read(); // LSB 118 | ((uint8_t *)&result)[1] = f.read(); // MSB 119 | return result; 120 | } 121 | 122 | uint32_t GfxUi::read32(fs::File &f) { 123 | uint32_t result; 124 | ((uint8_t *)&result)[0] = f.read(); // LSB 125 | ((uint8_t *)&result)[1] = f.read(); 126 | ((uint8_t *)&result)[2] = f.read(); 127 | ((uint8_t *)&result)[3] = f.read(); // MSB 128 | return result; 129 | } 130 | 131 | /*==================================================================================== 132 | This sketch support functions to render the Jpeg images. 133 | 134 | Created by Bodmer 15th Jan 2017 135 | ==================================================================================*/ 136 | 137 | // Return the minimum of two values a and b 138 | #define minimum(a,b) (((a) < (b)) ? (a) : (b)) 139 | 140 | #define USE_SPI_BUFFER // Comment out to use slower 16 bit pushColor() 141 | 142 | //==================================================================================== 143 | // Opens the image file and prime the Jpeg decoder 144 | //==================================================================================== 145 | void GfxUi::drawJpeg(String filename, int xpos, int ypos) { 146 | 147 | Serial.println("==========================="); 148 | Serial.print("Drawing file: "); Serial.println(filename); 149 | Serial.println("==========================="); 150 | 151 | // Open the named file (the Jpeg decoder library will close it after rendering image) 152 | fs::File jpegFile = SPIFFS.open( filename, "r"); // File handle reference for SPIFFS 153 | // File jpegFile = SD.open( filename, FILE_READ); // or, file handle reference for SD library 154 | 155 | if ( !jpegFile ) { 156 | Serial.print("ERROR: File \""); Serial.print(filename); Serial.println ("\" not found!"); 157 | return; 158 | } 159 | 160 | // Use one of the three following methods to initialise the decoder: 161 | //boolean decoded = JpegDec.decodeFsFile(jpegFile); // Pass a SPIFFS file handle to the decoder, 162 | //boolean decoded = JpegDec.decodeSdFile(jpegFile); // or pass the SD file handle to the decoder, 163 | boolean decoded = JpegDec.decodeFsFile(filename); // or pass the filename (leading / distinguishes SPIFFS files) 164 | // Note: the filename can be a String or character array type 165 | if (decoded) { 166 | // print information about the image to the serial port 167 | jpegInfo(); 168 | 169 | // render the image onto the screen at given coordinates 170 | jpegRender(xpos, ypos); 171 | } 172 | else { 173 | Serial.println("Jpeg file format not supported!"); 174 | } 175 | } 176 | 177 | //==================================================================================== 178 | // Decode and render the Jpeg image onto the TFT screen 179 | //==================================================================================== 180 | void GfxUi::jpegRender(int xpos, int ypos) { 181 | 182 | // retrieve infomration about the image 183 | uint16_t *pImg; 184 | uint16_t mcu_w = JpegDec.MCUWidth; 185 | uint16_t mcu_h = JpegDec.MCUHeight; 186 | uint32_t max_x = JpegDec.width; 187 | uint32_t max_y = JpegDec.height; 188 | 189 | // Jpeg images are draw as a set of image block (tiles) called Minimum Coding Units (MCUs) 190 | // Typically these MCUs are 16x16 pixel blocks 191 | // Determine the width and height of the right and bottom edge image blocks 192 | uint32_t min_w = minimum(mcu_w, max_x % mcu_w); 193 | uint32_t min_h = minimum(mcu_h, max_y % mcu_h); 194 | 195 | // save the current image block size 196 | uint32_t win_w = mcu_w; 197 | uint32_t win_h = mcu_h; 198 | 199 | // record the current time so we can measure how long it takes to draw an image 200 | uint32_t drawTime = millis(); 201 | 202 | // save the coordinate of the right and bottom edges to assist image cropping 203 | // to the screen size 204 | max_x += xpos; 205 | max_y += ypos; 206 | 207 | // read each MCU block until there are no more 208 | #ifdef USE_SPI_BUFFER 209 | while( JpegDec.readSwappedBytes()){ // Swap byte order so the SPI buffer can be used 210 | #else 211 | while ( JpegDec.read()) { // Normal byte order read 212 | #endif 213 | // save a pointer to the image block 214 | pImg = JpegDec.pImage; 215 | 216 | // calculate where the image block should be drawn on the screen 217 | int mcu_x = JpegDec.MCUx * mcu_w + xpos; 218 | int mcu_y = JpegDec.MCUy * mcu_h + ypos; 219 | 220 | // check if the image block size needs to be changed for the right edge 221 | if (mcu_x + mcu_w <= max_x) win_w = mcu_w; 222 | else win_w = min_w; 223 | 224 | // check if the image block size needs to be changed for the bottom edge 225 | if (mcu_y + mcu_h <= max_y) win_h = mcu_h; 226 | else win_h = min_h; 227 | 228 | // copy pixels into a contiguous block 229 | if (win_w != mcu_w) 230 | { 231 | uint16_t *cImg; 232 | int p = 0; 233 | cImg = pImg + win_w; 234 | for (int h = 1; h < win_h; h++) 235 | { 236 | p += mcu_w; 237 | for (int w = 0; w < win_w; w++) 238 | { 239 | *cImg = *(pImg + w + p); 240 | cImg++; 241 | } 242 | } 243 | } 244 | 245 | // draw image MCU block only if it will fit on the screen 246 | if ( ( mcu_x + win_w) <= _tft->width() && ( mcu_y + win_h) <= _tft->height()) 247 | { 248 | _tft->pushImage(mcu_x, mcu_y, win_w, win_h, pImg); 249 | } 250 | 251 | else if ( ( mcu_y + win_h) >= _tft->height()) JpegDec.abort(); 252 | 253 | } 254 | 255 | // calculate how long it took to draw the image 256 | drawTime = millis() - drawTime; // Calculate the time it took 257 | 258 | // print the results to the serial port 259 | Serial.print ("Total render time was : "); Serial.print(drawTime); Serial.println(" ms"); 260 | Serial.println("====================================="); 261 | 262 | } 263 | 264 | //==================================================================================== 265 | // Print information decoded from the Jpeg image 266 | //==================================================================================== 267 | void GfxUi::jpegInfo() { 268 | 269 | Serial.println("==============="); 270 | Serial.println("JPEG image info"); 271 | Serial.println("==============="); 272 | Serial.print ("Width :"); Serial.println(JpegDec.width); 273 | Serial.print ("Height :"); Serial.println(JpegDec.height); 274 | Serial.print ("Components :"); Serial.println(JpegDec.comps); 275 | Serial.print ("MCU / row :"); Serial.println(JpegDec.MCUSPerRow); 276 | Serial.print ("MCU / col :"); Serial.println(JpegDec.MCUSPerCol); 277 | Serial.print ("Scan type :"); Serial.println(JpegDec.scanType); 278 | Serial.print ("MCU width :"); Serial.println(JpegDec.MCUWidth); 279 | Serial.print ("MCU height :"); Serial.println(JpegDec.MCUHeight); 280 | Serial.println("==============="); 281 | Serial.println(""); 282 | 283 | } 284 | //==================================================================================== 285 | -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/GfxUi.h: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | Copyright (c) 2015 by Daniel Eichhorn 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | The above copyright notice and this permission notice shall be included in all 10 | copies or substantial portions of the Software. 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. 18 | See more at http://blog.squix.ch 19 | */ 20 | 21 | // Adapted by Bodmer to use the TFT_eSPI library: 22 | // https://github.com/Bodmer/TFT_eSPI 23 | 24 | 25 | 26 | #include // Hardware-specific library 27 | 28 | #define FS_NO_GLOBALS // Avoid conflict with SD library File type definition 29 | #include 30 | 31 | // JPEG decoder library 32 | #include 33 | 34 | #ifndef _GFX_UI_H 35 | #define _GFX_UI_H 36 | 37 | // Maximum of 85 for BUFFPIXEL as 3 x this value is stored in an 8 bit variable! 38 | // 32 is an efficient size for SPIFFS due to SPI hardware pipeline buffer size 39 | // A larger value of 80 is better for SD cards 40 | #define BUFFPIXEL 32 41 | 42 | class GfxUi { 43 | public: 44 | GfxUi(TFT_eSPI * tft); 45 | void drawBmp(String filename, uint16_t x, uint16_t y); 46 | void drawProgressBar(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t percentage, uint16_t frameColor, uint16_t barColor); 47 | void jpegInfo(); 48 | void drawJpeg(String filename, int xpos, int ypos); 49 | void jpegRender(int xpos, int ypos); 50 | 51 | private: 52 | TFT_eSPI * _tft; 53 | uint16_t read16(fs::File &f); 54 | uint32_t read32(fs::File &f); 55 | 56 | }; 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/NTP_Time.h: -------------------------------------------------------------------------------- 1 | //==================================================================================== 2 | // Libraries 3 | //==================================================================================== 4 | 5 | // Time library: 6 | // https://github.com/PaulStoffregen/Time 7 | 8 | #include 9 | 10 | // Time zone correction library: 11 | // https://github.com/JChristensen/Timezone 12 | 13 | // TODO: Use time zone correction in JSON reply from Dark Sky 14 | 15 | #include 16 | 17 | // Libraries built into IDE 18 | #ifdef ESP8266 19 | #include 20 | #else 21 | #include 22 | #endif 23 | 24 | #include 25 | 26 | // A UDP instance to let us send and receive packets over UDP 27 | WiFiUDP udp; 28 | 29 | //==================================================================================== 30 | // Settings 31 | //==================================================================================== 32 | 33 | #ifdef ESP32 // Temporary fix, ESP8266 fails to communicate with some servers... 34 | // Try to use pool url instead so the server IP address is looked up from those available 35 | // (use a pool server in your own country to improve response time and reliability) 36 | //const char* ntpServerName = "time.nist.gov"; 37 | //const char* ntpServerName = "pool.ntp.org"; 38 | const char* ntpServerName = "time.google.com"; 39 | #else 40 | // Try to use pool url instead so the server IP address is looked up from those available 41 | // (use a pool server in your own country to improve response time and reliability) 42 | // const char* ntpServerName = "time.nist.gov"; 43 | const char* ntpServerName = "pool.ntp.org"; 44 | //const char* ntpServerName = "time.google.com"; 45 | #endif 46 | 47 | // Try not to use hard-coded IP addresses which might change, you can if you want though... 48 | //IPAddress timeServerIP(129, 6, 15, 30); // time-c.nist.gov NTP server 49 | //IPAddress timeServerIP(24, 56, 178, 140); // wwv.nist.gov NTP server 50 | IPAddress timeServerIP; // Use server pool 51 | 52 | // Example time zone and DST rules, see Timezone library documents to see how 53 | // to add more time zones https://github.com/JChristensen/Timezone 54 | 55 | // Zone reference "UK" United Kingdom (London, Belfast) 56 | TimeChangeRule BST = {"BST", Last, Sun, Mar, 1, 60}; //British Summer (Daylight saving) Time 57 | TimeChangeRule GMT = {"GMT", Last, Sun, Oct, 2, 0}; //Standard Time 58 | Timezone UK(BST, GMT); 59 | 60 | // Zone reference "euCET" Central European Time (Frankfurt, Paris) 61 | TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120}; //Central European Summer Time 62 | TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60}; //Central European Standard Time 63 | Timezone euCET(CEST, CET); 64 | 65 | // Zone reference "ausET" Australia Eastern Time Zone (Sydney, Melbourne) 66 | TimeChangeRule aEDT = {"AEDT", First, Sun, Oct, 2, 660}; //UTC + 11 hours 67 | TimeChangeRule aEST = {"AEST", First, Sun, Apr, 3, 600}; //UTC + 10 hours 68 | Timezone ausET(aEDT, aEST); 69 | 70 | // Zone reference "usET US Eastern Time Zone (New York, Detroit) 71 | TimeChangeRule usEDT = {"EDT", Second, Sun, Mar, 2, -240}; //Eastern Daylight Time = UTC - 4 hours 72 | TimeChangeRule usEST = {"EST", First, Sun, Nov, 2, -300}; //Eastern Standard Time = UTC - 5 hours 73 | Timezone usET(usEDT, usEST); 74 | 75 | // Zone reference "usCT" US Central Time Zone (Chicago, Houston) 76 | TimeChangeRule usCDT = {"CDT", Second, dowSunday, Mar, 2, -300}; 77 | TimeChangeRule usCST = {"CST", First, dowSunday, Nov, 2, -360}; 78 | Timezone usCT(usCDT, usCST); 79 | 80 | // Zone reference "usMT" US Mountain Time Zone (Denver, Salt Lake City) 81 | TimeChangeRule usMDT = {"MDT", Second, dowSunday, Mar, 2, -360}; 82 | TimeChangeRule usMST = {"MST", First, dowSunday, Nov, 2, -420}; 83 | Timezone usMT(usMDT, usMST); 84 | 85 | // Zone reference "usAZ" Arizona is US Mountain Time Zone but does not use DST 86 | Timezone usAZ(usMST, usMST); 87 | 88 | // Zone reference "usPT" US Pacific Time Zone (Las Vegas, Los Angeles) 89 | TimeChangeRule usPDT = {"PDT", Second, dowSunday, Mar, 2, -420}; 90 | TimeChangeRule usPST = {"PST", First, dowSunday, Nov, 2, -480}; 91 | Timezone usPT(usPDT, usPST); 92 | 93 | 94 | //==================================================================================== 95 | // Variables 96 | //==================================================================================== 97 | TimeChangeRule *tz1_Code; // Pointer to the time change rule, use to get the TZ abbrev, e.g. "GMT" 98 | 99 | time_t utc = 0; 100 | 101 | bool timeValid = false; 102 | 103 | unsigned int localPort = 2390; // local port to listen for UDP packets 104 | 105 | const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message 106 | 107 | byte packetBuffer[ NTP_PACKET_SIZE ]; //buffer to hold incoming and outgoing packets 108 | 109 | uint8_t lastMinute = 0; 110 | 111 | uint32_t nextSendTime = 0; 112 | uint32_t newRecvTime = 0; 113 | uint32_t lastRecvTime = 0; 114 | 115 | uint32_t newTickTime = 0; 116 | uint32_t lastTickTime = 0; 117 | 118 | bool rebooted = 1; 119 | 120 | uint32_t no_packet_count = 0; 121 | 122 | 123 | //==================================================================================== 124 | // Function prototype 125 | //==================================================================================== 126 | 127 | void syncTime(void); 128 | void displayTime(void); 129 | void printTime(time_t zone, char *tzCode); 130 | void decodeNTP(void); 131 | void sendNTPpacket(IPAddress& address); 132 | 133 | //==================================================================================== 134 | // Update Time 135 | //==================================================================================== 136 | void syncTime(void) 137 | { 138 | // Don't send too often so we don't trigger Denial of Service 139 | if (nextSendTime < millis()) { 140 | // Get a random server from the pool 141 | WiFi.hostByName(ntpServerName, timeServerIP); 142 | nextSendTime = millis() + 5000; 143 | sendNTPpacket(timeServerIP); // send an NTP packet to a time server 144 | decodeNTP(); 145 | } 146 | } 147 | 148 | //==================================================================================== 149 | // Send an NTP request to the time server at the given address 150 | //==================================================================================== 151 | void sendNTPpacket(IPAddress& address) 152 | { 153 | // Serial.println("sending NTP packet..."); 154 | // set all bytes in the buffer to 0 155 | memset(packetBuffer, 0, NTP_PACKET_SIZE); 156 | // Initialize values needed to form NTP request 157 | // (see URL above for details on the packets) 158 | packetBuffer[0] = 0b11100011; // LI, Version, Mode 159 | packetBuffer[1] = 0; // Stratum, or type of clock 160 | packetBuffer[2] = 6; // Polling Interval 161 | packetBuffer[3] = 0xEC; // Peer Clock Precision 162 | 163 | // 8 bytes of zero for Root Delay & Root Dispersion 164 | 165 | packetBuffer[12] = 49; 166 | packetBuffer[13] = 0x4E; 167 | packetBuffer[14] = 49; 168 | packetBuffer[15] = 52; 169 | 170 | // all NTP fields have been given values, now 171 | // you can send a packet requesting a timestamp: 172 | udp.beginPacket(address, 123); //NTP requests are to port 123 173 | udp.write(packetBuffer, NTP_PACKET_SIZE); 174 | udp.endPacket(); 175 | } 176 | 177 | //==================================================================================== 178 | // Decode the NTP message and print status to serial port 179 | //==================================================================================== 180 | void decodeNTP(void) 181 | { 182 | timeValid = false; 183 | uint32_t waitTime = millis() + 500; 184 | while (millis() < waitTime && !timeValid) 185 | { 186 | yield(); 187 | if (udp.parsePacket()) 188 | { 189 | newRecvTime = millis(); 190 | 191 | // We've received a packet, read the data from it 192 | udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer 193 | 194 | Serial.print("\nNTP response time was : "); 195 | Serial.print(500 - (waitTime - newRecvTime)); 196 | Serial.println(" ms"); 197 | 198 | Serial.print("Time since last sync is: "); 199 | Serial.print((newRecvTime - lastRecvTime) / 1000.0); 200 | Serial.println(" s"); 201 | lastRecvTime = newRecvTime; 202 | 203 | // The timestamp starts at byte 40 of the received packet and is four bytes, 204 | // or two words, long. First, extract the two words: 205 | unsigned long highWord = word(packetBuffer[40], packetBuffer[41]); 206 | unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]); 207 | 208 | // Combine the four bytes (two words) into a long integer 209 | // this is NTP time (seconds since Jan 1 1900): 210 | unsigned long secsSince1900 = highWord << 16 | lowWord; 211 | 212 | // Now convert NTP Unix time (Seconds since Jan 1 1900) into everyday time: 213 | // UTC time starts on Jan 1 1970. In seconds the difference is 2208988800: 214 | utc = secsSince1900 - 2208988800UL; 215 | 216 | setTime(utc); // Set system clock to utc time (not time zone compensated) 217 | 218 | timeValid = true; 219 | 220 | // Print the hour, minute and second: 221 | Serial.print("Received NTP UTC time : "); 222 | 223 | uint8_t hh = hour(utc); 224 | Serial.print(hh); // print the hour (86400 equals secs per day) 225 | 226 | Serial.print(':'); 227 | uint8_t mm = minute(utc); 228 | if (mm < 10 ) Serial.print('0'); 229 | Serial.print(mm); // print the minute (3600 equals secs per minute) 230 | 231 | Serial.print(':'); 232 | uint8_t ss = second(utc); 233 | if ( ss < 10 ) Serial.print('0'); 234 | Serial.println(ss); // print the second 235 | } 236 | } 237 | 238 | // Keep a count of missing or bad NTP replies 239 | 240 | if ( timeValid ) { 241 | no_packet_count = 0; 242 | } 243 | else 244 | { 245 | Serial.println("\nNo NTP reply, trying again in 1 minute..."); 246 | no_packet_count++; 247 | } 248 | 249 | if (no_packet_count >= 10) { 250 | no_packet_count = 0; 251 | // TODO: Flag the lack of sync on the display 252 | Serial.println("\nNo NTP packet in last 10 minutes"); 253 | } 254 | } 255 | 256 | //==================================================================================== 257 | // Debug use only 258 | //==================================================================================== 259 | void printTime(time_t t, char *tzCode) 260 | { 261 | String dateString = dayStr(weekday(t)); 262 | dateString += " "; 263 | dateString += day(t); 264 | if (day(t) == 1 || day(t) == 21 || day(t) == 31) dateString += "st"; 265 | else if (day(t) == 2 || day(t) == 22) dateString += "nd"; 266 | else if (day(t) == 3 || day(t) == 23) dateString += "rd"; 267 | else dateString += "th"; 268 | 269 | dateString += " "; 270 | dateString += monthStr(month(t)); 271 | dateString += " "; 272 | dateString += year(t); 273 | 274 | // Print time to serial port 275 | Serial.print(hour(t)); 276 | Serial.print(":"); 277 | Serial.print(minute(t)); 278 | Serial.print(":"); 279 | Serial.print(second(t)); 280 | Serial.print(" "); 281 | // Print time t 282 | Serial.print(tzCode); 283 | Serial.print(" "); 284 | 285 | // Print date 286 | Serial.print(day(t)); 287 | Serial.print("/"); 288 | Serial.print(month(t)); 289 | Serial.print("/"); 290 | Serial.print(year(t)); 291 | Serial.print(" "); 292 | 293 | // Now test some other functions that might be useful one day! 294 | Serial.print(dayStr(weekday(t))); 295 | Serial.print(" "); 296 | Serial.print(monthStr(month(t))); 297 | Serial.print(" "); 298 | Serial.print(dayShortStr(weekday(t))); 299 | Serial.print(" "); 300 | Serial.print(monthShortStr(month(t))); 301 | Serial.println(); 302 | } 303 | 304 | //==================================================================================== 305 | -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/SPIFFS_Support.h: -------------------------------------------------------------------------------- 1 | /*==================================================================================== 2 | This sketch contains support functions for the ESP6266 SPIFFS filing system 3 | 4 | Created by Bodmer 15th Jan 2017 5 | ==================================================================================*/ 6 | void listDir(fs::FS &fs, const char * dirname, uint8_t levels); 7 | 8 | // ------------------------------------------------------------------------- 9 | // List SPIFFS files in a neat format for ESP8266 or ESP32 10 | // ------------------------------------------------------------------------- 11 | void listFiles(void) { 12 | Serial.println(); 13 | Serial.println("SPIFFS files found:"); 14 | 15 | #ifdef ESP32 16 | listDir(SPIFFS, "/", true); 17 | #else 18 | fs::Dir dir = SPIFFS.openDir("/"); // Root directory 19 | String line = "====================================="; 20 | 21 | Serial.println(line); 22 | Serial.println(" File name Size"); 23 | Serial.println(line); 24 | 25 | while (dir.next()) { 26 | String fileName = dir.fileName(); 27 | Serial.print(fileName); 28 | // File path can be 31 characters maximum in SPIFFS 29 | int spaces = 33 - fileName.length(); // Tabulate nicely 30 | if (spaces < 1) spaces = 1; 31 | while (spaces--) Serial.print(" "); 32 | fs::File f = dir.openFile("r"); 33 | Serial.print(f.size()); Serial.println(" bytes"); 34 | yield(); 35 | } 36 | 37 | Serial.println(line); 38 | #endif 39 | Serial.println(); 40 | delay(1000); 41 | } 42 | 43 | #ifdef ESP32 44 | void listDir(fs::FS &fs, const char * dirname, uint8_t levels) { 45 | Serial.printf("Listing directory: %s\n", dirname); 46 | 47 | fs::File root = fs.open(dirname); 48 | if (!root) { 49 | Serial.println("Failed to open directory"); 50 | return; 51 | } 52 | if (!root.isDirectory()) { 53 | Serial.println("Not a directory"); 54 | return; 55 | } 56 | 57 | fs::File file = root.openNextFile(); 58 | while (file) { 59 | 60 | if (file.isDirectory()) { 61 | Serial.print("DIR : "); 62 | String fileName = file.name(); 63 | Serial.print(fileName); 64 | if (levels) { 65 | listDir(fs, file.name(), levels - 1); 66 | } 67 | } else { 68 | String fileName = file.name(); 69 | Serial.print(" " + fileName); 70 | // File path can be 31 characters maximum in SPIFFS 71 | int spaces = 33 - fileName.length(); // Tabulate nicely 72 | if (spaces < 1) spaces = 1; 73 | while (spaces--) Serial.print(" "); 74 | String fileSize = (String) file.size(); 75 | spaces = 8 - fileSize.length(); // Tabulate nicely 76 | if (spaces < 1) spaces = 1; 77 | while (spaces--) Serial.print(" "); 78 | Serial.println(fileSize + " bytes"); 79 | } 80 | 81 | file = root.openNextFile(); 82 | } 83 | } 84 | #endif 85 | //==================================================================================== 86 | -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/ScreenGrabClient.ino: -------------------------------------------------------------------------------- 1 | // This is a copy of the processing sketch that can be used to capture the images 2 | // Not needed by this sketch, used during development with screenSaver() functions. 3 | 4 | // Copy the sketch below into the Processing IDE and remove the /* and */ at the beginning and 5 | // end. 6 | 7 | // The sketch runs in Processing version 3.3 (or later) on a PC, it can be downloaded here: 8 | // https://processing.org/download/ 9 | 10 | /* 11 | 12 | // This is a Processing sketch, see https://processing.org/ to download the IDE 13 | 14 | // The sketch is a client that requests TFT screenshots from an Arduino board. 15 | // The Arduino must call a screenshot server function to respond with pixels. 16 | 17 | // It has been created to work with the TFT_eSPI library here: 18 | // https://github.com/Bodmer/TFT_eSPI 19 | 20 | // The sketch must only be run when the designated serial port is available and enumerated 21 | // otherwise the screenshot window may freeze and that process will need to be terminated 22 | // This is a limitation of the Processing environment and not the sketch. 23 | // If anyone knows how to determine if a serial port is available at start up the PM me 24 | // on (Bodmer) the Arduino forum. 25 | 26 | // The block below contains variables that the user may need to change for a particular setup 27 | // As a minimum set the serial port and baud rate must be defined. The capture window is 28 | // automatically resized for landscape, portrait and different TFT resolutions. 29 | 30 | // Captured images are stored in the sketch folder, use the Processing IDE "Sketch" menu 31 | // option "Show Sketch Folder" or press Ctrl+K 32 | 33 | // Created by: Bodmer 5/3/17 34 | // Updated by: Bodmer 12/3/17 35 | // Version: 0.07 36 | 37 | // MIT licence applies, all text above must be included in derivative works 38 | 39 | 40 | // ########################################################################################### 41 | // # These are the values to change for a particular setup # 42 | // # 43 | int serial_port = 0; // Use enumerated value from list provided when sketch is run # 44 | // # 45 | // On an Arduino Due Programming Port use a baud rate of:115200) # 46 | // On an Arduino Due Native USB Port use a baud rate of any value # 47 | int serial_baud_rate = 250000; // # 48 | // # 49 | // Change the image file type saved here, comment out all but one # 50 | //String image_type = ".jpg"; // # 51 | String image_type = ".png"; // Lossless compression # 52 | //String image_type = ".bmp"; // # 53 | //String image_type = ".tif"; // # 54 | // # 55 | boolean save_border = true; // Save the image with a border # 56 | int border = 5; // Border pixel width # 57 | boolean fade = false; // Fade out image after saving # 58 | // # 59 | int max_images = 100; // Maximum of numbered file images before over-writing files # 60 | // # 61 | int max_allowed = 1000; // Maximum number of save images allowed before a restart # 62 | // # 63 | // # End of the values to change for a particular setup # 64 | // ########################################################################################### 65 | 66 | // These are default values, this sketch obtains the actual values from the Arduino board 67 | int tft_width = 480; // default TFT width (automatic - sent by Arduino) 68 | int tft_height = 480; // default TFT height (automatic - sent by Arduino) 69 | int color_bytes = 2; // 2 for 16 bit, 3 for three RGB bytes (automatic - sent by Arduino) 70 | 71 | import processing.serial.*; 72 | 73 | Serial serial; // Create an instance called serial 74 | 75 | int serialCount = 0; // Count of colour bytes arriving 76 | 77 | // Stage window graded background colours 78 | color bgcolor1 = color(0, 100, 104); // Arduino IDE style background color 1 79 | color bgcolor2 = color(77, 183, 187); // Arduino IDE style background color 2 80 | //color bgcolor2 = color(255, 255, 255); // White 81 | 82 | // TFT image frame greyscale value (dark grey) 83 | color frameColor = 42; 84 | 85 | color buttonStopped = color(255, 0, 0); 86 | color buttonRunning = color(128, 204, 206); 87 | color buttonDimmed = color(180, 0, 0); 88 | boolean dimmed = false; 89 | boolean running = true; 90 | boolean mouseClick = false; 91 | 92 | int[] rgb = new int[3]; // Buffer for the colour bytes 93 | int indexRed = 0; // Colour byte index in the array 94 | int indexGreen = 1; 95 | int indexBlue = 2; 96 | 97 | int n = 0; 98 | 99 | int x_offset = (500 - tft_width) /2; // Image offsets in the window 100 | int y_offset = 20; 101 | 102 | int xpos = 0, ypos = 0; // Current pixel position 103 | 104 | int beginTime = 0; 105 | int pixelWaitTime = 1000; // Maximum 1000ms wait for image pixels to arrive 106 | int lastPixelTime = 0; // Time that "image send" command was sent 107 | 108 | int requestTime = 0; 109 | int requestCount = 0; 110 | 111 | int state = 0; // State machine current state 112 | 113 | int progress_bar = 0; // Console progress bar dot count 114 | int pixel_count = 0; // Number of pixels read for 1 screen 115 | float percentage = 0; // Percentage of pixels received 116 | 117 | int saved_image_count = 0; // Stats - number of images processed 118 | int bad_image_count = 0; // Stats - number of images that had lost pixels 119 | String filename = ""; 120 | 121 | int drawLoopCount = 0; // Used for the fade out 122 | 123 | void setup() { 124 | 125 | size(500, 540); // Stage size, can handle 480 pixels wide screen 126 | noStroke(); // No border on the next thing drawn 127 | noSmooth(); // No anti-aliasing to avoid adjacent pixel colour merging 128 | 129 | // Graded background and title 130 | drawWindow(); 131 | 132 | frameRate(2000); // High frame rate so draw() loops fast 133 | 134 | // Print a list of the available serial ports 135 | println("-----------------------"); 136 | println("Available Serial Ports:"); 137 | println("-----------------------"); 138 | printArray(Serial.list()); 139 | println("-----------------------"); 140 | 141 | print("Port currently used: ["); 142 | print(serial_port); 143 | println("]"); 144 | 145 | String portName = Serial.list()[serial_port]; 146 | 147 | serial = new Serial(this, portName, serial_baud_rate); 148 | 149 | state = 99; 150 | } 151 | 152 | void draw() { 153 | 154 | if (mouseClick) buttonClicked(); 155 | 156 | switch(state) { 157 | 158 | case 0: // Init varaibles, send start request 159 | if (running) { 160 | tint(0, 0, 0, 255); 161 | flushBuffer(); 162 | println(""); 163 | print("Ready: "); 164 | 165 | xpos = 0; 166 | ypos = 0; 167 | serialCount = 0; 168 | progress_bar = 0; 169 | pixel_count = 0; 170 | percentage = 0; 171 | drawLoopCount = frameCount; 172 | lastPixelTime = millis() + 1000; 173 | 174 | state = 1; 175 | } else { 176 | if (millis() > beginTime) { 177 | beginTime = millis() + 500; 178 | dimmed = !dimmed; 179 | if (dimmed) drawButton(buttonDimmed); 180 | else drawButton(buttonStopped); 181 | } 182 | } 183 | break; 184 | 185 | case 1: // Console message, give server some time 186 | print("requesting image "); 187 | serial.write("S"); 188 | delay(10); 189 | beginTime = millis(); 190 | requestTime = millis() + 1000; 191 | requestCount = 1; 192 | state = 2; 193 | break; 194 | 195 | case 2: // Get size and set start time for rendering duration report 196 | if (millis() > requestTime) { 197 | requestCount++; 198 | print("*"); 199 | serial.clear(); 200 | serial.write("S"); 201 | if (requestCount > 32) { 202 | requestCount = 0; 203 | System.err.println(" - no response!"); 204 | state = 0; 205 | } 206 | requestTime = millis() + 1000; 207 | } 208 | if ( getSize() == true ) { // Go to next state when we have the size and bits per pixel 209 | getFilename(); 210 | flushBuffer(); // Precaution in case image header size increases in later versions 211 | lastPixelTime = millis() + 1000; 212 | beginTime = millis(); 213 | state = 3; 214 | } 215 | break; 216 | 217 | case 3: // Request pixels and render returned RGB values 218 | state = renderPixels(); // State will change when all pixels are rendered 219 | 220 | // Request more pixels, changing the number requested allows the average transfer rate to 221 | // be controlled. The pixel transfer rate is dependant on four things: 222 | // 1. The frame rate defined in this Processing sketch in setup() 223 | // 2. The baud rate of the serial link (~10 bit periods per byte) 224 | // 3. The number of request bytes 'R' sent in the lines below 225 | // 4. The number of pixels sent in a burst by the server sketch (defined via NPIXELS) 226 | 227 | //serial.write("RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR"); // 32 x NPIXELS more 228 | serial.write("RRRRRRRRRRRRRRRR"); // 16 x NPIXELS more 229 | //serial.write("RRRRRRRR"); // 8 x NPIXELS more 230 | //serial.write("RRRR"); // 4 x NPIXELS more 231 | //serial.write("RR"); // 2 x NPIXELS more 232 | //serial.write("R"); // 1 x NPIXELS more 233 | if (!running) state = 4; 234 | break; 235 | 236 | case 4: // Pixel receive time-out, flush serial buffer 237 | flushBuffer(); 238 | state = 6; 239 | break; 240 | 241 | case 5: // Save the image to the sketch folder (Ctrl+K to access) 242 | saveScreenshot(); 243 | saved_image_count++; 244 | println("Saved image count = " + saved_image_count); 245 | if (bad_image_count > 0) System.err.println(" Bad image count = " + bad_image_count); 246 | drawLoopCount = frameCount; // Reset value ready for counting in step 6 247 | state = 6; 248 | break; 249 | 250 | case 6: // Fade the old image if enabled 251 | if ( fadedImage() == true ) state = 0; // Go to next state when image has faded 252 | break; 253 | 254 | case 99: // Draw image viewer window 255 | drawWindow(); 256 | delay(50); // Delay here seems to be required for the IDE console to get ready 257 | state = 0; 258 | break; 259 | 260 | default: 261 | println(""); 262 | System.err.println("Error state reached - check sketch!"); 263 | break; 264 | } 265 | } 266 | 267 | void drawWindow() 268 | { 269 | // Graded background in Arduino colours 270 | for (int i = 0; i < height - 25; i++) { 271 | float inter = map(i, 0, height - 25, 0, 1); 272 | color c = lerpColor(bgcolor1, bgcolor2, inter); 273 | stroke(c); 274 | line(0, i, 500, i); 275 | } 276 | fill(bgcolor2); 277 | rect( 0, height-25, width-1, 24); 278 | textAlign(CENTER); 279 | textSize(20); 280 | fill(0); 281 | text("Bodmer's TFT image viewer", width/2, height-6); 282 | 283 | if (running) drawButton(buttonRunning); 284 | else drawButton(buttonStopped); 285 | } 286 | 287 | void flushBuffer() 288 | { 289 | //println("Clearing serial pipe after a time-out"); 290 | int clearTime = millis() + 50; 291 | while ( millis() < clearTime ) serial.clear(); 292 | } 293 | 294 | boolean getSize() 295 | { 296 | if ( serial.available() > 6 ) { 297 | println(); 298 | char code = (char)serial.read(); 299 | if (code == 'W') { 300 | tft_width = serial.read()<<8 | serial.read(); 301 | } 302 | code = (char)serial.read(); 303 | if (code == 'H') { 304 | tft_height = serial.read()<<8 | serial.read(); 305 | } 306 | code = (char)serial.read(); 307 | if (code == 'Y') { 308 | int bits_per_pixel = (char)serial.read(); 309 | if (bits_per_pixel == 24) color_bytes = 3; 310 | else color_bytes = 2; 311 | } 312 | code = (char)serial.read(); 313 | if (code == '?') { 314 | drawWindow(); 315 | 316 | x_offset = (500 - tft_width) /2; 317 | tint(0, 0, 0, 255); 318 | noStroke(); 319 | fill(frameColor); 320 | rect((width - tft_width)/2 - border, y_offset - border, tft_width + 2 * border, tft_height + 2 * border); 321 | return true; 322 | } 323 | } 324 | return false; 325 | } 326 | 327 | void saveScreenshot() 328 | { 329 | println(); 330 | if (saved_image_count < max_allowed) 331 | { 332 | if (filename == "") filename = "tft_screen_" + (n++); 333 | filename = filename + image_type; 334 | println("Saving image as \"" + filename + "\""); 335 | if (save_border) 336 | { 337 | PImage partialSave = get(x_offset - border, y_offset - border, tft_width + 2*border, tft_height + 2*border); 338 | partialSave.save(filename); 339 | } else { 340 | PImage partialSave = get(x_offset, y_offset, tft_width, tft_height); 341 | partialSave.save(filename); 342 | } 343 | 344 | if (n>=max_images) n = 0; 345 | } 346 | else 347 | { 348 | System.err.println(max_allowed + " saved image count exceeded, restart the sketch"); 349 | } 350 | } 351 | 352 | void getFilename() 353 | { 354 | int readTime = millis() + 20; 355 | int inByte = 0; 356 | filename = ""; 357 | while ( serial.available() > 0 && millis() < readTime && inByte != '.') 358 | { 359 | inByte = serial.read(); 360 | if (inByte == ' ') inByte = '_'; 361 | if ( unicodeCheck(inByte) ) filename += (char)inByte; 362 | } 363 | 364 | inByte = serial.read(); 365 | if (inByte == '@') filename += "_" + timeCode(); 366 | else if (inByte == '#') filename += "_" + saved_image_count%100; 367 | else if (inByte == '%') filename += "_" + millis(); 368 | else if (inByte != '*') filename = ""; 369 | 370 | inByte = serial.read(); 371 | if (inByte == 'j') image_type =".jpg"; 372 | else if (inByte == 'b') image_type =".bmp"; 373 | else if (inByte == 'p') image_type =".png"; 374 | else if (inByte == 't') image_type =".tif"; 375 | } 376 | 377 | boolean unicodeCheck(int unicode) 378 | { 379 | if ( unicode >= '0' && unicode <= '9' ) return true; 380 | if ( (unicode >= 'A' && unicode <= 'Z' ) || (unicode >= 'a' && unicode <= 'z')) return true; 381 | if ( unicode == '_' || unicode == '/' ) return true; 382 | return false; 383 | } 384 | 385 | String timeCode() 386 | { 387 | String timeCode = (int)year() + "_" + (int)month() + "_" + (int)day() + "_"; 388 | timeCode += (int)hour() + "_" + (int)minute() + "_" + (int)second(); 389 | return timeCode; 390 | } 391 | 392 | int renderPixels() 393 | { 394 | if ( serial.available() > 0 ) { 395 | 396 | // Add the latest byte from the serial port to array: 397 | while (serial.available()>0) 398 | { 399 | rgb[serialCount++] = serial.read(); 400 | 401 | // If we have 3 colour bytes: 402 | if ( serialCount >= color_bytes ) { 403 | serialCount = 0; 404 | pixel_count++; 405 | if (color_bytes == 3) 406 | { 407 | stroke(rgb[indexRed], rgb[indexGreen], rgb[indexBlue], 1000); 408 | } else 409 | { // Can cater for various byte orders 410 | //stroke( (rgb[0] & 0x1F)<<3, (rgb[0] & 0xE0)>>3 | (rgb[1] & 0x07)<<5, (rgb[1] & 0xF8)); 411 | //stroke( (rgb[1] & 0x1F)<<3, (rgb[1] & 0xE0)>>3 | (rgb[0] & 0x07)<<5, (rgb[0] & 0xF8)); 412 | stroke( (rgb[0] & 0xF8), (rgb[1] & 0xE0)>>3 | (rgb[0] & 0x07)<<5, (rgb[1] & 0x1F)<<3); 413 | //stroke( (rgb[1] & 0xF8), (rgb[0] & 0xE0)>>3 | (rgb[1] & 0x07)<<5, (rgb[0] & 0x1F)<<3); 414 | } 415 | // We get some pixel merge aliasing if smooth() is defined, so draw pixel twice 416 | point(xpos + x_offset, ypos + y_offset); 417 | //point(xpos + x_offset, ypos + y_offset); 418 | 419 | lastPixelTime = millis(); 420 | xpos++; 421 | if (xpos >= tft_width) { 422 | xpos = 0; 423 | progressBar(); 424 | ypos++; 425 | if (ypos>=tft_height) { 426 | ypos = 0; 427 | if ((int)percentage <100) { 428 | while (progress_bar++ < 64) print(" "); 429 | percent(100); 430 | } 431 | println("Image fetch time = " + (millis()-beginTime)/1000.0 + " s"); 432 | return 5; 433 | } 434 | } 435 | } 436 | } 437 | } else 438 | { 439 | if (millis() > (lastPixelTime + pixelWaitTime)) 440 | { 441 | println(""); 442 | System.err.println(pixelWaitTime + "ms time-out for pixels exceeded..."); 443 | if (pixel_count > 0) { 444 | bad_image_count++; 445 | System.err.print("Pixels missing = " + (tft_width * tft_height - pixel_count)); 446 | System.err.println(", corrupted image not saved"); 447 | System.err.println("Good image count = " + saved_image_count); 448 | System.err.println(" Bad image count = " + bad_image_count); 449 | } 450 | return 4; 451 | } 452 | } 453 | return 3; 454 | } 455 | 456 | void progressBar() 457 | { 458 | progress_bar++; 459 | print("."); 460 | if (progress_bar >63) 461 | { 462 | progress_bar = 0; 463 | percentage = 0.5 + 100 * pixel_count/(0.001 + tft_width * tft_height); 464 | percent(percentage); 465 | } 466 | } 467 | 468 | void percent(float percentage) 469 | { 470 | if (percentage > 100) percentage = 100; 471 | println(" [ " + (int)percentage + "% ]"); 472 | textAlign(LEFT); 473 | textSize(16); 474 | noStroke(); 475 | fill(bgcolor2); 476 | rect(10, height - 25, 70, 20); 477 | fill(0); 478 | text(" [ " + (int)percentage + "% ]", 10, height-8); 479 | } 480 | 481 | boolean fadedImage() 482 | { 483 | int opacity = frameCount - drawLoopCount; // So we get increasing fade 484 | if (fade) 485 | { 486 | tint(255, opacity); 487 | //image(tft_img, x_offset, y_offset); 488 | noStroke(); 489 | fill(50, 50, 50, opacity); 490 | rect( (width - tft_width)/2, y_offset, tft_width, tft_height); 491 | delay(10); 492 | } 493 | if (opacity > 50) // End fade after 50 cycles 494 | { 495 | return true; 496 | } 497 | return false; 498 | } 499 | 500 | void drawButton(color buttonColor) 501 | { 502 | stroke(0); 503 | fill(buttonColor); 504 | rect(500 - 100, 540 - 26, 80, 24); 505 | textAlign(CENTER); 506 | textSize(20); 507 | fill(0); 508 | if (running) text(" Pause ", 500 - 60, height-7); 509 | else text(" Run ", 500 - 60, height-7); 510 | } 511 | 512 | void buttonClicked() 513 | { 514 | mouseClick = false; 515 | if (running) { 516 | running = false; 517 | drawButton(buttonStopped); 518 | System.err.println(""); 519 | System.err.println("Stopped - click 'Run' button: "); 520 | //noStroke(); 521 | //fill(50); 522 | //rect( (width - tft_width)/2, y_offset, tft_width, tft_height); 523 | beginTime = millis() + 500; 524 | dimmed = false; 525 | state = 4; 526 | } else { 527 | running = true; 528 | drawButton(buttonRunning); 529 | } 530 | } 531 | 532 | void mousePressed() { 533 | if (mouseX > (500 - 100) && mouseX < (500 - 20) && mouseY > (540 - 26) && mouseY < (540 - 2)) { 534 | mouseClick = true; 535 | } 536 | } 537 | 538 | */ 539 | -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/ScreenGrabServer.ino: -------------------------------------------------------------------------------- 1 | // Reads a screen image off the TFT and send it to a processing client sketch 2 | // over the serial port. Use a high baud rate, e.g. for an ESP8266: 3 | // Serial.begin(921600); 4 | 5 | // ONLY works if TFT CGRAM can be read back, not all displays support this! 6 | // Tested with ILI9341 display 7 | 8 | // At 921600 baud a 320 x 240 image with 16 bit colour transfers can be sent to the 9 | // PC client in ~1.67s and 24 bit colour in ~2.5s which is close to the theoretical 10 | // minimum transfer time. 11 | 12 | // This sketch has been created to work with the TFT_eSPI library here: 13 | // https://github.com/Bodmer/TFT_eSPI 14 | 15 | // Created by: Bodmer 27/1/17 16 | // Updated by: Bodmer 10/3/17 17 | // Version: 0.07 18 | 19 | // MIT licence applies, all text above must be included in derivative works 20 | 21 | //==================================================================================== 22 | // Definitions 23 | //==================================================================================== 24 | 25 | #define PIXEL_TIMEOUT 100 // 100ms Time-out between pixel requests 26 | #define START_TIMEOUT 10000 // 10s Maximum time to wait at start transfer 27 | 28 | #define BITS_PER_PIXEL 16 // 24 for RGB colour format, 16 for 565 colour format 29 | 30 | // File names must be alpha-numeric characters (0-9, a-z, A-Z) or "/" underscore "_" 31 | // other ascii characters are stripped out by client, including / generates 32 | // sub-directories 33 | #define DEFAULT_FILENAME "tft_screenshots/screenshot" // In case none is specified 34 | #define FILE_TYPE "png" // jpg, bmp, png, tif are valid 35 | 36 | // Filename extension 37 | // '#' = add 0-9, '@' = add timestamp, '%' add millis() timestamp, '*' = add nothing 38 | // '@' and '%' will generate new unique filenames, so beware of cluttering up your 39 | // hard drive with lots of images! The PC client sketch is set to limit the number of 40 | // saved images to 1000 and will then prompt for a restart. 41 | #define FILE_EXT '%' 42 | 43 | // Number of pixels to send in a burst (minimum of 1), no benefit above 8 44 | // NPIXELS values and render times: 1 = 5.0s, 2 = 1.75s, 4 = 1.68s, 8 = 1.67s 45 | #define NPIXELS 8 // Must be integer division of both TFT width and TFT height 46 | 47 | boolean screenServer(void); 48 | boolean screenServer(String filename); 49 | boolean serialScreenServer(String filename); 50 | void sendParameters(String filename); 51 | 52 | //==================================================================================== 53 | // Screen server call with no filename 54 | //==================================================================================== 55 | // Start a screen dump server (serial or network) - no filename specified 56 | boolean screenServer(void) 57 | { 58 | // With no filename the screenshot will be saved with a default name e.g. tft_screen_#.xxx 59 | // where # is a number 0-9 and xxx is a file type specified below 60 | return screenServer(DEFAULT_FILENAME); 61 | } 62 | 63 | //==================================================================================== 64 | // Screen server call with filename 65 | //==================================================================================== 66 | // Start a screen dump server (serial or network) - filename specified 67 | boolean screenServer(String filename) 68 | { 69 | boolean result = serialScreenServer(filename); // Screenshot serial port server 70 | //boolean result = wifiScreenServer(filename); // Screenshot WiFi UDP port server (WIP) 71 | 72 | delay(0); // Equivalent to yield() for ESP8266; 73 | 74 | //Serial.println(); 75 | //if (result) Serial.println(F("Screen dump passed :-)")); 76 | //else Serial.println(F("Screen dump failed :-(")); 77 | 78 | return result; 79 | } 80 | 81 | //==================================================================================== 82 | // Serial server function that sends the data to the client 83 | //==================================================================================== 84 | boolean serialScreenServer(String filename) 85 | { 86 | // Precautionary receive buffer garbage flush for 50ms 87 | uint32_t clearTime = millis() + 50; 88 | while ( millis() < clearTime && Serial.read() >= 0) delay(0); // Equivalent to yield() for ESP8266; 89 | 90 | boolean wait = true; 91 | uint32_t lastCmdTime = millis(); // Initialise start of command time-out 92 | 93 | // Wait for the starting flag with a start time-out 94 | while (wait) 95 | { 96 | delay(0); // Equivalent to yield() for ESP8266; 97 | // Check serial buffer 98 | if (Serial.available() > 0) { 99 | // Read the command byte 100 | uint8_t cmd = Serial.read(); 101 | // If it is 'S' (start command) then clear the serial buffer for 100ms and stop waiting 102 | if ( cmd == 'S' ) { 103 | // Precautionary receive buffer garbage flush for 50ms 104 | clearTime = millis() + 50; 105 | while ( millis() < clearTime && Serial.read() >= 0) delay(0); // Equivalent to yield() for ESP8266; 106 | 107 | wait = false; // No need to wait anymore 108 | lastCmdTime = millis(); // Set last received command time 109 | 110 | // Send screen size etc using a simple header with delimiters for client checks 111 | sendParameters(filename); 112 | } 113 | } 114 | else 115 | { 116 | // Check for time-out 117 | if ( millis() > lastCmdTime + START_TIMEOUT) return false; 118 | } 119 | } 120 | 121 | uint8_t color[3 * NPIXELS]; // RGB and 565 format color buffer for N pixels 122 | 123 | // Send all the pixels on the whole screen 124 | for ( uint32_t y = 0; y < tft.height(); y++) 125 | { 126 | // Increment x by NPIXELS as we send NPIXELS for every byte received 127 | for ( uint32_t x = 0; x < tft.width(); x += NPIXELS) 128 | { 129 | delay(0); // Equivalent to yield() for ESP8266; 130 | 131 | // Wait here for serial data to arrive or a time-out elapses 132 | while ( Serial.available() == 0 ) 133 | { 134 | if ( millis() > lastCmdTime + PIXEL_TIMEOUT) return false; 135 | delay(0); // Equivalent to yield() for ESP8266; 136 | } 137 | 138 | // Serial data must be available to get here, read 1 byte and 139 | // respond with N pixels, i.e. N x 3 RGB bytes or N x 2 565 format bytes 140 | if ( Serial.read() == 'X' ) { 141 | // X command byte means abort, so clear the buffer and return 142 | clearTime = millis() + 50; 143 | while ( millis() < clearTime && Serial.read() >= 0) delay(0); // Equivalent to yield() for ESP8266; 144 | return false; 145 | } 146 | // Save arrival time of the read command (for later time-out check) 147 | lastCmdTime = millis(); 148 | 149 | #if defined BITS_PER_PIXEL && BITS_PER_PIXEL >= 24 150 | // Fetch N RGB pixels from x,y and put in buffer 151 | tft.readRectRGB(x, y, NPIXELS, 1, color); 152 | // Send buffer to client 153 | Serial.write(color, 3 * NPIXELS); // Write all pixels in the buffer 154 | #else 155 | // Fetch N 565 format pixels from x,y and put in buffer 156 | tft.readRect(x, y, NPIXELS, 1, (uint16_t *)color); 157 | // Send buffer to client 158 | Serial.write(color, 2 * NPIXELS); // Write all pixels in the buffer 159 | #endif 160 | } 161 | } 162 | 163 | Serial.flush(); // Make sure all pixel bytes have been despatched 164 | 165 | return true; 166 | } 167 | 168 | //==================================================================================== 169 | // Send screen size etc using a simple header with delimiters for client checks 170 | //==================================================================================== 171 | void sendParameters(String filename) 172 | { 173 | Serial.write('W'); // Width 174 | Serial.write(tft.width() >> 8); 175 | Serial.write(tft.width() & 0xFF); 176 | 177 | Serial.write('H'); // Height 178 | Serial.write(tft.height() >> 8); 179 | Serial.write(tft.height() & 0xFF); 180 | 181 | Serial.write('Y'); // Bits per pixel (16 or 24) 182 | Serial.write(BITS_PER_PIXEL); 183 | 184 | Serial.write('?'); // Filename next 185 | Serial.print(filename); 186 | 187 | Serial.write('.'); // End of filename marker 188 | 189 | Serial.write(FILE_EXT); // Filename extension identifier 190 | 191 | Serial.write(*FILE_TYPE); // First character defines file type j,b,p,t 192 | } 193 | -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/TFT_eSPI_weather.ino: -------------------------------------------------------------------------------- 1 | // Example from Dark Sky Weather library: https://github.com/Bodmer/DarkSkyWeather 2 | // Adapted by Bodmer to use the TFT_eSPI library: https://github.com/Bodmer/TFT_eSPI 3 | 4 | // This sketch is compatible with the ESP8266 and ESP32 5 | 6 | // >>> IMPORTANT <<< 7 | // Modify setup in All_Settings.h tab to configure your location etc 8 | 9 | // >>> EVEN MORE IMPORTANT TO PREVENT CRASHES <<< 10 | //>>>>>> For ESP8266 set SPIFFS to at least 2Mbytes before uploading files <<<<<< 11 | 12 | // ESP8266/ESP32 pin connections to the TFT are defined in the TFT_eSPI library. 13 | 14 | // Original by Daniel Eichhorn, see license at end of file. 15 | 16 | #define SERIAL_MESSAGES // For serial output weather reports 17 | //#define SCREEN_SERVER // For dumping screentshots from TFT 18 | //#define RANDOM_LOCATION // Test only, selects random weather location every refresh 19 | //#define FORMAT_SPIFFS // Wipe SPIFFS and all files! 20 | 21 | // This sketch uses font files created from the Noto family of fonts as bitmaps 22 | // generated from these fonts may be freely distributed: 23 | // https://www.google.com/get/noto/ 24 | 25 | // A processing sketch to create new fonts can be found in the Tools folder of TFT_eSPI 26 | // https://github.com/Bodmer/TFT_eSPI/tree/master/Tools/Create_Smooth_Font/Create_font 27 | // New fonts can be generated to include language specifc characters. The Noto family 28 | // of fonts has an extensive character set coverage. 29 | 30 | // Json streaming parser (do not use IDE library manager version) to use is here: 31 | // https://github.com/Bodmer/JSON_Decoder 32 | 33 | #define AA_FONT_SMALL "fonts/NotoSansBold15" // 15 point sans serif bold 34 | #define AA_FONT_LARGE "fonts/NotoSansBold36" // 36 point sans serif bold 35 | /*************************************************************************************** 36 | ** Load the libraries and settings 37 | ***************************************************************************************/ 38 | #include 39 | 40 | #include 41 | #include // https://github.com/Bodmer/TFT_eSPI 42 | 43 | // Additional functions 44 | #include "GfxUi.h" // Attached to this sketch 45 | #include "SPIFFS_Support.h" // Attached to this sketch 46 | 47 | #ifdef ESP8266 48 | #include 49 | #else 50 | #include 51 | #endif 52 | 53 | 54 | // check settings.h for adapting to your needs 55 | #include "All_Settings.h" 56 | 57 | #include // https://github.com/Bodmer/JSON_Decoder 58 | 59 | #include // Latest here: https://github.com/Bodmer/DarkSkyWeather 60 | 61 | #include "NTP_Time.h" // Attached to this sketch, see that tab for library needs 62 | 63 | /*************************************************************************************** 64 | ** Define the globals and class instances 65 | ***************************************************************************************/ 66 | 67 | TFT_eSPI tft = TFT_eSPI(); // Invoke custom library 68 | //TFT_eSprite spr = TFT_eSprite(&tft); // Sprites not used 69 | 70 | DS_Weather dsw; // Weather forcast library instance 71 | 72 | DSW_current *current; // Pointers to structs that temporarily holds weather data 73 | DSW_hourly *hourly; // Not used 74 | DSW_daily *daily; 75 | 76 | boolean booted = true; 77 | 78 | GfxUi ui = GfxUi(&tft); // Jpeg and bmpDraw functions TODO: pull outside of a class 79 | 80 | long lastDownloadUpdate = millis(); 81 | 82 | /*************************************************************************************** 83 | ** Declare prototypes 84 | ***************************************************************************************/ 85 | void updateData(); 86 | void drawProgress(uint8_t percentage, String text); 87 | void drawTime(); 88 | void drawCurrentWeather(); 89 | void drawForecast(); 90 | void drawForecastDetail(uint16_t x, uint16_t y, uint8_t dayIndex); 91 | const char* getMeteoconIcon(uint8_t iconIndex); 92 | void drawAstronomy(); 93 | void drawSeparator(uint16_t y); 94 | void fillSegment(int x, int y, int start_angle, int sub_angle, int r, unsigned int colour); 95 | String strDate(time_t unixTime); 96 | String strTime(time_t unixTime); 97 | void printWeather(void); 98 | int leftOffset(String text, String sub); 99 | int rightOffset(String text, String sub); 100 | int splitIndex(String text); 101 | 102 | /*************************************************************************************** 103 | ** Setup 104 | ***************************************************************************************/ 105 | void setup() { 106 | Serial.begin(250000); 107 | 108 | tft.begin(); 109 | tft.fillScreen(TFT_BLACK); 110 | 111 | SPIFFS.begin(); 112 | listFiles(); 113 | 114 | // Enable if you want to erase SPIFFS, this takes some time! 115 | // then disable and reload sketch to avoid reformatting on every boot! 116 | #ifdef FORMAT_SPIFFS 117 | tft.setTextDatum(BC_DATUM); // Bottom Centre datum 118 | tft.drawString("Formatting SPIFFS, so wait!", 120, 195); SPIFFS.format(); 119 | #endif 120 | 121 | // Draw splash screen for Dark Sky T&C compliance 122 | if (SPIFFS.exists("/splash/DarkSky.jpg") == true) ui.drawJpeg("/splash/DarkSky.jpg", 0, 0); 123 | 124 | delay(2000); 125 | 126 | // Clear bottom section of screen 127 | tft.fillRect(0, 206, 240, 320 - 206, TFT_BLACK); 128 | 129 | tft.loadFont(AA_FONT_SMALL); 130 | tft.setTextDatum(BC_DATUM); // Bottom Centre datum 131 | tft.setTextColor(TFT_LIGHTGREY, TFT_BLACK); 132 | 133 | tft.drawString("Original by: blog.squix.org", 120, 260); 134 | tft.drawString("Adapted by: Bodmer", 120, 280); 135 | 136 | tft.setTextColor(TFT_YELLOW, TFT_BLACK); 137 | 138 | delay(2000); 139 | 140 | tft.fillRect(0, 206, 240, 320 - 206, TFT_BLACK); 141 | 142 | tft.drawString("Connecting to WiFi", 120, 240); 143 | tft.setTextPadding(240); // Pad next drawString() text to full width to over-write old text 144 | 145 | //Manual Wifi connection 146 | //WiFi.mode(WIFI_STA); // Needed? 147 | WiFi.begin(WIFI_SSID, PASSWORD); 148 | 149 | while (WiFi.status() != WL_CONNECTED) { 150 | delay(1000); 151 | Serial.print("."); 152 | } 153 | Serial.println(); 154 | 155 | tft.setTextDatum(BC_DATUM); 156 | tft.setTextPadding(240); // Pad next drawString() text to full width to over-write old text 157 | tft.drawString(" ", 120, 220); // Clear line above using set padding width 158 | tft.drawString("Fetching weather data...", 120, 240); 159 | //delay(500); 160 | 161 | // Fetch the time 162 | udp.begin(localPort); 163 | syncTime(); 164 | 165 | tft.unloadFont(); 166 | } 167 | 168 | /*************************************************************************************** 169 | ** Loop 170 | ***************************************************************************************/ 171 | void loop() { 172 | 173 | // Check if we should update weather information 174 | if (booted || (millis() - lastDownloadUpdate > 1000UL * UPDATE_INTERVAL_SECS)) 175 | { 176 | updateData(); 177 | lastDownloadUpdate = millis(); 178 | } 179 | 180 | // If minute has changed then request new time from NTP server 181 | if (booted || minute() != lastMinute) 182 | { 183 | // Update displayed time first as we may have to wait for a response 184 | drawTime(); 185 | lastMinute = minute(); 186 | 187 | // Request and synchronise the local clock 188 | syncTime(); 189 | 190 | #ifdef SCREEN_SERVER 191 | screenServer(); 192 | #endif 193 | } 194 | 195 | booted = false; 196 | } 197 | 198 | /*************************************************************************************** 199 | ** Fetch the weather data and update screen 200 | ***************************************************************************************/ 201 | // Update the internet based information and update screen 202 | void updateData() { 203 | // booted = true; // Test only 204 | // booted = false; // Test only 205 | 206 | tft.loadFont(AA_FONT_SMALL); 207 | 208 | if (booted) drawProgress(20, "Updating time..."); 209 | else fillSegment(22, 22, 0, (int) (20 * 3.6), 16, TFT_NAVY); 210 | 211 | if (booted) drawProgress(50, "Updating conditions..."); 212 | else fillSegment(22, 22, 0, (int) (50 * 3.6), 16, TFT_NAVY); 213 | 214 | // Create the structures that hold the retrieved weather 215 | current = new DSW_current; 216 | daily = new DSW_daily; 217 | 218 | // hourly not used by this sketch, set to nullptr 219 | hourly = nullptr; //new DSW_hourly; 220 | 221 | #ifdef RANDOM_LOCATION // Randomly choose a place on Earth to test icons etc 222 | String latitude = ""; 223 | latitude = (random(180) - 90); 224 | String longitude = ""; 225 | longitude = (random(360) - 180); 226 | #endif 227 | 228 | bool parsed = dsw.getForecast(current, hourly, daily, api_key, latitude, longitude, units, language); 229 | 230 | printWeather(); // For debug, turn on output with #define SERIAL_MESSAGES 231 | 232 | if (booted) 233 | { 234 | drawProgress(100, "Done..."); 235 | delay(2000); 236 | tft.fillScreen(TFT_BLACK); 237 | } 238 | else 239 | { 240 | fillSegment(22, 22, 0, 360, 16, TFT_NAVY); 241 | fillSegment(22, 22, 0, 360, 22, TFT_BLACK); 242 | } 243 | 244 | //tft.fillScreen(TFT_CYAN); // For text padding and update graphics zone checking only 245 | 246 | if (parsed) 247 | { 248 | drawCurrentWeather(); 249 | drawForecast(); 250 | drawAstronomy(); 251 | 252 | tft.unloadFont(); 253 | 254 | // Update the temperature here so we dont need keep 255 | // loading and unloading font which takes time 256 | tft.loadFont(AA_FONT_LARGE); 257 | tft.setTextDatum(TR_DATUM); 258 | tft.setTextColor(TFT_YELLOW, TFT_BLACK); 259 | 260 | // Font ASCII code 0xB0 is a degree symbol, but o used instead in small font 261 | tft.setTextPadding(tft.textWidth(" -88")); // Max width of values 262 | 263 | String weatherText = ""; 264 | weatherText = (int16_t) current->temperature; // Make it integer temperature 265 | tft.drawString(weatherText, 215, 95); // + "°" symbol is big... use o in small font 266 | } 267 | else 268 | { 269 | Serial.println("Failed to get weather"); 270 | } 271 | 272 | // Delete to free up space 273 | delete current; 274 | delete hourly; 275 | delete daily; 276 | 277 | tft.unloadFont(); 278 | } 279 | 280 | /*************************************************************************************** 281 | ** Update progress bar 282 | ***************************************************************************************/ 283 | void drawProgress(uint8_t percentage, String text) { 284 | tft.setTextDatum(BC_DATUM); 285 | tft.setTextColor(TFT_ORANGE, TFT_BLACK); 286 | tft.setTextPadding(240); 287 | tft.drawString(text, 120, 260); 288 | 289 | ui.drawProgressBar(10, 269, 240 - 20, 15, percentage, TFT_WHITE, TFT_BLUE); 290 | 291 | tft.setTextPadding(0); 292 | } 293 | 294 | /*************************************************************************************** 295 | ** Draw the clock digits 296 | ***************************************************************************************/ 297 | void drawTime() { 298 | tft.loadFont(AA_FONT_LARGE); 299 | 300 | // Convert UTC to local time, returns zone code in tz1_Code, e.g "GMT" 301 | time_t local_time = TIMEZONE.toLocal(now(), &tz1_Code); 302 | 303 | String timeNow = ""; 304 | 305 | if (hour(local_time) < 10) timeNow += "0"; 306 | timeNow += hour(local_time); 307 | timeNow += ":"; 308 | if (minute(local_time) < 10) timeNow += "0"; 309 | timeNow += minute(local_time); 310 | 311 | tft.setTextDatum(BC_DATUM); 312 | tft.setTextColor(TFT_YELLOW, TFT_BLACK); 313 | tft.setTextPadding(tft.textWidth(" 44:44 ")); // String width + margin 314 | tft.drawString(timeNow, 120, 53); 315 | 316 | drawSeparator(51); 317 | 318 | tft.setTextPadding(0); 319 | 320 | tft.unloadFont(); 321 | } 322 | 323 | /*************************************************************************************** 324 | ** Draw the current weather 325 | ***************************************************************************************/ 326 | void drawCurrentWeather() { 327 | String date = "Updated: " + strDate(current->time); 328 | 329 | tft.setTextDatum(BC_DATUM); 330 | tft.setTextColor(TFT_ORANGE, TFT_BLACK); 331 | tft.setTextPadding(tft.textWidth(" Updated: Mmm 44 44:44 ")); // String width + margin 332 | tft.drawString(date, 120, 16); 333 | 334 | String weatherIcon = ""; 335 | 336 | String currentSummary = current->summary; 337 | currentSummary.toLowerCase(); 338 | 339 | if (currentSummary.indexOf("light rain") >= 0 && (current->icon == ICON_RAIN)) weatherIcon = "lightRain"; 340 | else if (currentSummary.indexOf("drizzle") >= 0 && (current->icon == ICON_RAIN)) weatherIcon = "drizzle"; 341 | else weatherIcon = getMeteoconIcon(current->icon); 342 | 343 | //uint32_t dt = millis(); 344 | ui.drawBmp("/icon/" + weatherIcon + ".bmp", 0, 53); 345 | //Serial.print("Icon draw time = "); Serial.println(millis()-dt); 346 | 347 | // Weather Text 348 | String weatherText = current->summary; 349 | 350 | tft.setTextDatum(BR_DATUM); 351 | tft.setTextColor(TFT_ORANGE, TFT_BLACK); 352 | 353 | int splitPoint = 0; 354 | int xpos = 235; 355 | splitPoint = splitIndex(weatherText); 356 | 357 | tft.setTextPadding(xpos - 100); // xpos - icon width 358 | if (splitPoint) tft.drawString(weatherText.substring(0, splitPoint), xpos, 69); 359 | else tft.drawString(" ", xpos, 69); 360 | tft.drawString(weatherText.substring(splitPoint), xpos, 86); 361 | 362 | tft.setTextColor(TFT_YELLOW, TFT_BLACK); 363 | tft.setTextDatum(TR_DATUM); 364 | tft.setTextPadding(0); 365 | if (units == "si") tft.drawString("oC", 237, 95); 366 | else tft.drawString("oF", 237, 95); 367 | 368 | //Temperature large digits added in updateData() to save swapping font here 369 | 370 | tft.setTextColor(TFT_ORANGE, TFT_BLACK); 371 | weatherText = (uint16_t)current->windSpeed; 372 | 373 | // TODO: check if this works correctly 374 | if (units == "si") weatherText += " m/s"; 375 | else if (units == "us" || units == "uk2") weatherText += " mph"; 376 | else if (units == "ca") weatherText += " kph"; 377 | 378 | tft.setTextDatum(TC_DATUM); 379 | tft.setTextPadding(tft.textWidth("888 m/s")); // Max string length? 380 | tft.drawString(weatherText, 124, 136); 381 | 382 | if (units == "us") 383 | { 384 | weatherText = current->pressure; 385 | weatherText += " in"; 386 | } 387 | else 388 | { 389 | weatherText = (uint16_t)current->pressure; 390 | weatherText += " hPa"; 391 | } 392 | 393 | tft.setTextDatum(TR_DATUM); 394 | tft.setTextPadding(tft.textWidth(" 8888hPa")); // Max string length? 395 | tft.drawString(weatherText, 230, 136); 396 | 397 | int windAngle = (current->windBearing + 22.5) / 45; 398 | if (windAngle > 7) windAngle = 0; 399 | String wind[] = {"N", "NE", "E", "SE", "S", "SW", "W", "NW" }; 400 | ui.drawBmp("/wind/" + wind[windAngle] + ".bmp", 101, 86); 401 | 402 | drawSeparator(153); 403 | 404 | tft.setTextDatum(TL_DATUM); // Reset datum to normal 405 | tft.setTextPadding(0); // Reset padding width to none 406 | } 407 | 408 | /*************************************************************************************** 409 | ** Draw the 4 forecast columns 410 | ***************************************************************************************/ 411 | // draws the three forecast columns 412 | void drawForecast() { 413 | int8_t dayIndex = 1; 414 | //while ((daily->time[dayIndex] < (current->time - 12*60*60UL)) && (dayIndex < (MAX_DAYS - 4))) dayIndex++; 415 | drawForecastDetail( 8, 171, dayIndex++); 416 | drawForecastDetail( 66, 171, dayIndex++); // was 95 417 | drawForecastDetail(124, 171, dayIndex++); // was 180 418 | drawForecastDetail(182, 171, dayIndex ); // was 180 419 | drawSeparator(171 + 69); 420 | } 421 | 422 | /*************************************************************************************** 423 | ** Draw 1 forecast column at x, y 424 | ***************************************************************************************/ 425 | // helper for the forecast columns 426 | void drawForecastDetail(uint16_t x, uint16_t y, uint8_t dayIndex) { 427 | 428 | if (dayIndex >= MAX_DAYS) return; 429 | 430 | String day = shortDOW[weekday(TIMEZONE.toLocal(daily->time[dayIndex], &tz1_Code))]; 431 | day.toUpperCase(); 432 | 433 | tft.setTextDatum(BC_DATUM); 434 | 435 | tft.setTextColor(TFT_ORANGE, TFT_BLACK); 436 | tft.setTextPadding(tft.textWidth("WWW")); 437 | tft.drawString(day, x + 25, y); 438 | 439 | tft.setTextColor(TFT_WHITE, TFT_BLACK); 440 | tft.setTextPadding(tft.textWidth("-88 -88")); 441 | String highTemp = String(daily->temperatureHigh[dayIndex], 0); 442 | String lowTemp = String(daily->temperatureLow[dayIndex], 0); 443 | tft.drawString(highTemp + " " + lowTemp, x + 25, y + 17); 444 | 445 | String weatherIcon = getMeteoconIcon(daily->icon[dayIndex]); 446 | 447 | ui.drawBmp("/icon50/" + weatherIcon + ".bmp", x, y + 18); 448 | 449 | tft.setTextPadding(0); // Reset padding width to none 450 | } 451 | 452 | /*************************************************************************************** 453 | ** Draw Sun rise/set, Moon, cloud cover and humidity 454 | ***************************************************************************************/ 455 | void drawAstronomy() { 456 | 457 | tft.setTextDatum(BC_DATUM); 458 | tft.setTextColor(TFT_WHITE, TFT_BLACK); 459 | tft.setTextPadding(tft.textWidth(" Last qtr ")); 460 | 461 | int8_t dayIndex = 0; 462 | 463 | //The first daily summary might be yesterday, maybe search for time > now 464 | //while ((daily->time[dayIndex] < current->time) && (dayIndex < MAX_DAYS)) dayIndex++; 465 | 466 | uint8_t moonIndex = (uint8_t)(((float)daily->moonPhase[dayIndex] + 6.25) / 12.5); 467 | if (moonIndex > 7) moonIndex = 0; 468 | tft.drawString(moonPhase[moonIndex], 120, 319); 469 | 470 | uint8_t moonAgeImage = (24.0 * daily->moonPhase[dayIndex]) / 100; 471 | if (moonAgeImage > 23) moonAgeImage = 0; 472 | ui.drawBmp("/moon/moonphase_L" + String(moonAgeImage) + ".bmp", 120 - 30, 318 - 16 - 60); 473 | 474 | tft.setTextDatum(BC_DATUM); 475 | tft.setTextColor(TFT_ORANGE, TFT_BLACK); 476 | tft.setTextPadding(0); // Reset padding width to none 477 | tft.drawString(sunStr, 40, 270); 478 | 479 | tft.setTextDatum(BR_DATUM); 480 | tft.setTextColor(TFT_WHITE, TFT_BLACK); 481 | tft.setTextPadding(tft.textWidth(" 88:88 ")); 482 | 483 | String rising = strTime(daily->sunriseTime[dayIndex]) + " "; 484 | int dt = rightOffset(rising, ":"); // Draw relative to colon to them aligned 485 | tft.drawString(rising, 40 + dt, 290); 486 | 487 | String setting = strTime(daily->sunsetTime[dayIndex]) + " "; 488 | dt = rightOffset(setting, ":"); 489 | tft.drawString(setting, 40 + dt, 305); 490 | 491 | /* Dark Sky does not provide Moon rise and setting times 492 | There are other free API providers to try, maybe: 493 | http://api.usno.navy.mil/rstt/oneday?date=1/5/2005&loc=Los%20Angeles,%20CA 494 | */ 495 | 496 | tft.setTextDatum(BC_DATUM); 497 | tft.setTextColor(TFT_ORANGE, TFT_BLACK); 498 | tft.drawString(cloudStr, 195, 260); 499 | 500 | String cloudCover = ""; 501 | cloudCover += current->cloudCover; 502 | cloudCover += "%"; 503 | 504 | tft.setTextDatum(BR_DATUM); 505 | tft.setTextColor(TFT_WHITE, TFT_BLACK); 506 | tft.setTextPadding(tft.textWidth(" 100%")); 507 | tft.drawString(cloudCover, 210, 277); 508 | 509 | tft.setTextDatum(BC_DATUM); 510 | tft.setTextColor(TFT_ORANGE, TFT_BLACK); 511 | tft.drawString(humidityStr, 195, 300 - 2); 512 | 513 | String humidity = ""; 514 | humidity += current->humidity; 515 | humidity += "%"; 516 | 517 | tft.setTextDatum(BR_DATUM); 518 | tft.setTextColor(TFT_WHITE, TFT_BLACK); 519 | tft.setTextPadding(tft.textWidth("100%")); 520 | tft.drawString(humidity, 210, 315); 521 | 522 | tft.setTextPadding(0); // Reset padding width to none 523 | } 524 | 525 | /*************************************************************************************** 526 | ** Get the icon file name from the index number 527 | ***************************************************************************************/ 528 | const char* getMeteoconIcon(uint8_t iconIndex) 529 | { 530 | if (iconIndex > MAX_ICON_INDEX) iconIndex = 0; // 0 = unknown 531 | if( iconIndex == 4 && current->time > daily->sunsetTime[0] && current->time < daily->sunriseTime[1]) iconIndex = 5; 532 | else if (iconIndex == 7) iconIndex = 4; // Change partly-cloudy-night to clear-day 533 | return dsw.iconName(iconIndex); 534 | } 535 | 536 | /*************************************************************************************** 537 | ** Draw screen section separator line 538 | ***************************************************************************************/ 539 | // if you don't want separators, comment out the tft-line 540 | void drawSeparator(uint16_t y) { 541 | tft.drawFastHLine(10, y, 240 - 2 * 10, 0x4228); 542 | } 543 | 544 | /*************************************************************************************** 545 | ** Determine place to split a line line 546 | ***************************************************************************************/ 547 | // determine the "space" split point in a long string 548 | int splitIndex(String text) 549 | { 550 | int index = 0; 551 | while ( (text.indexOf(' ', index) >= 0) && ( index <= text.length() / 2 ) ) { 552 | index = text.indexOf(' ', index) + 1; 553 | } 554 | if (index) index--; 555 | return index; 556 | } 557 | 558 | /*************************************************************************************** 559 | ** Right side offset to a character 560 | ***************************************************************************************/ 561 | // Calculate coord delta from end of text String to start of sub String contained within that text 562 | // Can be used to vertically right align text so for example a colon ":" in the time value is always 563 | // plotted at same point on the screen irrespective of different proportional character widths, 564 | // could also be used to align decimal points for neat formatting 565 | int rightOffset(String text, String sub) 566 | { 567 | int index = text.indexOf(sub); 568 | return tft.textWidth(text.substring(index)); 569 | } 570 | 571 | /*************************************************************************************** 572 | ** Left side offset to a character 573 | ***************************************************************************************/ 574 | // Calculate coord delta from start of text String to start of sub String contained within that text 575 | // Can be used to vertically left align text so for example a colon ":" in the time value is always 576 | // plotted at same point on the screen irrespective of different proportional character widths, 577 | // could also be used to align decimal points for neat formatting 578 | int leftOffset(String text, String sub) 579 | { 580 | int index = text.indexOf(sub); 581 | return tft.textWidth(text.substring(0, index)); 582 | } 583 | 584 | /*************************************************************************************** 585 | ** Draw circle segment 586 | ***************************************************************************************/ 587 | // Draw a segment of a circle, centred on x,y with defined start_angle and subtended sub_angle 588 | // Angles are defined in a clockwise direction with 0 at top 589 | // Segment has radius r and it is plotted in defined colour 590 | // Can be used for pie charts etc, in this sketch it is used for wind direction 591 | #define DEG2RAD 0.0174532925 // Degrees to Radians conversion factor 592 | #define INC 2 // Minimum segment subtended angle and plotting angle increment (in degrees) 593 | void fillSegment(int x, int y, int start_angle, int sub_angle, int r, unsigned int colour) 594 | { 595 | // Calculate first pair of coordinates for segment start 596 | float sx = cos((start_angle - 90) * DEG2RAD); 597 | float sy = sin((start_angle - 90) * DEG2RAD); 598 | uint16_t x1 = sx * r + x; 599 | uint16_t y1 = sy * r + y; 600 | 601 | // Draw colour blocks every INC degrees 602 | for (int i = start_angle; i < start_angle + sub_angle; i += INC) { 603 | 604 | // Calculate pair of coordinates for segment end 605 | int x2 = cos((i + 1 - 90) * DEG2RAD) * r + x; 606 | int y2 = sin((i + 1 - 90) * DEG2RAD) * r + y; 607 | 608 | tft.fillTriangle(x1, y1, x2, y2, x, y, colour); 609 | 610 | // Copy segment end to sgement start for next segment 611 | x1 = x2; 612 | y1 = y2; 613 | } 614 | } 615 | 616 | /*************************************************************************************** 617 | ** Print the weather info to the Serial Monitor 618 | ***************************************************************************************/ 619 | void printWeather(void) 620 | { 621 | #ifdef SERIAL_MESSAGES 622 | Serial.println("Weather from Dark Sky\n"); 623 | 624 | Serial.println("############### Current weather ###############\n"); 625 | Serial.print("time : "); Serial.println(strDate(current->time)); 626 | Serial.print("summary : "); Serial.println(current->summary); 627 | Serial.print("icon : "); Serial.println(current->icon); 628 | Serial.print("temperature : "); Serial.println(current->temperature); 629 | Serial.print("humidity : "); Serial.println(current->humidity); 630 | Serial.print("pressure : "); Serial.println(current->pressure); 631 | Serial.print("wind speed : "); Serial.println(current->windSpeed); 632 | Serial.print("wind dirn : "); Serial.println(current->windBearing); 633 | Serial.print("cloudCover : "); Serial.println(current->cloudCover); 634 | 635 | Serial.println(); 636 | 637 | Serial.println("############### Daily weather ###############\n"); 638 | Serial.print(" Overall summary : "); Serial.println(daily->overallSummary); 639 | Serial.println(); 640 | 641 | for (int i = 0; i < 5; i++) 642 | { 643 | Serial.print("Day summary "); Serial.print(i); Serial.print(" : "); Serial.println(daily->summary[i]); 644 | Serial.print("time : "); Serial.println(strDate(daily->time[i])); 645 | Serial.print("icon : "); Serial.println(daily->icon[i]); 646 | Serial.print("sunriseTime : "); Serial.println(strDate(daily->sunriseTime[i])); 647 | Serial.print("sunsetTime : "); Serial.println(strDate(daily->sunsetTime[i])); 648 | Serial.print("Moon phase : "); Serial.println(daily->moonPhase[i]); 649 | Serial.print("temperatureHigh : "); Serial.println(daily->temperatureHigh[i]); 650 | Serial.print("temperatureLow : "); Serial.println(daily->temperatureLow[i]); 651 | Serial.println(); 652 | } 653 | #endif 654 | } 655 | /*************************************************************************************** 656 | ** Convert unix time to a "local time" time string "12:34" 657 | ***************************************************************************************/ 658 | String strTime(time_t unixTime) 659 | { 660 | time_t local_time = TIMEZONE.toLocal(unixTime, &tz1_Code); 661 | 662 | String localTime = ""; 663 | 664 | if (hour(local_time) < 10) localTime += "0"; 665 | localTime += hour(local_time); 666 | localTime += ":"; 667 | if (minute(local_time) < 10) localTime += "0"; 668 | localTime += minute(local_time); 669 | 670 | return localTime; 671 | } 672 | 673 | /*************************************************************************************** 674 | ** Convert unix time to a local date + time string "Oct 16 17:18", ends with newline 675 | ***************************************************************************************/ 676 | String strDate(time_t unixTime) 677 | { 678 | time_t local_time = TIMEZONE.toLocal(unixTime, &tz1_Code); 679 | 680 | String localDate = ""; 681 | 682 | localDate += monthShortStr(month(local_time)); 683 | localDate += " "; 684 | localDate += day(local_time); 685 | localDate += " " + strTime(unixTime); 686 | 687 | return localDate; 688 | } 689 | 690 | /**The MIT License (MIT) 691 | Copyright (c) 2015 by Daniel Eichhorn 692 | Permission is hereby granted, free of charge, to any person obtaining a copy 693 | of this software and associated documentation files (the "Software"), to deal 694 | in the Software without restriction, including without limitation the rights 695 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 696 | copies of the Software, and to permit persons to whom the Software is 697 | furnished to do so, subject to the following conditions: 698 | The above copyright notice and this permission notice shall be included in all 699 | copies or substantial portions of the Software. 700 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 701 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 702 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 703 | AUTHORS OR COPYBR_DATUM HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 704 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 705 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 706 | SOFTWARE. 707 | See more at http://blog.squix.ch 708 | */ 709 | 710 | // Changes made by Bodmer: 711 | 712 | // Minor changes to text placement and auto-blanking out old text with background colour padding 713 | // Moon phase text added 714 | // Forecast text lines are automatically split onto two lines at a central space (some are long!) 715 | // Time is printed with colons aligned to tidy display 716 | // Min and max forecast temperatures spaced out 717 | // New smart splash startup screen and updated progress messages 718 | // Display does not need to be blanked between updates 719 | // Icons nudged about slightly to add wind direction + speed 720 | // Barometric pressure added 721 | 722 | // Adapted to use the DarkSkyWeather library: https://github.com/Bodmer/DarkSkyWeather 723 | // Moon rise/set (not provided by Dark Sky) replace with and cloud cover humidity 724 | // Created and added new 100x100 and 50x50 pixel weather icons, these are in the 725 | // sketch data folder, press Ctrl+K to view 726 | // Add moon icons, eliminate all downloads of icons (may lose server!) 727 | // Addapted to use anti-aliased fonts, tweaked coords 728 | // Added forecast for 4th day 729 | // Added cloud cover and humidity in lieu of Moon rise/set 730 | // Adapted to be compatible with ESP32 731 | -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/fonts/NotoSansBold15.vlw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/fonts/NotoSansBold15.vlw -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/fonts/NotoSansBold36.vlw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/fonts/NotoSansBold36.vlw -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon/clear-day.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon/clear-day.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon/clear-night.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon/clear-night.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon/cloudy.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon/cloudy.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon/drizzle.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon/drizzle.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon/fog.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon/fog.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon/hail.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon/hail.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon/lightRain.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon/lightRain.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon/partly-cloudy-day.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon/partly-cloudy-day.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon/partly-cloudy-night.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon/partly-cloudy-night.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon/rain.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon/rain.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon/sleet.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon/sleet.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon/snow.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon/snow.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon/thunderstorm.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon/thunderstorm.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon/unknown.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon/unknown.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon/wind.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon/wind.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon50/clear-day.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon50/clear-day.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon50/clear-night.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon50/clear-night.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon50/cloudy.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon50/cloudy.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon50/drizzle.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon50/drizzle.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon50/fog.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon50/fog.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon50/hail.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon50/hail.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon50/lightRain.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon50/lightRain.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon50/partly-cloudy-day.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon50/partly-cloudy-day.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon50/partly-cloudy-night.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon50/partly-cloudy-night.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon50/rain.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon50/rain.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon50/sleet.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon50/sleet.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon50/snow.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon50/snow.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon50/thunderstorm.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon50/thunderstorm.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon50/unknown.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon50/unknown.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/icon50/wind.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/icon50/wind.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/moon/moonphase_L0.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/moon/moonphase_L0.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/moon/moonphase_L1.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/moon/moonphase_L1.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/moon/moonphase_L10.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/moon/moonphase_L10.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/moon/moonphase_L11.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/moon/moonphase_L11.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/moon/moonphase_L12.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/moon/moonphase_L12.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/moon/moonphase_L13.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/moon/moonphase_L13.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/moon/moonphase_L14.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/moon/moonphase_L14.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/moon/moonphase_L15.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/moon/moonphase_L15.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/moon/moonphase_L16.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/moon/moonphase_L16.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/moon/moonphase_L17.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/moon/moonphase_L17.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/moon/moonphase_L18.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/moon/moonphase_L18.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/moon/moonphase_L19.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/moon/moonphase_L19.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/moon/moonphase_L2.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/moon/moonphase_L2.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/moon/moonphase_L20.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/moon/moonphase_L20.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/moon/moonphase_L21.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/moon/moonphase_L21.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/moon/moonphase_L22.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/moon/moonphase_L22.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/moon/moonphase_L23.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/moon/moonphase_L23.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/moon/moonphase_L3.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/moon/moonphase_L3.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/moon/moonphase_L4.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/moon/moonphase_L4.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/moon/moonphase_L5.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/moon/moonphase_L5.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/moon/moonphase_L6.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/moon/moonphase_L6.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/moon/moonphase_L7.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/moon/moonphase_L7.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/moon/moonphase_L8.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/moon/moonphase_L8.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/moon/moonphase_L9.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/moon/moonphase_L9.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/splash/DarkSky.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/splash/DarkSky.jpg -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/wind/E.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/wind/E.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/wind/N.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/wind/N.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/wind/NE.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/wind/NE.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/wind/NW.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/wind/NW.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/wind/S.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/wind/S.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/wind/SE.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/wind/SE.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/wind/SW.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/wind/SW.bmp -------------------------------------------------------------------------------- /examples/TFT_eSPI_weather/data/wind/W.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bodmer/DarkSkyWeather/aef1f206f52567f12540d780a6c9642fca52c4c4/examples/TFT_eSPI_weather/data/wind/W.bmp -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | DarkSkyWeather KEYWORD1 2 | DS_Weather KEYWORD1 3 | 4 | getForecast KEYWORD2 5 | parseRequest KEYWORD2 6 | 7 | DSW_current KEYWORD2 8 | DSW_minutely KEYWORD2 9 | DSW_hourly KEYWORD2 10 | DSW_daily KEYWORD2 11 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DarkSkyWeather", 3 | "version": "0.1.2", 4 | "keywords": "weather, TFT_eSPI, tft, display, ESP8266, NodeMCU, ESP32, M5Stack, ILI9341, ILI9486, ILI9488", 5 | "description": "Dark Sky Weather API client for ESP8266 and ESP32", 6 | "repository": 7 | { 8 | "type": "git", 9 | "url": "https://github.com/Bodmer/DarkSkyWeather" 10 | }, 11 | "authors": 12 | [ 13 | { 14 | "name": "Bodmer", 15 | "email": "bodmer@anola.net", 16 | "maintainer": true 17 | } 18 | ], 19 | "frameworks": "arduino", 20 | "platforms": "espressif8266, espressif32" 21 | } 22 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=DarkSkyWeather 2 | version=0.1.2 3 | author=Bodmer 4 | maintainer=Bodmer 5 | sentence=Dark Sky Weather client 6 | paragraph=A weather retrieval library for ESP8266 and ESP32 7 | category=Display 8 | url=https://github.com/Bodmer/DarkSkyWeather 9 | architectures=esp8266,esp32 10 | includes=DarkSkyWeather.h 11 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvStartvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv 2 | Software License Agreement (FreeBSD License) 3 | 4 | Dark Sky weather API client library for ESP8266 5 | Copyright (c) 2018 Bodmer (https://github.com/Bodmer) 6 | 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without 10 | modification, are permitted provided that the following conditions are met: 11 | 12 | 1. Redistributions of source code must retain the above copyright notice, this 13 | list of conditions and the following disclaimer. 14 | 2. Redistributions in binary form must reproduce the above copyright notice, 15 | this list of conditions and the following disclaimer in the documentation 16 | and/or other materials provided with the distribution. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | The views and conclusions contained in the software and documentation are those 30 | of the authors and should not be interpreted as representing official policies, 31 | either expressed or implied, of the FreeBSD Project. 32 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^End^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 33 | --------------------------------------------------------------------------------