Showing preview only (314K chars total). Download the full file or copy to clipboard to get everything.
Repository: Bodmer/OpenWeather
Branch: main
Commit: 4ad58e7f0c52
Files: 45
Total size: 298.0 KB
Directory structure:
gitextract_ry32umis/
├── .gitattributes
├── Data_Point_Set.h
├── OpenWeather.cpp
├── OpenWeather.h
├── README.md
├── User_Setup.h
├── examples/
│ ├── Onecall API (subscription required)/
│ │ ├── My_OpenWeather_Test/
│ │ │ ├── My_OpenWeather_Test.ino
│ │ │ └── Notes.ino
│ │ ├── TFT_eSPI_OpenWeather/
│ │ │ ├── All_Settings.h
│ │ │ ├── GfxUi.cpp
│ │ │ ├── GfxUi.h
│ │ │ ├── MoonPhase.ino
│ │ │ ├── NTP_Time.h
│ │ │ ├── SPIFFS_Support.h
│ │ │ ├── ScreenGrabClient.ino
│ │ │ ├── ScreenGrabServer.ino
│ │ │ ├── TFT_eSPI_OpenWeather.ino
│ │ │ └── data/
│ │ │ └── fonts/
│ │ │ ├── NotoSansBold15.vlw
│ │ │ └── NotoSansBold36.vlw
│ │ └── TFT_eSPI_OpenWeather_LittleFS/
│ │ ├── All_Settings.h
│ │ ├── GfxUi.cpp
│ │ ├── GfxUi.h
│ │ ├── MoonPhase.ino
│ │ ├── NTP_Time.h
│ │ ├── ScreenGrabClient.ino
│ │ ├── ScreenGrabServer.ino
│ │ ├── TFT_eSPI_OpenWeather_LittleFS.ino
│ │ └── data/
│ │ └── fonts/
│ │ ├── NSBold15.vlw
│ │ └── NSBold36.vlw
│ ├── OpenWeather_Forecast_Test/
│ │ ├── Notes.ino
│ │ └── OpenWeather_Forecast_Test.ino
│ └── TFT_eSPI_OpenWeather_LittleFS/
│ ├── All_Settings.h
│ ├── GfxUi.cpp
│ ├── GfxUi.h
│ ├── MoonPhase.ino
│ ├── NTP_Time.h
│ ├── ScreenGrabClient.ino
│ ├── ScreenGrabServer.ino
│ ├── TFT_eSPI_OpenWeather_LittleFS.ino
│ └── data/
│ └── fonts/
│ ├── NSBold15.vlw
│ └── NSBold36.vlw
├── keywords.txt
├── library.json
├── library.properties
└── license.txt
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
# Auto detect text files and perform LF normalization
* text=auto
================================================
FILE: Data_Point_Set.h
================================================
// The structures below are the repository for the data values extracted from the
// JSON message. The structures are populated with the extracted data by the "value()"
// member function in the main OpenWeather.cpp file.
// Some structs contain arrays so watch out for memory consumption. You can
// request a subset of the full weather report but this library grabs all values with
// one GET request to avoid exceeding the 1000 free request count per day (count reset
// at 00:00 UTC). 1000 per day means ~40 per hour. As the weather forecast changes slowly
// the example requests the forecast every 15 minutes, so adapting to reduce memory
// by requesting current, daily, hourly etc forecasts individually can be done.
// The content is zero or "" when first created.
/***************************************************************************************
** Description: Structure for current weather using onecall API
***************************************************************************************/
typedef struct OW_current {
// current
uint32_t dt = 0;
uint32_t sunrise = 0;
uint32_t sunset = 0;
float temp = 0;
float feels_like = 0;
float pressure = 0;
uint8_t humidity = 0;
float dew_point = 0;
uint8_t clouds = 0;
float uvi = 0;
uint32_t visibility = 0;
float wind_speed = 0;
float wind_gust = 0;
uint16_t wind_deg = 0;
float rain = 0;
float snow = 0;
// current.weather
uint16_t id = 0;
String main;
String description;
String icon;
} OW_current;
/***************************************************************************************
** Description: Structure for hourly weather using onecall API
***************************************************************************************/
typedef struct OW_hourly {
// hourly
uint32_t dt[MAX_HOURS] = { 0 };
float temp[MAX_HOURS] = { 0 };
float feels_like[MAX_HOURS] = { 0 };
float pressure[MAX_HOURS] = { 0 };
uint8_t humidity[MAX_HOURS] = { 0 };
float dew_point[MAX_HOURS] = { 0 };
uint8_t clouds[MAX_HOURS] = { 0 };
float wind_speed[MAX_HOURS] = { 0 };
float wind_gust[MAX_HOURS] = { 0 };
uint16_t wind_deg[MAX_HOURS] = { 0 };
float rain[MAX_HOURS] = { 0 };
float snow[MAX_HOURS] = { 0 };
// hourly.weather
uint16_t id[MAX_HOURS] = { 0 };
String main[MAX_HOURS];
String description[MAX_HOURS];
String icon[MAX_HOURS];
float pop[MAX_HOURS];
float rain1h[MAX_HOURS];
} OW_hourly;
/***************************************************************************************
** Description: Structure for daily weather using onecall API
***************************************************************************************/
typedef struct OW_daily {
// daily
uint32_t dt[MAX_DAYS] = { 0 }; // dt
uint32_t sunrise[MAX_DAYS] = { 0 };
uint32_t sunset[MAX_DAYS] = { 0 };
uint32_t moonrise[MAX_DAYS] = { 0 };
uint32_t moonset[MAX_DAYS] = { 0 };
// daily.temp
float temp_morn[MAX_DAYS] = { 0 };
float temp_day[MAX_DAYS] = { 0 };
float temp_eve[MAX_DAYS] = { 0 };
float temp_night[MAX_DAYS] = { 0 };
float temp_min[MAX_DAYS] = { 0 };
float temp_max[MAX_DAYS] = { 0 };
// daily.feels_like
float feels_like_morn[MAX_DAYS] = { 0 };
float feels_like_day[MAX_DAYS] = { 0 };
float feels_like_eve[MAX_DAYS] = { 0 };
float feels_like_night[MAX_DAYS] = { 0 };
// daily
float pressure[MAX_DAYS] = { 0 };
uint8_t humidity[MAX_DAYS] = { 0 };
float dew_point[MAX_DAYS] = { 0 };
float wind_speed[MAX_DAYS] = { 0 };
float wind_gust[MAX_DAYS] = { 0 };
uint16_t wind_deg[MAX_DAYS] = { 0 };
uint8_t clouds[MAX_DAYS] = { 0 };
float uvi[MAX_DAYS] = { 0 };
uint32_t visibility[MAX_DAYS] = { 0 };
float rain[MAX_DAYS] = { 0 };
float snow[MAX_DAYS] = { 0 };
// hourly.weather
uint16_t id[MAX_DAYS] = { 0 };
String main[MAX_DAYS];
String description[MAX_DAYS];
String icon[MAX_DAYS];
float pop[MAX_DAYS];
} OW_daily;
/***************************************************************************************
** Description: Structure for new "forecast" API
***************************************************************************************/
typedef struct OW_forecast {
// list.Nth 3hr slot
uint32_t dt[MAX_3HRS] = { 0 }; // dt
// main
float temp[MAX_3HRS] = { 0 };
float feels_like[MAX_3HRS] = { 0 };
float temp_min[MAX_3HRS] = { 0 };
float temp_max[MAX_3HRS] = { 0 };
float pressure[MAX_3HRS] = { 0 };
float sea_level[MAX_3HRS] = { 0 };
float grnd_level[MAX_3HRS] = { 0 };
uint8_t humidity[MAX_3HRS] = { 0 };
uint16_t id[MAX_3HRS] = { 0 };
String main[MAX_3HRS];
String description[MAX_3HRS];
String icon[MAX_3HRS];
uint8_t clouds_all[MAX_3HRS] = { 0 };
float wind_speed[MAX_3HRS] = { 0 };
uint16_t wind_deg[MAX_3HRS] = { 0 };
float wind_gust[MAX_3HRS] = { 0 };
uint32_t visibility[MAX_3HRS] = { 0 };
float pop[MAX_3HRS] = { 0 };
String dt_txt[MAX_3HRS];
// city
String city_name = "";
int32_t timezone = 0;
uint32_t sunrise = 0;
uint32_t sunset = 0;
} OW_forecast;
// Structures for minimal set of data points for TFT_eSPI examples to reduce RAM needs
/*
typedef struct OW_current {
//float lat = 0;
//float lon = 0;
//String timezone;
// current
uint32_t dt = 0;
uint32_t sunrise = 0;
uint32_t sunset = 0;
float temp = 0;
//float feels_like = 0;
float pressure = 0;
uint8_t humidity = 0;
//float dew_point = 0;
uint8_t clouds = 0;
//uint8_t uvi = 0;
//uint32_t visibility = 0;
float wind_speed = 0;
//float wind_gust = 0;
uint16_t wind_deg = 0;
//float rain = 0;
//float snow = 0;
// current.weather
uint16_t id = 0;
String main;
//String description;
//String icon;
} OW_current;
typedef struct OW_hourly {
} OW_hourly;
typedef struct OW_daily {
// daily
uint32_t dt[MAX_DAYS] = { 0 }; // dt
//uint32_t sunrise = 0;
//uint32_t sunset = 0;
// daily.temp
//float temp_morn[MAX_DAYS] = { 0 };
//float temp_day[MAX_DAYS] = { 0 };
//float temp_eve[MAX_DAYS] = { 0 };
//float temp_night[MAX_DAYS] = { 0 };
float temp_min[MAX_DAYS] = { 0 };
float temp_max[MAX_DAYS] = { 0 };
// daily.feels_like
//float feels_like_morn[MAX_DAYS] = { 0 };
//float feels_like_day[MAX_DAYS] = { 0 };
//float feels_like_eve[MAX_DAYS] = { 0 };
//float feels_like_night[MAX_DAYS] = { 0 };
// daily
//float pressure[MAX_DAYS] = { 0 };
//uint8_t humidity[MAX_DAYS] = { 0 };
//float dew_point[MAX_DAYS] = { 0 };
//float wind_speed[MAX_DAYS] = { 0 };
//float wind_gust[MAX_DAYS] = { 0 };
//uint16_t wind_deg[MAX_DAYS] = { 0 };
//uint8_t clouds[MAX_DAYS] = { 0 };
//uint8_t uvi[MAX_DAYS] = { 0 };
//uint32_t visibility[MAX_DAYS] = { 0 };
//float rain[MAX_DAYS] = { 0 };
//float snow[MAX_DAYS] = { 0 };
// hourly.weather
uint16_t id[MAX_DAYS] = { 0 };
//String main[MAX_DAYS];
//String description[MAX_DAYS];
//String icon[MAX_DAYS];
} OW_daily;
*/
================================================
FILE: OpenWeather.cpp
================================================
// Client library for the OpenWeather data-point server
// https://openweathermap.org/
// Created by Bodmer 09/04/2020
// Updated by Bodmer 08/01/2021
// Updated by Bodmer 15/02/2023 to support free forecast API
// See license.txt in root folder of library
// Insecure mode added by ADAMSIN12
#if defined(ARDUINO_ARCH_MBED) || defined(ARDUINO_ARCH_RP2040)
#if defined(ARDUINO_RASPBERRY_PI_PICO_W)
#include <WiFi.h>
#else
#include <WiFiNINA.h>
#endif
#else
#ifdef ESP8266
#include <ESP8266WiFi.h>
#else
#include <WiFi.h>
#endif
#include <WiFiClientSecure.h>
#endif
#include "OpenWeather.h"
/***************************************************************************************
** Function name: getForecast (using onecall API)
** Description: Setup the weather forecast request
***************************************************************************************/
// The structures etc are created by the sketch and passed to this function.
// Pass a nullptr for current, hourly or daily pointers to exclude in response.
// ESP8266: Setting secure to false will invoke an insecure connection with AXTLS
// for the connection, when set true BearSSL will be used.
// ESP32: Secure parameter has no affect.
bool OW_Weather::getForecast(OW_current *current, OW_hourly *hourly, OW_daily *daily,
String api_key, String latitude, String longitude,
String units, String language, bool secure) {
data_set = "";
hourly_index = 0;
daily_index = 0;
Secure = secure;
oneCall = true;
// Local copies of structure pointers, the structures are filled during parsing
this->current = current;
this->hourly = hourly;
this->daily = daily;
// Exclude some info by passing fn a NULL pointer to reduce memory needed
String exclude = ",alerts";
if (!current) exclude += ",current";
if (!hourly) exclude += ",hourly";
if (!daily) exclude += ",daily";
// One call API now subscription
String url = "https://api.openweathermap.org/data/2.5/onecall?lat=" + latitude + "&lon=" + longitude + "&exclude=minutely" + exclude + "&units=" + units + "&lang=" + language + "&appid=" + api_key;
// Send GET request and feed the parser
bool result = parseRequest(url);
// Null out pointers to prevent crashes
this->current = nullptr;
this->hourly = nullptr;
this->daily = nullptr;
return result;
}
/***************************************************************************************
** Function name: getForecast (using forecast API)
** Description: Setup the weather forecast request
***************************************************************************************/
bool OW_Weather::getForecast(OW_forecast *forecast, String api_key,
String latitude, String longitude,
String units, String language, bool secure)
{
data_set = "";
forecast_index = 0;
Secure = secure;
oneCall = false;
// Local copies of structure pointers, the structures are filled during parsing
this->forecast = forecast;
// 5 day forecast every 3 hours from request time
String url = "https://api.openweathermap.org/data/2.5/forecast?lat=" + latitude + "&lon=" + longitude + "&units=" + units + "&lang=" + language + "&appid=" + api_key;
// Send GET request and feed the parser
bool result = parseRequest(url);
// Null out pointers to prevent crashes
this->forecast = nullptr;
return result;
}
/***************************************************************************************
** Function name: partialDataSet
** Description: Set requested data set to partial (true) or full (false)
***************************************************************************************/
void OW_Weather::partialDataSet(bool partialSet) {
this->partialSet = partialSet;
}
#ifdef ESP32 // Decide if ESP32 or ESP8266 parseRequest available
/***************************************************************************************
** Function name: parseRequest (for ESP32)
** Description: Fetches the JSON message and feeds to the parser
***************************************************************************************/
bool OW_Weather::parseRequest(String url) {
uint32_t dt = millis();
OW_STATUS_PRINTF("\n\nThe connection to server is secure (https). Certificate not checked.\n");
WiFiClientSecure client;
client.setInsecure(); // Certificate not checked
const char* host = "api.openweathermap.org";
port = 443;
if (!client.connect(host, port))
{
OW_STATUS_PRINTF("Connection failed.\n");
return false;
}
JSON_Decoder parser;
parser.setListener(this);
uint32_t timeout = millis();
char c = 0;
parseOK = false;
#ifdef SHOW_JSON
int ccount = 0;
#endif
// Send GET request
Serial.println();
OW_STATUS_PRINT("Sending GET request to "); OW_STATUS_PRINT(host); OW_STATUS_PRINT(" port "); OW_STATUS_PRINT(port); OW_STATUS_PRINTF("\n");
client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n");
// Pull out any header, X-Forecast-API-Calls: reports current daily API call count
while (client.connected())
{
String line = client.readStringUntil('\n');
if (line == "\r") {
OW_STATUS_PRINTF("Header end found\n");
break;
}
#ifdef SHOW_HEADER
Serial.println(line);
#endif
if ((millis() - timeout) > 5000UL)
{
OW_STATUS_PRINTF ("HTTP header timeout\n");
client.stop();
return false;
}
}
OW_STATUS_PRINTF("\nParsing JSON\n");
// Parse the JSON data, available() includes yields
while ( client.available() > 0 || client.connected())
{
while(client.available() > 0)
{
c = client.read();
parser.parse(c);
#ifdef SHOW_JSON
if (c == '{' || c == '[' || c == '}' || c == ']') Serial.println();
Serial.println(c); if (ccount++ > 100 && c == ',') {ccount = 0; Serial.println();}
#endif
}
if ((millis() - timeout) > 8000UL)
{
OW_STATUS_PRINTF("Client timeout during JSON parse\n");
parser.reset();
client.stop();
return false;
}
yield();
}
OW_STATUS_PRINTF("\nDone in "); OW_STATUS_PRINT(millis()-dt); OW_STATUS_PRINTF(" ms\n");
Serial.println();
parser.reset();
client.stop();
// A message has been parsed, but the data-point correctness is unknown
return parseOK;
}
#else // ESP8266 or Arduino RP2040 Nano Connect version
/***************************************************************************************
** Function name: parseRequest (for ESP8266)
** Description: Fetches the JSON message and feeds to the parser
***************************************************************************************/
bool OW_Weather::parseRequest(String url) {
if (Secure) return parseRequestSecure(&url);
else return parseRequestInsecure(&url);
}
bool OW_Weather::parseRequestSecure(String* url) {
uint32_t dt = millis();
const char* host = "api.openweathermap.org";
#if (defined(ARDUINO_ARCH_MBED) || defined(ARDUINO_ARCH_RP2040)) && !defined(ARDUINO_RASPBERRY_PI_PICO_W)
WiFiSSLClient client;
#else
// Must use namespace:: to select BearSSL
BearSSL::WiFiClientSecure client;
client.setInsecure(); // Certificate not checked
#endif
port = 443;
if (!client.connect(host, port))
{
OW_STATUS_PRINTF("Connection failed.\n");
return false;
}
JSON_Decoder parser;
parser.setListener(this);
uint32_t timeout = millis();
char c = 0;
parseOK = false;
#ifdef SHOW_JSON
int ccount = 0;
#endif
#ifdef ESP8266
OW_STATUS_PRINTF("\nThe connection to server is using BearSSL in insecure mode (certificates not checked).\n");
#endif
// Send GET request
Serial.println();
OW_STATUS_PRINTF("Sending GET request to api.openweathermap.org...\n");
Serial.println();
client.print(String("GET ") + *url + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n");
Serial.println();
// Pull out any header, X-Forecast-API-Calls: reports current daily API call count
while (client.available() || client.connected())
{
String line = client.readStringUntil('\n');
if (line == "\r") {
OW_STATUS_PRINTF("Header end found\n");
break;
}
OW_STATUS_PRINT(line); OW_STATUS_PRINTF("\n");
if ((millis() - timeout) > 5000UL)
{
OW_STATUS_PRINTF ("HTTP header timeout\n");
client.stop();
return false;
}
}
// Parse the JSON data, available() includes yields
while (client.available() || client.connected())
{
while (client.available())
{
c = client.read();
parser.parse(c);
#ifdef SHOW_JSON
if (c == '{' || c == '[' || c == '}' || c == ']') Serial.println();
Serial.print(c); if (ccount++ > 100 && c == ',') {ccount = 0; Serial.println();}
#endif
}
if ((millis() - timeout) > 8000UL)
{
OW_STATUS_PRINTF ("JSON client timeout\n");
parser.reset();
client.stop();
return false;
}
}
Serial.println();
OW_STATUS_PRINTF("\nDone in "); OW_STATUS_PRINT(millis()-dt); OW_STATUS_PRINTF(" ms\n");
parser.reset();
client.stop();
// A message has been parsed without error but the data-point correctness is unknown
return parseOK;
}
bool OW_Weather::parseRequestInsecure(String* url) {
uint32_t dt = millis();
const char* host = "api.openweathermap.org";
// AXTLS used (insecure)
WiFiClient client;
port = 80;
if (!client.connect(host, port))
{
OW_STATUS_PRINTF("Connection failed.\n");
return false;
}
JSON_Decoder parser;
parser.setListener(this);
uint32_t timeout = millis();
char c = 0;
parseOK = false;
#ifdef SHOW_JSON
int ccount = 0;
#endif
OW_STATUS_PRINTF("\nThe connection to server is INSECURE (using AXTLS).\n");
// Send GET request
OW_STATUS_PRINTF("Sending GET request to api.openweathermap.org...\n");
client.print(String("GET ") + *url + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n");
// Pull out any header, X-Forecast-API-Calls: reports current daily API call count
while (client.available() || client.connected())
{
String line = client.readStringUntil('\n');
if (line == "\r") {
OW_STATUS_PRINTF("Header end found\n");
break;
}
OW_STATUS_PRINT(line); OW_STATUS_PRINTF("\n");
if ((millis() - timeout) > 5000UL)
{
OW_STATUS_PRINTF("HTTP header timeout\n");
client.stop();
return false;
}
}
// Parse the JSON data, available() includes yields
while (client.available() || client.connected())
{
while (client.available())
{
c = client.read();
parser.parse(c);
#ifdef SHOW_JSON
if (c == '{' || c == '[' || c == '}' || c == ']') Serial.println();
Serial.print(c); if (ccount++ > 100 && c == ',') {ccount = 0; Serial.println();}
#endif
}
if ((millis() - timeout) > 8000UL)
{
OW_STATUS_PRINTF("JSON client timeout\n");
parser.reset();
client.stop();
return false;
}
}
OW_STATUS_PRINTF("\nDone in "); OW_STATUS_PRINT(millis()-dt); OW_STATUS_PRINTF(" ms\n");
parser.reset();
client.stop();
// A message has been parsed without error but the data-point correctness is unknown
return parseOK;
}
#endif // ESP32 or ESP8266 parseRequest
/***************************************************************************************
** Function name: key etc
** Description: These functions are called while parsing the JSON message
***************************************************************************************/
void OW_Weather::key(const char *key) {
currentKey = key;
#ifdef SHOW_CALLBACK
Serial.println("\n>>> Key >>>" + (String)key);
#endif
}
void OW_Weather::startDocument() {
currentParent = currentKey = currentSet = "";
objectLevel = 0;
valuePath = "";
arrayIndex = 0;
arrayLevel = 0;
parseOK = true;
#ifdef SHOW_CALLBACK
Serial.print("\n>>> Start document >>>");
#endif
}
void OW_Weather::endDocument() {
currentParent = currentKey = "";
objectLevel = 0;
valuePath = "";
arrayIndex = 0;
arrayLevel = 0;
#ifdef SHOW_CALLBACK
Serial.print("\n<<< End document <<<");
#endif
}
void OW_Weather::startObject() {
if (arrayIndex == 0 && objectLevel == 1) currentParent = currentKey;
currentSet = currentKey;
objectLevel++;
#ifdef SHOW_CALLBACK
Serial.print("\n>>> Start object level:" + (String) objectLevel + " array level:" + (String) arrayLevel + " array index:" + (String) arrayIndex +" >>>");
#endif
}
void OW_Weather::endObject() {
if (arrayLevel == 0) currentParent = "";
if (arrayLevel == 1 && objectLevel == 2) arrayIndex++;
objectLevel--;
#ifdef SHOW_CALLBACK
Serial.print("\n<<< End object <<<");
#endif
}
void OW_Weather::startArray() {
arrayLevel++;
valuePath = currentParent + "/" + currentKey; // aka = current Object, e.g. "daily:data"
#ifdef SHOW_CALLBACK
Serial.print("\n>>> Start array " + valuePath + "/" + (String) arrayLevel + "/" + (String) arrayIndex +" >>>");
#endif
}
void OW_Weather::endArray() {
if (arrayLevel > 0) arrayLevel--;
if (arrayLevel == 0) arrayIndex = 0;
valuePath = "";
#ifdef SHOW_CALLBACK
Serial.print("\n<<< End array <<<");
#endif
}
void OW_Weather::whitespace(char c) {
c = c; // Avoid warning
}
void OW_Weather::error( const char *message ) {
Serial.print("\nParse error message: ");
Serial.print(message);
parseOK = false;
}
/***************************************************************************************
** Function name: value (full or partial data set)
** Description: Stores the parsed data in the structures for sketch access
***************************************************************************************/
void OW_Weather::value(const char *val)
{
if (oneCall) {
if (!partialSet) fullDataSet(val);
else partialDataSet(val);
}
else {
forecastDataSet(val);
}
}
/***************************************************************************************
** Function name: fullDataSet
** Description: Collects full data set
***************************************************************************************/
void OW_Weather::fullDataSet(const char *val) {
String value = val;
// Start of JSON
if (currentParent == "") {
if (currentKey == "lat") lat = value.toFloat();
if (currentKey == "lon") lon = value.toFloat();
if (currentKey == "timezone_offset") timezone = value;
}
// Current forecast - no array index - short path
if (currentParent == "current") {
data_set = "current";
if (currentKey == "dt") current->dt = (uint32_t)value.toInt();
else
if (currentKey == "sunrise") current->sunrise = (uint32_t)value.toInt();
else
if (currentKey == "sunset") current->sunset = (uint32_t)value.toInt();
else
if (currentKey == "temp") current->temp = value.toFloat();
else
if (currentKey == "feels_like") current->feels_like = value.toFloat();
else
if (currentKey == "pressure") current->pressure = value.toFloat();
else
if (currentKey == "humidity") current->humidity = value.toInt();
else
if (currentKey == "dew_point") current->dew_point = value.toFloat();
else
if (currentKey == "uvi") current->uvi = value.toFloat();
else
if (currentKey == "clouds") current->clouds = value.toInt();
else
if (currentKey == "visibility") current->visibility = value.toInt();
else
if (currentKey == "wind_speed") current->wind_speed = value.toFloat();
else
if (currentKey == "wind_gust") current->wind_gust = value.toFloat();
else
if (currentKey == "wind_deg") current->wind_deg = (uint16_t)value.toInt();
else
if (currentKey == "rain") current->rain = value.toFloat();
else
if (currentKey == "snow") current->snow = value.toFloat();
else
if (currentKey == "id") current->id = value.toInt();
else
if (currentKey == "main") current->main = value;
else
if (currentKey == "description") current->description = value;
else
if (currentKey == "icon") current->icon = value;
return;
}
// Hourly forecast
if (currentParent == "hourly") {
data_set = "hourly";
if (arrayIndex >= MAX_HOURS) return;
if (currentKey == "dt") hourly->dt[arrayIndex] = (uint32_t)value.toInt();
else
if (currentKey == "temp") hourly->temp[arrayIndex] = value.toFloat();
else
if (currentKey == "feels_like") hourly->feels_like[arrayIndex] = value.toFloat();
else
if (currentKey == "pressure") hourly->pressure[arrayIndex] = value.toFloat();
else
if (currentKey == "humidity") hourly->humidity[arrayIndex] = value.toInt();
else
if (currentKey == "dew_point") hourly->dew_point[arrayIndex] = value.toFloat();
else
if (currentKey == "clouds") hourly->clouds[arrayIndex] = value.toInt();
else
if (currentKey == "wind_speed") hourly->wind_speed[arrayIndex] = value.toFloat();
else
if (currentKey == "wind_gust") hourly->wind_gust[arrayIndex] = value.toFloat();
else
if (currentKey == "wind_deg") hourly->wind_deg[arrayIndex] = (uint16_t)value.toInt();
else
if (currentKey == "rain") hourly->rain[arrayIndex] = value.toFloat();
else
if (currentKey == "snow") hourly->snow[arrayIndex] = value.toFloat();
else
if (currentKey == "id") hourly->id[arrayIndex] = value.toInt();
else
if (currentKey == "main") hourly->main[arrayIndex] = value;
else
if (currentKey == "description") hourly->description[arrayIndex] = value;
else
if (currentKey == "icon") hourly->icon[arrayIndex] = value;
else
if (currentKey == "pop") hourly->pop[arrayIndex] = value.toFloat();
else
if (currentKey == "1h") hourly->rain1h[arrayIndex] = value.toFloat();
return;
}
// Daily forecast
if (currentParent == "daily") {
data_set = "daily";
if (arrayIndex >= MAX_DAYS) return;
if (currentKey == "dt") daily->dt[arrayIndex] = (uint32_t)value.toInt();
else
if (currentKey == "sunrise") daily->sunrise[arrayIndex] = (uint32_t)value.toInt();
else
if (currentKey == "sunset") daily->sunset[arrayIndex] = (uint32_t)value.toInt();
else
if (currentKey == "moonrise") daily->moonrise[arrayIndex] = (uint32_t)value.toInt();
else
if (currentKey == "moonset") daily->moonset[arrayIndex] = (uint32_t)value.toInt();
else
if (currentKey == "pressure") daily->pressure[arrayIndex] = value.toFloat();
else
if (currentKey == "humidity") daily->humidity[arrayIndex] = value.toInt();
else
if (currentKey == "dew_point") daily->dew_point[arrayIndex] = value.toFloat();
else
if (currentKey == "clouds") daily->clouds[arrayIndex] = value.toInt();
else
if (currentKey == "wind_speed") daily->wind_speed[arrayIndex] = value.toFloat();
else
if (currentKey == "wind_gust") daily->wind_gust[arrayIndex] = value.toFloat();
else
if (currentKey == "wind_deg") daily->wind_deg[arrayIndex] = (uint16_t)value.toInt();
else
if (currentKey == "rain") daily->rain[arrayIndex] = value.toFloat();
else
if (currentKey == "snow") daily->snow[arrayIndex] = value.toFloat();
else
if (currentKey == "id") daily->id[arrayIndex] = value.toInt();
else
if (currentKey == "main") daily->main[arrayIndex] = value;
else
if (currentKey == "description") daily->description[arrayIndex] = value;
else
if (currentKey == "icon") daily->icon[arrayIndex] = value;
else
if (currentKey == "pop") daily->pop[arrayIndex] = value.toFloat();
if (currentSet == "temp") {
if (currentKey == "morn") daily->temp_morn[arrayIndex] = value.toFloat();
else
if (currentKey == "day") daily->temp_day[arrayIndex] = value.toFloat();
else
if (currentKey == "eve") daily->temp_eve[arrayIndex] = value.toFloat();
else
if (currentKey == "night") daily->temp_night[arrayIndex] = value.toFloat();
else
if (currentKey == "min") daily->temp_min[arrayIndex] = value.toFloat();
else
if (currentKey == "max") daily->temp_max[arrayIndex] = value.toFloat();
}
if (currentSet == "feels_like") {
if (currentKey == "morn") daily->feels_like_morn[arrayIndex] = value.toFloat();
else
if (currentKey == "day") daily->feels_like_day[arrayIndex] = value.toFloat();
else
if (currentKey == "eve") daily->feels_like_eve[arrayIndex] = value.toFloat();
else
if (currentKey == "night") daily->feels_like_night[arrayIndex] = value.toFloat();
}
return;
}
}
/***************************************************************************************
** Function name: forecastDataSet
** Description: Collects full data set
***************************************************************************************/
void OW_Weather::forecastDataSet(const char *val) {
String value = val;
// Start of JSON
if (currentParent == "") {
if (currentKey == "timezone") forecast->timezone = value.toInt();
else
if (currentKey == "sunrise") forecast->sunrise = (uint32_t)value.toInt();
else
if (currentKey == "sunset") forecast->sunset = (uint32_t)value.toInt();
return;
}
// Loacation
if (currentParent == "city") {
if (currentKey == "name") forecast->city_name = value;
else
if (currentKey == "lat") lat = value.toFloat();
else
if (currentKey == "lon") lon = value.toFloat();
return;
}
// 3 hourly forecasts
if (currentParent == "list") {
data_set = "list";
if (arrayIndex >= MAX_3HRS) return;
if (currentKey == "dt") forecast->dt[arrayIndex] = (uint32_t)value.toInt();
else
if (currentKey == "temp") forecast->temp[arrayIndex] = value.toFloat();
else
if (currentKey == "temp_min") forecast->temp_min[arrayIndex] = value.toFloat();
else
if (currentKey == "temp_max") forecast->temp_max[arrayIndex] = value.toFloat();
else
if (currentKey == "feels_like") forecast->feels_like[arrayIndex] = value.toFloat();
else
if (currentKey == "pressure") forecast->pressure[arrayIndex] = value.toFloat();
else
if (currentKey == "sea_level") forecast->sea_level[arrayIndex] = value.toFloat();
else
if (currentKey == "grnd_level") forecast->grnd_level[arrayIndex] = value.toFloat();
else
if (currentKey == "humidity") forecast->humidity[arrayIndex] = value.toInt();
else
if (currentKey == "id") forecast->id[arrayIndex] = value.toInt();
else
if (currentKey == "main") forecast->main[arrayIndex] = value;
else
if (currentKey == "description") forecast->description[arrayIndex] = value;
else
if (currentKey == "icon") forecast->icon[arrayIndex] = value;
else
if (currentKey == "all") forecast->clouds_all[arrayIndex] = (uint8_t)value.toInt();
else
if (currentKey == "speed") forecast->wind_speed[arrayIndex] = value.toFloat();
else
if (currentKey == "deg") forecast->wind_deg[arrayIndex] = (uint16_t)value.toInt();
else
if (currentKey == "gust") forecast->wind_gust[arrayIndex] = value.toFloat();
else
if (currentKey == "visibility") forecast->visibility[arrayIndex] = value.toInt();
else
if (currentKey == "pop") forecast->pop[arrayIndex] = value.toFloat();
else
if (currentKey == "dt_txt") forecast->dt_txt[arrayIndex] = value;
return;
}
}
/***************************************************************************************
** Function name: partialDataSet
** Description: Collects partial data set
***************************************************************************************/
void OW_Weather::partialDataSet(const char *val) {
String value = val;
// Current forecast - no array index - short path
if (currentParent == "current") {
data_set = "current";
if (currentKey == "dt") current->dt = (uint32_t)value.toInt();
else
if (currentKey == "sunrise") current->sunrise = (uint32_t)value.toInt();
else
if (currentKey == "sunset") current->sunset = (uint32_t)value.toInt();
else
if (currentKey == "temp") current->temp = value.toFloat();
//else
//if (currentKey == "feels_like") current->feels_like = value.toFloat();
else
if (currentKey == "pressure") current->pressure = value.toFloat();
else
if (currentKey == "humidity") current->humidity = value.toInt();
//else
//if (currentKey == "dew_point") current->dew_point = value.toFloat();
//else
//if (currentKey == "uvi") current->uvi = value.toFloat();
else
if (currentKey == "clouds") current->clouds = value.toInt();
//else
//if (currentKey == "visibility") current->visibility = value.toInt();
else
if (currentKey == "wind_speed") current->wind_speed = value.toFloat();
//else
//if (currentKey == "wind_gust") current->wind_gust = value.toFloat();
else
if (currentKey == "wind_deg") current->wind_deg = (uint16_t)value.toInt();
//else
//if (currentKey == "rain") current->rain = value.toFloat();
//else
//if (currentKey == "snow") current->snow = value.toFloat();
else
if (currentKey == "id") current->id = value.toInt();
else
if (currentKey == "main") current->main = value;
else
if (currentKey == "description") current->description = value;
//else
//if (currentKey == "icon") current->icon = value;
return;
}
/*
// Hourly forecast
if (currentParent == "hourly") {
data_set = "hourly";
if (arrayIndex >= MAX_HOURS) return;
if (currentKey == "dt") hourly->dt[arrayIndex] = (uint32_t)value.toInt();
else
if (currentKey == "temp") hourly->temp[arrayIndex] = value.toFloat();
else
if (currentKey == "feels_like") hourly->feels_like[arrayIndex] = value.toFloat();
else
if (currentKey == "pressure") hourly->pressure[arrayIndex] = value.toFloat();
else
if (currentKey == "humidity") hourly->humidity[arrayIndex] = value.toInt();
else
if (currentKey == "dew_point") hourly->dew_point[arrayIndex] = value.toFloat();
else
if (currentKey == "clouds") hourly->clouds[arrayIndex] = value.toInt();
else
if (currentKey == "wind_speed") hourly->wind_speed[arrayIndex] = value.toFloat();
else
if (currentKey == "wind_gust") hourly->wind_gust[arrayIndex] = value.toFloat();
else
if (currentKey == "wind_deg") hourly->wind_deg[arrayIndex] = (uint16_t)value.toInt();
else
if (currentKey == "rain") hourly->rain[arrayIndex] = value.toFloat();
else
if (currentKey == "snow") hourly->snow[arrayIndex] = value.toFloat();
else
if (currentKey == "id") hourly->id[arrayIndex] = value.toInt();
else
if (currentKey == "main") hourly->main[arrayIndex] = value;
else
if (currentKey == "description") hourly->description[arrayIndex] = value;
else
if (currentKey == "icon") hourly->icon[arrayIndex] = value;
return;
}
*/
// Daily forecast
if (currentParent == "daily") {
data_set = "daily";
if (arrayIndex >= MAX_DAYS) return;
if (currentKey == "dt") daily->dt[arrayIndex] = (uint32_t)value.toInt();
else
//if (currentKey == "sunrise") daily->sunrise[arrayIndex] = (uint32_t)value.toInt();
//else
//if (currentKey == "sunset") daily->sunset[arrayIndex] = (uint32_t)value.toInt();
//else
//if (currentKey == "pressure") daily->pressure[arrayIndex] = value.toFloat();
//else
//if (currentKey == "humidity") daily->humidity[arrayIndex] = value.toInt();
//else
//if (currentKey == "dew_point") daily->dew_point[arrayIndex] = value.toFloat();
//else
//if (currentKey == "clouds") daily->clouds[arrayIndex] = value.toInt();
//else
//if (currentKey == "wind_speed") daily->wind_speed[arrayIndex] = value.toFloat();
//else
//if (currentKey == "wind_gust") daily->wind_gust[arrayIndex] = value.toFloat();
//else
//if (currentKey == "wind_deg") daily->wind_deg[arrayIndex] = (uint16_t)value.toInt();
//else
//if (currentKey == "rain") daily->rain[arrayIndex] = value.toFloat();
//else
//if (currentKey == "snow") daily->snow[arrayIndex] = value.toFloat();
//else
if (currentKey == "id") daily->id[arrayIndex] = value.toInt();
//else
//if (currentKey == "main") daily->main[arrayIndex] = value;
//else
//if (currentKey == "description") daily->description[arrayIndex] = value;
//else
//if (currentKey == "icon") daily->icon[arrayIndex] = value;
if (currentSet == "temp") {
//if (currentKey == "morn") daily->temp_morn[arrayIndex] = value.toFloat();
//else
//if (currentKey == "day") daily->temp_day[arrayIndex] = value.toFloat();
//else
//if (currentKey == "eve") daily->temp_eve[arrayIndex] = value.toFloat();
//else
//if (currentKey == "night") daily->temp_night[arrayIndex] = value.toFloat();
//else
if (currentKey == "min") daily->temp_min[arrayIndex] = value.toFloat();
else
if (currentKey == "max") daily->temp_max[arrayIndex] = value.toFloat();
}
//if (currentSet == "feels_like") {
//if (currentKey == "morn") daily->feels_like_morn[arrayIndex] = value.toFloat();
//else
//if (currentKey == "day") daily->feels_like_day[arrayIndex] = value.toFloat();
//else
//if (currentKey == "eve") daily->feels_like_eve[arrayIndex] = value.toFloat();
//else
//if (currentKey == "night") daily->feels_like_night[arrayIndex] = value.toFloat();
//}
return;
}
}
================================================
FILE: OpenWeather.h
================================================
// Client library for the OpenWeatherMap data-point server
// https://openweathermap.org/
// The API server uses https, so a client library with secure support is needed
// Created by Bodmer 9/4/2020
// This is a beta test version and is subject to change!
// Insecure mode added by ADAMSIN12
// See license.txt in root folder of library
#define MAX_ICON_INDEX 11 // Maximum for weather icon index
#define ICON_RAIN 1 // Index for the rain icon bitmap (bmp file)
#define NO_VALUE 11 // for precipType default (none)
#ifndef OpenWeather_h
#define OpenWeather_h
// The streaming parser to use is not the Arduino IDE library manager default,
// but this one which is slightly different and renamed to avoid conflicts:
// https://github.com/Bodmer/JSON_Decoder
#include <JSON_Listener.h>
#include <JSON_Decoder.h>
#include "User_Setup.h"
#include "Data_Point_Set.h"
/***************************************************************************************
** Description: JSON interface class
***************************************************************************************/
class OW_Weather: public JsonListener {
public:
// Sketch calls this forecast request, it returns true if no parse errors encountered
// ESP8266 only: setting secure to false will invoke an insecure connection
bool getForecast(OW_current *current, OW_hourly *hourly, OW_daily *daily,
String api_key, String latitude, String longitude,
String units, String language, bool secure = true);
// From 2023 the above call requires a subscription, this of uses the forecast API
// and is free for 1000 calls per day
bool getForecast(OW_forecast *forecast,
String api_key, String latitude, String longitude,
String units, String language, bool secure = true);
// Called by library (or user sketch), sends a GET request to a https (secure) url
bool parseRequest(String url); // and parses response, returns true if no parse errors
// Called by library (or user sketch), sends a GET request to a http (insecure) url
bool parseRequestSecure(String* url);
bool parseRequestInsecure(String* url);
void partialDataSet(bool partialSet);
float lat = 0;
float lon = 0;
String timezone = "";
private: // Streaming parser callback functions, allow tracking and decisions
void startDocument(); // JSON document has started, typically starts once
// Initialises variables used, e.g. sets objectLayer = 0
// and arrayIndex =0
void endDocument(); // JSON document has ended, typically ends once
void startObject(); // Called every time an Object start detected
// may be called multiple times as object layers entered
// Used to increment objectLayer
void endObject(); // Called every time an object ends
// Used to decrement objectLayer and zero arrayIndex
void startArray(); // An array of name:value pairs entered
void endArray(); // Array member ended, increments arrayIndex
void key(const char *key); // The current "object" or "name for a name:value pair"
void value(const char *value); // String value from name:value pair e.g. "1.23" or "rain"
void whitespace(char c); // Whitespace character in JSON - not used
void error( const char *message ); // Error message is sent to serial port
void fullDataSet(const char *value); // Populate structure with full data set
void partialDataSet(const char *value); // Populate structure with minimal data set
void forecastDataSet(const char *val); // Populate forecast structure
private: // Variables used internal to library
uint16_t hourly_index; // index into the OW_hourly structure's data arrays
uint16_t daily_index; // index into the OW_daily structure's data arrays
uint16_t forecast_index; // index into the OW_forecast structure's data arrays
// The value storage structures are created and deleted by the sketch and
// a pointer passed via the library getForecast() call the value() function
// is then used to populate the structs with values
OW_current *current; // pointer provided by sketch to the OW_current struct
OW_hourly *hourly; // pointer provided by sketch to the OW_hourly struct
OW_daily *daily; // pointer provided by sketch to the OW_daily struct
OW_forecast *forecast; // pointer provided by sketch to the OW_forecast struct
String valuePath; // object (i.e. sequential key) path (like a "file path")
// taken to the name:value pair in the form "hourly/data"
// so values can be pulled from the correct array.
// Needed since different objects contain "data" arrays.
String data_set; // A copy of the last object name at the head of an array
// short equivalent to path.
bool parseOK; // true if the parse been completed
// (does not mean data values gathered are good!)
bool partialSet = false; // Set true for partial data set acquisition
bool oneCall = true; // Use the oneCall API
String currentParent; // Current object e.g. "daily"
uint16_t objectLevel; // Object level, increments for new object, decrements at end
String currentKey; // Name key of the name:value pair e.g "temperature"
String currentSet; // Name key of the data set
String arrayPath; // Path to name:value pair e.g. "daily/data"
uint16_t arrayIndex; // Array index e.g. 5 for day 5 forecast, qualify with arrayPath
uint16_t arrayLevel; // Array level
bool Secure = true; // Link security setting secure (https) or insecure (http)
uint16_t port; //
};
/***************************************************************************************
***************************************************************************************/
#endif
================================================
FILE: README.md
================================================
# Raspberry Pico W, RP2040 Nano Connect, ESP8266 and ESP32 OpenWeather client
Arduino client library for https://openweathermap.org/
Collects current weather plus daily forecasts.
Requires the JSON parse library here:
https://github.com/Bodmer/JSON_Decoder
The OpenWeather_Forecast_Test example sketch sends collected data to the Serial port for API test. It does not not require a TFT screen and works with the Raspberry Pico W, RP2040 Nano Connect, ESP32 and ESP8266 processor boards. This example provides access to the weather data via a ser of variables, so could be adapted for use in weather related projects.
The TFT_eSPI_OpenWeather_LittleFS example works with the RP2040 Pico W, RP2040 Nano Connect, ESP32 and ESP8266. It uses LittleFS and displays the weather data on a TFT screen. This example uses the TFT_eSPI library.
The above examples will work with a free subscription to the OpenWeather service. The examples in the Onecall folder however require a subscription account (See OpenWeatherMap website for details).
The Raspberry Pico W and RP2040 Nano Connect must be used with Earle Philhower's board package:
https://github.com/earlephilhower/arduino-pico
These examples use anti-aliased fonts and newly created icons:

Latest screen grabs:

================================================
FILE: User_Setup.h
================================================
// Configuration settings for OpenWeather library
// These parameters set the data point count stored in program memory (not the datapoint
// count sent by the server). So they determine the memory used during collection
// of the data points.
#define MAX_HOURS 6 // Maximum "hourly" forecast period, can be up 1 to 48
// Hourly forecast not used by TFT_eSPI_OpenWeather example
#define MAX_DAYS 5 // Maximum "daily" forecast periods can be 1 to 8 (Today + 7 days = 8 maximum)
// TFT_eSPI_OpenWeather example requires this to be >= 5 (today + 4 forecast days)
//#define SHOW_HEADER // Debug only - for checking response header via serial message
//#define SHOW_JSON // Debug only - simple serial output formatting of whole JSON message
//#define SHOW_CALLBACK // Debug only to show the decode tree
#define OW_STATUS_ON // Debug only - turn on/off progress and status messages
// ###############################################################################
// DO NOT tinker below, this is configuration checking that helps stop crashes:
// ###############################################################################
#ifdef OW_STATUS_ON
#define OW_STATUS_PRINTF(C) Serial.print(F(C))
#define OW_STATUS_PRINT(V) Serial.print(V)
#else
#define OW_STATUS_PRINTF(C)
#define OW_STATUS_PRINT(X)
#endif
// Check and correct bad setting
#if (MAX_HOURS > 48) || (MAX_HOURS < 1)
#undef MAX_HOURS
#define MAX_HOURS 48 // Ignore compiler warning!
#endif
// Check and correct bad setting
#if (MAX_DAYS > 8) || (MAX_DAYS < 1)
#undef MAX_DAYS
#define MAX_DAYS 8 // Ignore compiler warning!
#endif
#define MAX_3HRS (MAX_DAYS * 8)
================================================
FILE: examples/Onecall API (subscription required)/My_OpenWeather_Test/My_OpenWeather_Test.ino
================================================
// Sketch for Pico W, RP2040 Nano Connect, ESP32 and ESP8266 to fetch the Weather
// Forecast from OpenWeather, an example from the library here:
// https://github.com/Bodmer/OpenWeather
// Sign up for a key and read API configuration info here:
// https://openweathermap.org/
// You can change the number of hours and days for the forecast in the
// "User_Setup.h" file inside the OpenWeather library folder.
// By default this is 6 hours (can be up to 48) and 5 days
// (can be up to 8 days = today plus 7 days)
// Choose library to load
#ifdef ESP8266
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#elif defined(ARDUINO_ARCH_MBED) || defined(ARDUINO_ARCH_RP2040)
#if defined(ARDUINO_RASPBERRY_PI_PICO_W)
#include <WiFi.h>
#else
#include <WiFiNINA.h>
#endif
#else // ESP32
#include <WiFi.h>
#endif
#include <JSON_Decoder.h>
#include <OpenWeather.h>
// Just using this library for unix time conversion
#include <Time.h>
// =====================================================
// ========= User configured stuff starts here =========
// Further configuration settings can be found in the
// OpenWeather library "User_Setup.h" file
#define TIME_OFFSET 1UL * 3600UL // UTC + 0 hour
// Change to suit your WiFi router
#define WIFI_SSID "Your_SSID"
#define WIFI_PASSWORD "Your_password"
// OpenWeather API Details, replace x's with your API key
String api_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; // Obtain this from your OpenWeather account
// Set both your longitude and latitude to at least 4 decimal places
String latitude = "27.9881"; // 90.0000 to -90.0000 negative for Southern hemisphere
String longitude = "86.9250"; // 180.000 to -180.000 negative for West
String units = "metric"; // or "imperial"
String language = "en"; // See notes tab
// ========= User configured stuff ends here =========
// =====================================================
OW_Weather ow; // Weather forecast library instance
void setup() {
Serial.begin(250000); // Fast to stop it holding up the stream
Serial.println("");
Serial.printf("\n\nConnecting to %s\n", WIFI_SSID);
Serial.println("");
// Call once for ESP32 and ESP8266
#if !defined(ARDUINO_ARCH_MBED)
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
#endif
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
#if defined(ARDUINO_ARCH_MBED) || defined(ARDUINO_ARCH_RP2040)
if (WiFi.status() != WL_CONNECTED) WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
#endif
delay(500);
}
Serial.println();
Serial.println("Connected\n");
}
void loop() {
printCurrentWeather();
// We can make 1000 requests a day
delay(5 * 60 * 1000); // Every 5 minutes = 288 requests per day
}
/***************************************************************************************
** Send weather info to serial port
***************************************************************************************/
void printCurrentWeather()
{
// Create the structures that hold the retrieved weather
OW_current *current = new OW_current;
OW_hourly *hourly = new OW_hourly;
OW_daily *daily = new OW_daily;
Serial.println("\nRequesting weather information from OpenWeather... ");
//On the ESP8266 (only) the library by default uses BearSSL, another option is to use AXTLS
//For problems with ESP8266 stability, use AXTLS by adding a false parameter thus vvvvv
//ow.getForecast(current, hourly, daily, api_key, latitude, longitude, units, language, false);
ow.getForecast(current, hourly, daily, api_key, latitude, longitude, units, language);
Serial.println("");
Serial.println("Weather from Open Weather\n");
// Position as reported by Open Weather
Serial.print("Latitude : "); Serial.println(ow.lat);
Serial.print("Longitude : "); Serial.println(ow.lon);
// We can use the timezone to set the offset eventually...
Serial.print("Timezone : "); Serial.println(ow.timezone);
Serial.println();
if (current)
{
Serial.println("############### Current weather ###############\n");
Serial.print("dt (time) : "); Serial.println(strTime(current->dt));
Serial.print("sunrise : "); Serial.println(strTime(current->sunrise));
Serial.print("sunset : "); Serial.println(strTime(current->sunset));
Serial.print("temp : "); Serial.println(current->temp);
Serial.print("feels_like : "); Serial.println(current->feels_like);
Serial.print("pressure : "); Serial.println(current->pressure);
Serial.print("humidity : "); Serial.println(current->humidity);
Serial.print("dew_point : "); Serial.println(current->dew_point);
Serial.print("uvi : "); Serial.println(current->uvi);
Serial.print("clouds : "); Serial.println(current->clouds);
Serial.print("visibility : "); Serial.println(current->visibility);
Serial.print("wind_speed : "); Serial.println(current->wind_speed);
Serial.print("wind_gust : "); Serial.println(current->wind_gust);
Serial.print("wind_deg : "); Serial.println(current->wind_deg);
Serial.print("rain : "); Serial.println(current->rain);
Serial.print("snow : "); Serial.println(current->snow);
Serial.println();
Serial.print("id : "); Serial.println(current->id);
Serial.print("main : "); Serial.println(current->main);
Serial.print("description : "); Serial.println(current->description);
Serial.print("icon : "); Serial.println(current->icon);
Serial.println();
}
if (hourly)
{
Serial.println("############### Hourly weather ###############\n");
for (int i = 0; i < MAX_HOURS; i++)
{
Serial.print("Hourly summary "); if (i < 10) Serial.print(" "); Serial.print(i);
Serial.println();
Serial.print("dt (time) : "); Serial.println(strTime(hourly->dt[i]));
Serial.print("temp : "); Serial.println(hourly->temp[i]);
Serial.print("feels_like : "); Serial.println(hourly->feels_like[i]);
Serial.print("pressure : "); Serial.println(hourly->pressure[i]);
Serial.print("humidity : "); Serial.println(hourly->humidity[i]);
Serial.print("dew_point : "); Serial.println(hourly->dew_point[i]);
Serial.print("clouds : "); Serial.println(hourly->clouds[i]);
Serial.print("wind_speed : "); Serial.println(hourly->wind_speed[i]);
Serial.print("wind_gust : "); Serial.println(hourly->wind_gust[i]);
Serial.print("wind_deg : "); Serial.println(hourly->wind_deg[i]);
Serial.print("rain : "); Serial.println(hourly->rain[i]);
Serial.print("snow : "); Serial.println(hourly->snow[i]);
Serial.println();
Serial.print("id : "); Serial.println(hourly->id[i]);
Serial.print("main : "); Serial.println(hourly->main[i]);
Serial.print("description : "); Serial.println(hourly->description[i]);
Serial.print("icon : "); Serial.println(hourly->icon[i]);
Serial.print("pop : "); Serial.println(hourly->pop[i]);
Serial.println();
}
}
if (daily)
{
Serial.println("############### Daily weather ###############\n");
for (int i = 0; i < MAX_DAYS; i++)
{
Serial.print("Daily summary "); if (i < 10) Serial.print(" "); Serial.print(i);
Serial.println();
Serial.print("dt (time) : "); Serial.println(strTime(daily->dt[i]));
Serial.print("sunrise : "); Serial.println(strTime(daily->sunrise[i]));
Serial.print("sunset : "); Serial.println(strTime(daily->sunset[i]));
Serial.print("temp.morn : "); Serial.println(daily->temp_morn[i]);
Serial.print("temp.day : "); Serial.println(daily->temp_day[i]);
Serial.print("temp.eve : "); Serial.println(daily->temp_eve[i]);
Serial.print("temp.night : "); Serial.println(daily->temp_night[i]);
Serial.print("temp.min : "); Serial.println(daily->temp_min[i]);
Serial.print("temp.max : "); Serial.println(daily->temp_max[i]);
Serial.print("feels_like.morn : "); Serial.println(daily->feels_like_morn[i]);
Serial.print("feels_like.day : "); Serial.println(daily->feels_like_day[i]);
Serial.print("feels_like.eve : "); Serial.println(daily->feels_like_eve[i]);
Serial.print("feels_like.night : "); Serial.println(daily->feels_like_night[i]);
Serial.print("pressure : "); Serial.println(daily->pressure[i]);
Serial.print("humidity : "); Serial.println(daily->humidity[i]);
Serial.print("dew_point : "); Serial.println(daily->dew_point[i]);
Serial.print("uvi : "); Serial.println(daily->uvi[i]);
Serial.print("clouds : "); Serial.println(daily->clouds[i]);
Serial.print("visibility : "); Serial.println(daily->visibility[i]);
Serial.print("wind_speed : "); Serial.println(daily->wind_speed[i]);
Serial.print("wind_gust : "); Serial.println(daily->wind_gust[i]);
Serial.print("wind_deg : "); Serial.println(daily->wind_deg[i]);
Serial.print("rain : "); Serial.println(daily->rain[i]);
Serial.print("snow : "); Serial.println(daily->snow[i]);
Serial.println();
Serial.print("id : "); Serial.println(daily->id[i]);
Serial.print("main : "); Serial.println(daily->main[i]);
Serial.print("description : "); Serial.println(daily->description[i]);
Serial.print("icon : "); Serial.println(daily->icon[i]);
Serial.print("pop : "); Serial.println(daily->pop[i]);
Serial.println();
}
}
// Delete to free up space and prevent fragmentation as strings change in length
delete current;
delete hourly;
delete daily;
}
/***************************************************************************************
** Convert unix time to a time string
***************************************************************************************/
String strTime(time_t unixTime)
{
unixTime += TIME_OFFSET;
return ctime(&unixTime);
}
================================================
FILE: examples/Onecall API (subscription required)/My_OpenWeather_Test/Notes.ino
================================================
/*
[units] should be one of the following:
metric
imperial
lang=[language] optional
Return summary properties in the desired language.
(Note that units in the summary will be set according to the units parameter,
so be sure to set both parameters appropriately.) language may be:
af: Africans
ar: Arabic
az: Azerbaijani
bg: Bulgarian
ca: Catalan
cs: Czech
da: Danish
de: German
el: Greek
en: English (which is the default)
eu: Basque
fa: Persian (Farsi)
fi: Finnish
fr: French
gl: Galician
he: Hebrew
hi: Hindi
hr: Croatian
hu: Hungarian
id: Indonesian
it: Italian
ja: Japanese
kr: Korean
la: Latvian
lt: Lithuanian
mk: Macedonian
no: Norwegian Bokmål
nl: Dutch
pl: Polish
pt: Portuguese
pt_br: Português Brasil
ro: Romanian
ru: Russian
sk: Slovak
sl: Slovenian
sp, es: Spanish
sr: Serbian
sv, se: Swedish
th: Thai
tr: Turkish
ua, uk: Ukrainian
vi: Vietnamese
zh_cn: Chinese Simplified
zh-tw: Chinese Traditional
zu: Zulu
*/
================================================
FILE: examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/All_Settings.h
================================================
// Use the OpenWeather library: https://github.com/Bodmer/OpenWeather
// The weather icons and fonts are in the sketch data folder, press Ctrl+K
// to view.
// >>> IMPORTANT TO PREVENT CRASHES <<<
//>>>>>> Set SPIFFS to at least 1.5Mbytes before uploading files <<<<<<
// >>> DON'T FORGET THIS <<<
// Upload the fonts and icons to SPIFFS using the "Tools" "ESP32 Sketch Data Upload"
// or "ESP8266 Sketch Data Upload" menu option in the IDE.
// To add this option follow instructions here for the ESP8266:
// https://github.com/esp8266/arduino-esp8266fs-plugin
// To add this option follow instructions here for the ESP32:
// https://github.com/me-no-dev/arduino-esp32fs-plugin
// Close the IDE and open again to see the new menu option.
// You can change the number of hours and days for the forecast in the
// "User_Setup.h" file inside the OpenWeather library folder.
// By default this is 6 hours (can be up to 48) and 5 days
// (can be up to 8 days = today plus 7 days). This sketch requires
// at least 5 days of forecast. Forecast hours can be set to 1 as
// the hourly forecast data is not used in this sketch.
//////////////////////////////
// Setttings defined below
#define WIFI_SSID "Your_SSID"
#define WIFI_PASSWORD "Your_password"
#define TIMEZONE UK // See NTP_Time.h tab for other "Zone references", UK, usMT etc
// Update every 15 minutes, up to 1000 request per day are free (viz average of ~40 per hour)
const int UPDATE_INTERVAL_SECS = 15 * 60UL; // 15 minutes
// Pins for the TFT interface are defined in the User_Config.h file inside the TFT_eSPI library
// For units use "metric" or "imperial"
const String units = "metric";
// Sign up for a key and read API configuration info here:
// https://openweathermap.org/, change x's to your API key
const String api_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Set the forecast longitude and latitude to at least 4 decimal places
const String latitude = "27.9881"; // 90.0000 to -90.0000 negative for Southern hemisphere
const String longitude = "86.9250"; // 180.000 to -180.000 negative for West
// For language codes see https://openweathermap.org/current#multi
const String language = "en"; // Default language = en = English
// Short day of week abbreviations used in 4 day forecast (change to your language)
const String shortDOW [8] = {"???", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"};
// Change the labels to your language here:
const char sunStr[] = "Sun";
const char cloudStr[] = "Cloud";
const char humidityStr[] = "Humidity";
const String moonPhase [8] = {"New", "Waxing", "1st qtr", "Waxing", "Full", "Waning", "Last qtr", "Waning"};
// End of user settings
//////////////////////////////
================================================
FILE: examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/GfxUi.cpp
================================================
/**The MIT License (MIT)
Copyright (c) 2015 by Daniel Eichhorn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
See more at http://blog.squix.ch
*/
// Adapted by Bodmer to use the TFT_eSPI library: https://github.com/Bodmer/TFT_eSPI
// Functions no longer needed weeded out, Jpeg decoder functions added and updated
// drawBMP() updated to buffer input and output pixels and avoid slow seeks
#include "GfxUi.h"
GfxUi::GfxUi(TFT_eSPI *tft) {
_tft = tft;
}
void GfxUi::drawProgressBar(uint16_t x0, uint16_t y0, uint16_t w, uint16_t h, uint8_t percentage, uint16_t frameColor, uint16_t barColor) {
if (percentage == 0) {
_tft->fillRoundRect(x0, y0, w, h, 3, TFT_BLACK);
}
uint8_t margin = 2;
uint16_t barHeight = h - 2 * margin;
uint16_t barWidth = w - 2 * margin;
_tft->drawRoundRect(x0, y0, w, h, 3, frameColor);
_tft->fillRect(x0 + margin, y0 + margin, barWidth * percentage / 100.0, barHeight, barColor);
}
// Bodmer's streamlined x2 faster "no seek" version
void GfxUi::drawBmp(String filename, uint16_t x, uint16_t y)
{
if ((x >= _tft->width()) || (y >= _tft->height())) return;
fs::File bmpFS;
// Check file exists and open it
// Serial.println(filename);
// Note: ESP32 passes "open" test even if file does not exist, whereas ESP8266 returns NULL
if ( !SPIFFS.exists(filename) )
{
Serial.println(F(" File not found")); // Can comment out if not needed
return;
}
// Open requested file
bmpFS = SPIFFS.open(filename, "r");
uint32_t seekOffset;
uint16_t w, h, row;
uint8_t r, g, b;
bool oldSwap = false;
if (read16(bmpFS) == 0x4D42)
{
read32(bmpFS);
read32(bmpFS);
seekOffset = read32(bmpFS);
read32(bmpFS);
w = read32(bmpFS);
h = read32(bmpFS);
if ((read16(bmpFS) == 1) && (read16(bmpFS) == 24) && (read32(bmpFS) == 0))
{
y += h - 1;
oldSwap = _tft->getSwapBytes();
_tft->setSwapBytes(true);
bmpFS.seek(seekOffset);
// Calculate padding to avoid seek
uint16_t padding = (4 - ((w * 3) & 3)) & 3;
uint8_t lineBuffer[w * 3 + padding];
for (row = 0; row < h; row++) {
bmpFS.read(lineBuffer, sizeof(lineBuffer));
uint8_t* bptr = lineBuffer;
uint16_t* tptr = (uint16_t*)lineBuffer;
// Convert 24 to 16 bit colours using the same line buffer for results
for (uint16_t col = 0; col < w; col++)
{
b = *bptr++;
g = *bptr++;
r = *bptr++;
*tptr++ = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}
// Push the pixel row to screen, pushImage will crop the line if needed
// y is decremented as the BMP image is drawn bottom up
_tft->pushImage(x, y--, w, 1, (uint16_t*)lineBuffer);
}
}
else Serial.println("BMP format not recognized.");
}
_tft->setSwapBytes(oldSwap);
bmpFS.close();
}
// These read 16- and 32-bit types from the SD card file.
// BMP data is stored little-endian, Arduino is little-endian too.
// May need to reverse subscript order if porting elsewhere.
uint16_t GfxUi::read16(fs::File &f) {
uint16_t result;
((uint8_t *)&result)[0] = f.read(); // LSB
((uint8_t *)&result)[1] = f.read(); // MSB
return result;
}
uint32_t GfxUi::read32(fs::File &f) {
uint32_t result;
((uint8_t *)&result)[0] = f.read(); // LSB
((uint8_t *)&result)[1] = f.read();
((uint8_t *)&result)[2] = f.read();
((uint8_t *)&result)[3] = f.read(); // MSB
return result;
}
/*====================================================================================
This sketch support functions to render the Jpeg images.
Created by Bodmer 15th Jan 2017
==================================================================================*/
// Return the minimum of two values a and b
#define minimum(a,b) (((a) < (b)) ? (a) : (b))
#define USE_SPI_BUFFER // Comment out to use slower 16 bit pushColor()
//====================================================================================
// Opens the image file and prime the Jpeg decoder
//====================================================================================
void GfxUi::drawJpeg(String filename, int xpos, int ypos) {
Serial.println("===========================");
Serial.print("Drawing file: "); Serial.println(filename);
Serial.println("===========================");
// Open the named file (the Jpeg decoder library will close it after rendering image)
fs::File jpegFile = SPIFFS.open( filename, "r"); // File handle reference for SPIFFS
// File jpegFile = SD.open( filename, FILE_READ); // or, file handle reference for SD library
if ( !jpegFile ) {
Serial.print("ERROR: File \""); Serial.print(filename); Serial.println ("\" not found!");
return;
}
// Use one of the three following methods to initialise the decoder:
//boolean decoded = JpegDec.decodeFsFile(jpegFile); // Pass a SPIFFS file handle to the decoder,
//boolean decoded = JpegDec.decodeSdFile(jpegFile); // or pass the SD file handle to the decoder,
boolean decoded = JpegDec.decodeFsFile(filename); // or pass the filename (leading / distinguishes SPIFFS files)
// Note: the filename can be a String or character array type
if (decoded) {
// print information about the image to the serial port
jpegInfo();
// render the image onto the screen at given coordinates
jpegRender(xpos, ypos);
}
else {
Serial.println("Jpeg file format not supported!");
}
}
//====================================================================================
// Decode and render the Jpeg image onto the TFT screen
//====================================================================================
void GfxUi::jpegRender(int xpos, int ypos) {
// retrieve infomration about the image
uint16_t *pImg;
int16_t mcu_w = JpegDec.MCUWidth;
int16_t mcu_h = JpegDec.MCUHeight;
int32_t max_x = JpegDec.width;
int32_t max_y = JpegDec.height;
// Jpeg images are draw as a set of image block (tiles) called Minimum Coding Units (MCUs)
// Typically these MCUs are 16x16 pixel blocks
// Determine the width and height of the right and bottom edge image blocks
uint32_t min_w = minimum(mcu_w, max_x % mcu_w);
uint32_t min_h = minimum(mcu_h, max_y % mcu_h);
// save the current image block size
int32_t win_w = mcu_w;
int32_t win_h = mcu_h;
// record the current time so we can measure how long it takes to draw an image
uint32_t drawTime = millis();
// save the coordinate of the right and bottom edges to assist image cropping
// to the screen size
max_x += xpos;
max_y += ypos;
// read each MCU block until there are no more
#ifdef USE_SPI_BUFFER
while( JpegDec.readSwappedBytes()){ // Swap byte order so the SPI buffer can be used
#else
while ( JpegDec.read()) { // Normal byte order read
#endif
// save a pointer to the image block
pImg = JpegDec.pImage;
// calculate where the image block should be drawn on the screen
int32_t mcu_x = JpegDec.MCUx * mcu_w + xpos;
int32_t mcu_y = JpegDec.MCUy * mcu_h + ypos;
// check if the image block size needs to be changed for the right edge
if (mcu_x + mcu_w <= max_x) win_w = mcu_w;
else win_w = min_w;
// check if the image block size needs to be changed for the bottom edge
if (mcu_y + mcu_h <= max_y) win_h = mcu_h;
else win_h = min_h;
// copy pixels into a contiguous block
if (win_w != mcu_w)
{
uint16_t *cImg;
int p = 0;
cImg = pImg + win_w;
for (int h = 1; h < win_h; h++)
{
p += mcu_w;
for (int w = 0; w < win_w; w++)
{
*cImg = *(pImg + w + p);
cImg++;
}
}
}
// draw image MCU block only if it will fit on the screen
if ( ( mcu_x + win_w) <= _tft->width() && ( mcu_y + win_h) <= _tft->height())
{
_tft->pushImage(mcu_x, mcu_y, win_w, win_h, pImg);
}
else if ( ( mcu_y + win_h) >= _tft->height()) JpegDec.abort();
}
// calculate how long it took to draw the image
drawTime = millis() - drawTime; // Calculate the time it took
// print the results to the serial port
Serial.print ("Total render time was : "); Serial.print(drawTime); Serial.println(" ms");
Serial.println("=====================================");
}
//====================================================================================
// Print information decoded from the Jpeg image
//====================================================================================
void GfxUi::jpegInfo() {
Serial.println("===============");
Serial.println("JPEG image info");
Serial.println("===============");
Serial.print ("Width :"); Serial.println(JpegDec.width);
Serial.print ("Height :"); Serial.println(JpegDec.height);
Serial.print ("Components :"); Serial.println(JpegDec.comps);
Serial.print ("MCU / row :"); Serial.println(JpegDec.MCUSPerRow);
Serial.print ("MCU / col :"); Serial.println(JpegDec.MCUSPerCol);
Serial.print ("Scan type :"); Serial.println(JpegDec.scanType);
Serial.print ("MCU width :"); Serial.println(JpegDec.MCUWidth);
Serial.print ("MCU height :"); Serial.println(JpegDec.MCUHeight);
Serial.println("===============");
Serial.println("");
}
//====================================================================================
================================================
FILE: examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/GfxUi.h
================================================
/**The MIT License (MIT)
Copyright (c) 2015 by Daniel Eichhorn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
See more at http://blog.squix.ch
*/
// Adapted by Bodmer to use the TFT_eSPI library:
// https://github.com/Bodmer/TFT_eSPI
#include <TFT_eSPI.h> // Hardware-specific library
#define FS_NO_GLOBALS // Avoid conflict with SD library File type definition
#include <FS.h>
// JPEG decoder library
#include <JPEGDecoder.h>
#ifndef _GFX_UI_H
#define _GFX_UI_H
// Maximum of 85 for BUFFPIXEL as 3 x this value is stored in an 8 bit variable!
// 32 is an efficient size for SPIFFS due to SPI hardware pipeline buffer size
// A larger value of 80 is better for SD cards
#define BUFFPIXEL 32
class GfxUi {
public:
GfxUi(TFT_eSPI * tft);
void drawBmp(String filename, uint16_t x, uint16_t y);
void drawProgressBar(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t percentage, uint16_t frameColor, uint16_t barColor);
void jpegInfo();
void drawJpeg(String filename, int xpos, int ypos);
void jpegRender(int xpos, int ypos);
private:
TFT_eSPI * _tft;
uint16_t read16(fs::File &f);
uint32_t read32(fs::File &f);
};
#endif
================================================
FILE: examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/MoonPhase.ino
================================================
/* Moon phase calculation for OpenWeather library*/
// Adapted by Bodmer from code here:
// http://www.voidware.com/moon_phase.htm
#include <stdio.h>
#include <math.h>
#define PI 3.1415926535897932384626433832795
#define RAD (PI/180.0)
#define SMALL_FLOAT (1e-12)
double Julian(int year, int month, double day)
{
int a, b, c, e;
if (month < 3) {
year--;
month += 12;
}
if (year > 1582 || (year == 1582 && month > 10) ||
(year == 1582 && month == 10 && day > 15)) {
a = year / 100;
b = 2 - a + a / 4;
}
c = 365.25 * year;
e = 30.6001 * (month + 1);
return b + c + e + day + 1720994.5;
}
double sun_position(double j)
{
double n, x, e, l, dl, v;
double m2;
int i;
n = 360 / 365.2422 * j;
i = n / 360;
n = n - i * 360.0;
x = n - 3.762863;
if (x < 0) x += 360;
x *= RAD;
e = x;
do {
dl = e - .016718 * sin(e) - x;
e = e - dl / (1 - .016718 * cos(e));
} while (fabs(dl) >= SMALL_FLOAT);
v = 360 / PI * atan(1.01686011182 * tan(e / 2));
l = v + 282.596403;
i = l / 360;
l = l - i * 360.0;
return l;
}
double moon_position(double j, double ls)
{
double ms, l, mm, n, ev, sms, z, x, lm, bm, ae, ec;
double d;
int i;
/* ls = sun_position(j) */
ms = 0.985647332099 * j - 3.762863;
if (ms < 0) ms += 360.0;
l = 13.176396 * j + 64.975464;
i = l / 360;
l = l - i * 360.0;
if (l < 0) l += 360.0;
mm = l - 0.1114041 * j - 349.383063;
i = mm / 360;
mm -= i * 360.0;
n = 151.950429 - 0.0529539 * j;
i = n / 360;
n -= i * 360.0;
ev = 1.2739 * sin((2 * (l - ls) - mm) * RAD);
sms = sin(ms * RAD);
ae = 0.1858 * sms;
mm += ev - ae - 0.37 * sms;
ec = 6.2886 * sin(mm * RAD);
l += ev + ec - ae + 0.214 * sin(2 * mm * RAD);
l = 0.6583 * sin(2 * (l - ls) * RAD) + l;
return l;
}
uint8_t moon_phase(int year, int month, int day, double hour, int* ip)
{
double j = Julian(year, month, (double)day + hour / 24.0) - 2444238.5;
double ls = sun_position(j);
double lm = moon_position(j, ls);
double t = lm - ls;
if (t < 0) t += 360;
*ip = (int)((t + 22.5)/45) & 0x7; // Moon state 0-7 for moonPhase[] index
return ((int)((t + 7.5)/15) + 23) % 24; // Moon state 0-23 for icon bitmap
//return 100.0 * ((1.0 - cos((lm - ls) * RAD)) / 2) + 0.5; // percent illuminated
}
================================================
FILE: examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/NTP_Time.h
================================================
//====================================================================================
// Libraries
//====================================================================================
// Time library:
// https://github.com/PaulStoffregen/Time
#include <Time.h>
// Time zone correction library:
// https://github.com/JChristensen/Timezone
#include <Timezone.h>
// Libraries built into IDE
#ifdef ESP8266
#include <ESP8266WiFi.h>
#else
#include <WiFi.h>
#endif
#include <WiFiUdp.h>
// A UDP instance to let us send and receive packets over UDP
WiFiUDP udp;
//====================================================================================
// Settings
//====================================================================================
#ifdef ESP32 // Temporary fix, ESP8266 fails to communicate with some servers...
// Try to use pool url instead so the server IP address is looked up from those available
// (use a pool server in your own country to improve response time and reliability)
//const char* ntpServerName = "time.nist.gov";
//const char* ntpServerName = "pool.ntp.org";
const char* ntpServerName = "time.google.com";
#else
// Try to use pool url instead so the server IP address is looked up from those available
// (use a pool server in your own country to improve response time and reliability)
// const char* ntpServerName = "time.nist.gov";
const char* ntpServerName = "pool.ntp.org";
//const char* ntpServerName = "time.google.com";
#endif
// Try not to use hard-coded IP addresses which might change, you can if you want though...
//IPAddress timeServerIP(129, 6, 15, 30); // time-c.nist.gov NTP server
//IPAddress timeServerIP(24, 56, 178, 140); // wwv.nist.gov NTP server
IPAddress timeServerIP; // Use server pool
// Example time zone and DST rules, see Timezone library documents to see how
// to add more time zones https://github.com/JChristensen/Timezone
// Zone reference "UK" United Kingdom (London, Belfast)
TimeChangeRule BST = {"BST", Last, Sun, Mar, 1, 60}; //British Summer (Daylight saving) Time
TimeChangeRule GMT = {"GMT", Last, Sun, Oct, 2, 0}; //Standard Time
Timezone UK(BST, GMT);
// Zone reference "euCET" Central European Time (Frankfurt, Paris)
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120}; //Central European Summer Time
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60}; //Central European Standard Time
Timezone euCET(CEST, CET);
// Zone reference "ausET" Australia Eastern Time Zone (Sydney, Melbourne)
TimeChangeRule aEDT = {"AEDT", First, Sun, Oct, 2, 660}; //UTC + 11 hours
TimeChangeRule aEST = {"AEST", First, Sun, Apr, 3, 600}; //UTC + 10 hours
Timezone ausET(aEDT, aEST);
// Zone reference "usET US Eastern Time Zone (New York, Detroit)
TimeChangeRule usEDT = {"EDT", Second, Sun, Mar, 2, -240}; //Eastern Daylight Time = UTC - 4 hours
TimeChangeRule usEST = {"EST", First, Sun, Nov, 2, -300}; //Eastern Standard Time = UTC - 5 hours
Timezone usET(usEDT, usEST);
// Zone reference "usCT" US Central Time Zone (Chicago, Houston)
TimeChangeRule usCDT = {"CDT", Second, dowSunday, Mar, 2, -300};
TimeChangeRule usCST = {"CST", First, dowSunday, Nov, 2, -360};
Timezone usCT(usCDT, usCST);
// Zone reference "usMT" US Mountain Time Zone (Denver, Salt Lake City)
TimeChangeRule usMDT = {"MDT", Second, dowSunday, Mar, 2, -360};
TimeChangeRule usMST = {"MST", First, dowSunday, Nov, 2, -420};
Timezone usMT(usMDT, usMST);
// Zone reference "usAZ" Arizona is US Mountain Time Zone but does not use DST
Timezone usAZ(usMST, usMST);
// Zone reference "usPT" US Pacific Time Zone (Las Vegas, Los Angeles)
TimeChangeRule usPDT = {"PDT", Second, dowSunday, Mar, 2, -420};
TimeChangeRule usPST = {"PST", First, dowSunday, Nov, 2, -480};
Timezone usPT(usPDT, usPST);
//====================================================================================
// Variables
//====================================================================================
TimeChangeRule *tz1_Code; // Pointer to the time change rule, use to get the TZ abbrev, e.g. "GMT"
time_t utc = 0;
bool timeValid = false;
unsigned int localPort = 2390; // local port to listen for UDP packets
const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE ]; //buffer to hold incoming and outgoing packets
uint8_t lastMinute = 0;
uint32_t nextSendTime = 0;
uint32_t newRecvTime = 0;
uint32_t lastRecvTime = 0;
uint32_t newTickTime = 0;
uint32_t lastTickTime = 0;
bool rebooted = 1;
uint32_t no_packet_count = 0;
//====================================================================================
// Function prototype
//====================================================================================
void syncTime(void);
void displayTime(void);
void printTime(time_t zone, char *tzCode);
void decodeNTP(void);
void sendNTPpacket(IPAddress& address);
//====================================================================================
// Update Time
//====================================================================================
void syncTime(void)
{
// Don't send too often so we don't trigger Denial of Service
if (nextSendTime < millis()) {
// Get a random server from the pool
WiFi.hostByName(ntpServerName, timeServerIP);
nextSendTime = millis() + 5000;
// Flush old late packets
while (udp.parsePacket() > 0) { // Is a packet there?
Serial.println("Reading delayed NTP packet."); // Yes
udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
}
sendNTPpacket(timeServerIP); // send an NTP packet to a time server
decodeNTP();
}
}
//====================================================================================
// Send an NTP request to the time server at the given address
//====================================================================================
void sendNTPpacket(IPAddress& address)
{
// Serial.println("sending NTP packet...");
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011; // LI, Version, Mode
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
udp.beginPacket(address, 123); //NTP requests are to port 123
udp.write(packetBuffer, NTP_PACKET_SIZE);
udp.endPacket();
}
//====================================================================================
// Decode the NTP message and print status to serial port
//====================================================================================
void decodeNTP(void)
{
timeValid = false;
uint32_t waitTime = millis() + 500;
while (millis() < waitTime && !timeValid)
{
yield();
if (udp.parsePacket())
{
newRecvTime = millis();
// We've received a packet, read the data from it
udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
Serial.print("\nNTP response time was : ");
Serial.print(500 - (waitTime - newRecvTime));
Serial.println(" ms");
Serial.print("Time since last sync is: ");
Serial.print((newRecvTime - lastRecvTime) / 1000.0);
Serial.println(" s");
lastRecvTime = newRecvTime;
// The timestamp starts at byte 40 of the received packet and is four bytes,
// or two words, long. First, extract the two words:
unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
// Combine the four bytes (two words) into a long integer
// this is NTP time (seconds since Jan 1 1900):
unsigned long secsSince1900 = highWord << 16 | lowWord;
// Now convert NTP Unix time (Seconds since Jan 1 1900) into everyday time:
// UTC time starts on Jan 1 1970. In seconds the difference is 2208988800:
utc = secsSince1900 - 2208988800UL;
setTime(utc); // Set system clock to utc time (not time zone compensated)
timeValid = true;
// Print the hour, minute and second:
Serial.print("Received NTP UTC time : ");
uint8_t hh = hour(utc);
Serial.print(hh); // print the hour (86400 equals secs per day)
Serial.print(':');
uint8_t mm = minute(utc);
if (mm < 10 ) Serial.print('0');
Serial.print(mm); // print the minute (3600 equals secs per minute)
Serial.print(':');
uint8_t ss = second(utc);
if ( ss < 10 ) Serial.print('0');
Serial.println(ss); // print the second
}
}
// Keep a count of missing or bad NTP replies
if ( timeValid ) {
no_packet_count = 0;
}
else
{
Serial.println("\nNo NTP reply, trying again in 1 minute...");
no_packet_count++;
}
if (no_packet_count >= 10) {
no_packet_count = 0;
// TODO: Flag the lack of sync on the display
Serial.println("\nNo NTP packet in last 10 minutes");
}
}
//====================================================================================
// Debug use only
//====================================================================================
void printTime(time_t t, char *tzCode)
{
String dateString = dayStr(weekday(t));
dateString += " ";
dateString += day(t);
if (day(t) == 1 || day(t) == 21 || day(t) == 31) dateString += "st";
else if (day(t) == 2 || day(t) == 22) dateString += "nd";
else if (day(t) == 3 || day(t) == 23) dateString += "rd";
else dateString += "th";
dateString += " ";
dateString += monthStr(month(t));
dateString += " ";
dateString += year(t);
// Print time to serial port
Serial.print(hour(t));
Serial.print(":");
Serial.print(minute(t));
Serial.print(":");
Serial.print(second(t));
Serial.print(" ");
// Print time t
Serial.print(tzCode);
Serial.print(" ");
// Print date
Serial.print(day(t));
Serial.print("/");
Serial.print(month(t));
Serial.print("/");
Serial.print(year(t));
Serial.print(" ");
// Now test some other functions that might be useful one day!
Serial.print(dayStr(weekday(t)));
Serial.print(" ");
Serial.print(monthStr(month(t)));
Serial.print(" ");
Serial.print(dayShortStr(weekday(t)));
Serial.print(" ");
Serial.print(monthShortStr(month(t)));
Serial.println();
}
//====================================================================================
================================================
FILE: examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/SPIFFS_Support.h
================================================
/*====================================================================================
This sketch contains support functions for the ESP6266 SPIFFS filing system
Created by Bodmer 15th Jan 2017
==================================================================================*/
void listDir(fs::FS &fs, const char * dirname, uint8_t levels);
// -------------------------------------------------------------------------
// List SPIFFS files in a neat format for ESP8266 or ESP32
// -------------------------------------------------------------------------
void listFiles(void) {
Serial.println();
Serial.println("SPIFFS files found:");
#ifdef ESP32
listDir(SPIFFS, "/", true);
#else
fs::Dir dir = SPIFFS.openDir("/"); // Root directory
String line = "=====================================";
Serial.println(line);
Serial.println(" File name Size");
Serial.println(line);
while (dir.next()) {
String fileName = dir.fileName();
Serial.print(fileName);
// File path can be 31 characters maximum in SPIFFS
int spaces = 33 - fileName.length(); // Tabulate nicely
if (spaces < 1) spaces = 1;
while (spaces--) Serial.print(" ");
fs::File f = dir.openFile("r");
Serial.print(f.size()); Serial.println(" bytes");
yield();
}
Serial.println(line);
#endif
Serial.println();
delay(1000);
}
#ifdef ESP32
void listDir(fs::FS &fs, const char * dirname, uint8_t levels) {
Serial.printf("Listing directory: %s\n", dirname);
fs::File root = fs.open(dirname);
if (!root) {
Serial.println("Failed to open directory");
return;
}
if (!root.isDirectory()) {
Serial.println("Not a directory");
return;
}
fs::File file = root.openNextFile();
while (file) {
if (file.isDirectory()) {
Serial.print("DIR : ");
String fileName = file.name();
Serial.print(fileName);
if (levels) {
listDir(fs, file.name(), levels - 1);
}
} else {
String fileName = file.name();
Serial.print(" " + fileName);
// File path can be 31 characters maximum in SPIFFS
int spaces = 33 - fileName.length(); // Tabulate nicely
if (spaces < 1) spaces = 1;
while (spaces--) Serial.print(" ");
String fileSize = (String) file.size();
spaces = 8 - fileSize.length(); // Tabulate nicely
if (spaces < 1) spaces = 1;
while (spaces--) Serial.print(" ");
Serial.println(fileSize + " bytes");
}
file = root.openNextFile();
}
}
#endif
//====================================================================================
================================================
FILE: examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/ScreenGrabClient.ino
================================================
// This is a copy of the processing sketch that can be used to capture the images
// Not needed by this sketch, used during development with screenSaver() functions.
// Copy the sketch below into the Processing IDE and remove the /* and */ at the beginning and
// end.
// The sketch runs in Processing version 3.3 (or later) on a PC, it can be downloaded here:
// https://processing.org/download/
/*
// This is a Processing sketch, see https://processing.org/ to download the IDE
// The sketch is a client that requests TFT screen-shots from an Arduino board.
// The Arduino must call a screen-shot server function to respond with pixels.
// It has been created to work with the TFT_eSPI library here:
// https://github.com/Bodmer/TFT_eSPI
// The sketch must only be run when the designated serial port is available and enumerated
// otherwise the screen-shot window may freeze and that process will need to be terminated
// This is a limitation of the Processing environment and not the sketch.
// If anyone knows how to determine if a serial port is available at start up the PM me
// on (Bodmer) the Arduino forum.
// The block below contains variables that the user may need to change for a particular setup
// As a minimum set the serial port and baud rate must be defined. The capture window is
// automatically resized for landscape, portrait and different TFT resolutions.
// Captured images are stored in the sketch folder, use the Processing IDE "Sketch" menu
// option "Show Sketch Folder" or press Ctrl+K
// Created by: Bodmer 5/3/17
// Updated by: Bodmer 12/3/17
// Version: 0.07
// MIT licence applies, all text above must be included in derivative works
// ###########################################################################################
// # These are the values to change for a particular setup #
// #
int serial_port = 0; // Use enumerated value from list provided when sketch is run #
// #
// On an Arduino Due Programming Port use a baud rate of:115200) #
// On an Arduino Due Native USB Port use a baud rate of any value #
int serial_baud_rate = 250000; // #
// #
// Change the image file type saved here, comment out all but one #
//String image_type = ".jpg"; // #
String image_type = ".png"; // Lossless compression #
//String image_type = ".bmp"; // #
//String image_type = ".tif"; // #
// #
boolean save_border = true; // Save the image with a border #
int border = 5; // Border pixel width #
boolean fade = false; // Fade out image after saving #
// #
int max_images = 100; // Maximum of numbered file images before over-writing files #
// #
int max_allowed = 1000; // Maximum number of save images allowed before a restart #
// #
// # End of the values to change for a particular setup #
// ###########################################################################################
// These are default values, this sketch obtains the actual values from the Arduino board
int tft_width = 480; // default TFT width (automatic - sent by Arduino)
int tft_height = 480; // default TFT height (automatic - sent by Arduino)
int color_bytes = 2; // 2 for 16 bit, 3 for three RGB bytes (automatic - sent by Arduino)
import processing.serial.*;
Serial serial; // Create an instance called serial
int serialCount = 0; // Count of colour bytes arriving
// Stage window graded background colours
color bgcolor1 = color(0, 100, 104); // Arduino IDE style background color 1
color bgcolor2 = color(77, 183, 187); // Arduino IDE style background color 2
//color bgcolor2 = color(255, 255, 255); // White
// TFT image frame greyscale value (dark grey)
color frameColor = 42;
color buttonStopped = color(255, 0, 0);
color buttonRunning = color(128, 204, 206);
color buttonDimmed = color(180, 0, 0);
boolean dimmed = false;
boolean running = true;
boolean mouseClick = false;
int[] rgb = new int[3]; // Buffer for the colour bytes
int indexRed = 0; // Colour byte index in the array
int indexGreen = 1;
int indexBlue = 2;
int n = 0;
int x_offset = (500 - tft_width) /2; // Image offsets in the window
int y_offset = 20;
int xpos = 0, ypos = 0; // Current pixel position
int beginTime = 0;
int pixelWaitTime = 1000; // Maximum 1000ms wait for image pixels to arrive
int lastPixelTime = 0; // Time that "image send" command was sent
int requestTime = 0;
int requestCount = 0;
int state = 0; // State machine current state
int progress_bar = 0; // Console progress bar dot count
int pixel_count = 0; // Number of pixels read for 1 screen
float percentage = 0; // Percentage of pixels received
int saved_image_count = 0; // Stats - number of images processed
int bad_image_count = 0; // Stats - number of images that had lost pixels
String filename = "";
int drawLoopCount = 0; // Used for the fade out
void setup() {
size(500, 540); // Stage size, can handle 480 pixels wide screen
noStroke(); // No border on the next thing drawn
noSmooth(); // No anti-aliasing to avoid adjacent pixel colour merging
// Graded background and title
drawWindow();
frameRate(2000); // High frame rate so draw() loops fast
// Print a list of the available serial ports
println("-----------------------");
println("Available Serial Ports:");
println("-----------------------");
printArray(Serial.list());
println("-----------------------");
print("Port currently used: [");
print(serial_port);
println("]");
String portName = Serial.list()[serial_port];
serial = new Serial(this, portName, serial_baud_rate);
state = 99;
}
void draw() {
if (mouseClick) buttonClicked();
switch(state) {
case 0: // Init varaibles, send start request
if (running) {
tint(0, 0, 0, 255);
flushBuffer();
println("");
print("Ready: ");
xpos = 0;
ypos = 0;
serialCount = 0;
progress_bar = 0;
pixel_count = 0;
percentage = 0;
drawLoopCount = frameCount;
lastPixelTime = millis() + 1000;
state = 1;
} else {
if (millis() > beginTime) {
beginTime = millis() + 500;
dimmed = !dimmed;
if (dimmed) drawButton(buttonDimmed);
else drawButton(buttonStopped);
}
}
break;
case 1: // Console message, give server some time
print("requesting image ");
serial.write("S");
delay(10);
beginTime = millis();
requestTime = millis() + 1000;
requestCount = 1;
state = 2;
break;
case 2: // Get size and set start time for rendering duration report
if (millis() > requestTime) {
requestCount++;
print("*");
serial.clear();
serial.write("S");
if (requestCount > 32) {
requestCount = 0;
System.err.println(" - no response!");
state = 0;
}
requestTime = millis() + 1000;
}
if ( getSize() == true ) { // Go to next state when we have the size and bits per pixel
getFilename();
flushBuffer(); // Precaution in case image header size increases in later versions
lastPixelTime = millis() + 1000;
beginTime = millis();
state = 3;
}
break;
case 3: // Request pixels and render returned RGB values
state = renderPixels(); // State will change when all pixels are rendered
// Request more pixels, changing the number requested allows the average transfer rate to
// be controlled. The pixel transfer rate is dependant on four things:
// 1. The frame rate defined in this Processing sketch in setup()
// 2. The baud rate of the serial link (~10 bit periods per byte)
// 3. The number of request bytes 'R' sent in the lines below
// 4. The number of pixels sent in a burst by the server sketch (defined via NPIXELS)
//serial.write("RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR"); // 32 x NPIXELS more
serial.write("RRRRRRRRRRRRRRRR"); // 16 x NPIXELS more
//serial.write("RRRRRRRR"); // 8 x NPIXELS more
//serial.write("RRRR"); // 4 x NPIXELS more
//serial.write("RR"); // 2 x NPIXELS more
//serial.write("R"); // 1 x NPIXELS more
if (!running) state = 4;
break;
case 4: // Pixel receive time-out, flush serial buffer
flushBuffer();
state = 6;
break;
case 5: // Save the image to the sketch folder (Ctrl+K to access)
saveScreenshot();
saved_image_count++;
println("Saved image count = " + saved_image_count);
if (bad_image_count > 0) System.err.println(" Bad image count = " + bad_image_count);
drawLoopCount = frameCount; // Reset value ready for counting in step 6
state = 6;
break;
case 6: // Fade the old image if enabled
if ( fadedImage() == true ) state = 0; // Go to next state when image has faded
break;
case 99: // Draw image viewer window
drawWindow();
delay(50); // Delay here seems to be required for the IDE console to get ready
state = 0;
break;
default:
println("");
System.err.println("Error state reached - check sketch!");
break;
}
}
void drawWindow()
{
// Graded background in Arduino colours
for (int i = 0; i < height - 25; i++) {
float inter = map(i, 0, height - 25, 0, 1);
color c = lerpColor(bgcolor1, bgcolor2, inter);
stroke(c);
line(0, i, 500, i);
}
fill(bgcolor2);
rect( 0, height-25, width-1, 24);
textAlign(CENTER);
textSize(20);
fill(0);
text("Bodmer's TFT image viewer", width/2, height-6);
if (running) drawButton(buttonRunning);
else drawButton(buttonStopped);
}
void flushBuffer()
{
//println("Clearing serial pipe after a time-out");
int clearTime = millis() + 50;
while ( millis() < clearTime ) serial.clear();
}
boolean getSize()
{
if ( serial.available() > 6 ) {
println();
char code = (char)serial.read();
if (code == 'W') {
tft_width = serial.read()<<8 | serial.read();
}
code = (char)serial.read();
if (code == 'H') {
tft_height = serial.read()<<8 | serial.read();
}
code = (char)serial.read();
if (code == 'Y') {
int bits_per_pixel = (char)serial.read();
if (bits_per_pixel == 24) color_bytes = 3;
else color_bytes = 2;
}
code = (char)serial.read();
if (code == '?') {
drawWindow();
x_offset = (500 - tft_width) /2;
tint(0, 0, 0, 255);
noStroke();
fill(frameColor);
rect((width - tft_width)/2 - border, y_offset - border, tft_width + 2 * border, tft_height + 2 * border);
return true;
}
}
return false;
}
void saveScreenshot()
{
println();
if (saved_image_count < max_allowed)
{
if (filename == "") filename = "tft_screen_" + (n++);
filename = filename + image_type;
println("Saving image as \"" + filename + "\"");
if (save_border)
{
PImage partialSave = get(x_offset - border, y_offset - border, tft_width + 2*border, tft_height + 2*border);
partialSave.save(filename);
} else {
PImage partialSave = get(x_offset, y_offset, tft_width, tft_height);
partialSave.save(filename);
}
if (n>=max_images) n = 0;
}
else
{
System.err.println(max_allowed + " saved image count exceeded, restart the sketch");
}
}
void getFilename()
{
int readTime = millis() + 20;
int inByte = 0;
filename = "";
while ( serial.available() > 0 && millis() < readTime && inByte != '.')
{
inByte = serial.read();
if (inByte == ' ') inByte = '_';
if ( unicodeCheck(inByte) ) filename += (char)inByte;
}
inByte = serial.read();
if (inByte == '@') filename += "_" + timeCode();
else if (inByte == '#') filename += "_" + saved_image_count%100;
else if (inByte == '%') filename += "_" + millis();
else if (inByte != '*') filename = "";
inByte = serial.read();
if (inByte == 'j') image_type =".jpg";
else if (inByte == 'b') image_type =".bmp";
else if (inByte == 'p') image_type =".png";
else if (inByte == 't') image_type =".tif";
}
boolean unicodeCheck(int unicode)
{
if ( unicode >= '0' && unicode <= '9' ) return true;
if ( (unicode >= 'A' && unicode <= 'Z' ) || (unicode >= 'a' && unicode <= 'z')) return true;
if ( unicode == '_' || unicode == '/' ) return true;
return false;
}
String timeCode()
{
String timeCode = (int)year() + "_" + (int)month() + "_" + (int)day() + "_";
timeCode += (int)hour() + "_" + (int)minute() + "_" + (int)second();
return timeCode;
}
int renderPixels()
{
if ( serial.available() > 0 ) {
// Add the latest byte from the serial port to array:
while (serial.available()>0)
{
rgb[serialCount++] = serial.read();
// If we have 3 colour bytes:
if ( serialCount >= color_bytes ) {
serialCount = 0;
pixel_count++;
if (color_bytes == 3)
{
stroke(rgb[indexRed], rgb[indexGreen], rgb[indexBlue], 1000);
} else
{ // Can cater for various byte orders
//stroke( (rgb[0] & 0x1F)<<3, (rgb[0] & 0xE0)>>3 | (rgb[1] & 0x07)<<5, (rgb[1] & 0xF8));
//stroke( (rgb[1] & 0x1F)<<3, (rgb[1] & 0xE0)>>3 | (rgb[0] & 0x07)<<5, (rgb[0] & 0xF8));
stroke( (rgb[0] & 0xF8), (rgb[1] & 0xE0)>>3 | (rgb[0] & 0x07)<<5, (rgb[1] & 0x1F)<<3);
//stroke( (rgb[1] & 0xF8), (rgb[0] & 0xE0)>>3 | (rgb[1] & 0x07)<<5, (rgb[0] & 0x1F)<<3);
}
// We get some pixel merge aliasing if smooth() is defined, so draw pixel twice
point(xpos + x_offset, ypos + y_offset);
//point(xpos + x_offset, ypos + y_offset);
lastPixelTime = millis();
xpos++;
if (xpos >= tft_width) {
xpos = 0;
progressBar();
ypos++;
if (ypos>=tft_height) {
ypos = 0;
if ((int)percentage <100) {
while (progress_bar++ < 64) print(" ");
percent(100);
}
println("Image fetch time = " + (millis()-beginTime)/1000.0 + " s");
return 5;
}
}
}
}
} else
{
if (millis() > (lastPixelTime + pixelWaitTime))
{
println("");
System.err.println(pixelWaitTime + "ms time-out for pixels exceeded...");
if (pixel_count > 0) {
bad_image_count++;
System.err.print("Pixels missing = " + (tft_width * tft_height - pixel_count));
System.err.println(", corrupted image not saved");
System.err.println("Good image count = " + saved_image_count);
System.err.println(" Bad image count = " + bad_image_count);
}
return 4;
}
}
return 3;
}
void progressBar()
{
progress_bar++;
print(".");
if (progress_bar >63)
{
progress_bar = 0;
percentage = 0.5 + 100 * pixel_count/(0.001 + tft_width * tft_height);
percent(percentage);
}
}
void percent(float percentage)
{
if (percentage > 100) percentage = 100;
println(" [ " + (int)percentage + "% ]");
textAlign(LEFT);
textSize(16);
noStroke();
fill(bgcolor2);
rect(10, height - 25, 70, 20);
fill(0);
text(" [ " + (int)percentage + "% ]", 10, height-8);
}
boolean fadedImage()
{
int opacity = frameCount - drawLoopCount; // So we get increasing fade
if (fade)
{
tint(255, opacity);
//image(tft_img, x_offset, y_offset);
noStroke();
fill(50, 50, 50, opacity);
rect( (width - tft_width)/2, y_offset, tft_width, tft_height);
delay(10);
}
if (opacity > 50) // End fade after 50 cycles
{
return true;
}
return false;
}
void drawButton(color buttonColor)
{
stroke(0);
fill(buttonColor);
rect(500 - 100, 540 - 26, 80, 24);
textAlign(CENTER);
textSize(20);
fill(0);
if (running) text(" Pause ", 500 - 60, height-7);
else text(" Run ", 500 - 60, height-7);
}
void buttonClicked()
{
mouseClick = false;
if (running) {
running = false;
drawButton(buttonStopped);
System.err.println("");
System.err.println("Stopped - click 'Run' button: ");
//noStroke();
//fill(50);
//rect( (width - tft_width)/2, y_offset, tft_width, tft_height);
beginTime = millis() + 500;
dimmed = false;
state = 4;
} else {
running = true;
drawButton(buttonRunning);
}
}
void mousePressed() {
if (mouseX > (500 - 100) && mouseX < (500 - 20) && mouseY > (540 - 26) && mouseY < (540 - 2)) {
mouseClick = true;
}
}
*/
================================================
FILE: examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/ScreenGrabServer.ino
================================================
// Reads a screen image off the TFT and send it to a processing client sketch
// over the serial port. Use a high baud rate, e.g. for an ESP8266:
// Serial.begin(921600);
// ONLY works if TFT CGRAM can be read back, not all displays support this!
// Tested with ILI9341 display
// At 921600 baud a 320 x 240 image with 16 bit colour transfers can be sent to the
// PC client in ~1.67s and 24 bit colour in ~2.5s which is close to the theoretical
// minimum transfer time.
// This sketch has been created to work with the TFT_eSPI library here:
// https://github.com/Bodmer/TFT_eSPI
// Created by: Bodmer 27/1/17
// Updated by: Bodmer 10/3/17
// Version: 0.07
// MIT licence applies, all text above must be included in derivative works
//====================================================================================
// Definitions
//====================================================================================
#define PIXEL_TIMEOUT 100 // 100ms Time-out between pixel requests
#define START_TIMEOUT 10000 // 10s Maximum time to wait at start transfer
#define BITS_PER_PIXEL 16 // 24 for RGB colour format, 16 for 565 colour format
// File names must be alpha-numeric characters (0-9, a-z, A-Z) or "/" underscore "_"
// other ascii characters are stripped out by client, including / generates
// sub-directories
#define DEFAULT_FILENAME "tft_screenshots/screenshot" // In case none is specified
#define FILE_TYPE "png" // jpg, bmp, png, tif are valid
// Filename extension
// '#' = add 0-9, '@' = add timestamp, '%' add millis() timestamp, '*' = add nothing
// '@' and '%' will generate new unique filenames, so beware of cluttering up your
// hard drive with lots of images! The PC client sketch is set to limit the number of
// saved images to 1000 and will then prompt for a restart.
#define FILE_EXT '%'
// Number of pixels to send in a burst (minimum of 1), no benefit above 8
// NPIXELS values and render times: 1 = 5.0s, 2 = 1.75s, 4 = 1.68s, 8 = 1.67s
#define NPIXELS 8 // Must be integer division of both TFT width and TFT height
boolean screenServer(void);
boolean screenServer(String filename);
boolean serialScreenServer(String filename);
void sendParameters(String filename);
//====================================================================================
// Screen server call with no filename
//====================================================================================
// Start a screen dump server (serial or network) - no filename specified
boolean screenServer(void)
{
// With no filename the screenshot will be saved with a default name e.g. tft_screen_#.xxx
// where # is a number 0-9 and xxx is a file type specified below
return screenServer(DEFAULT_FILENAME);
}
//====================================================================================
// Screen server call with filename
//====================================================================================
// Start a screen dump server (serial or network) - filename specified
boolean screenServer(String filename)
{
boolean result = serialScreenServer(filename); // Screenshot serial port server
//boolean result = wifiScreenServer(filename); // Screenshot WiFi UDP port server (WIP)
delay(0); // Equivalent to yield() for ESP8266;
//Serial.println();
//if (result) Serial.println(F("Screen dump passed :-)"));
//else Serial.println(F("Screen dump failed :-("));
return result;
}
//====================================================================================
// Serial server function that sends the data to the client
//====================================================================================
boolean serialScreenServer(String filename)
{
// Precautionary receive buffer garbage flush for 50ms
uint32_t clearTime = millis() + 50;
while ( millis() < clearTime && Serial.read() >= 0) delay(0); // Equivalent to yield() for ESP8266;
boolean wait = true;
uint32_t lastCmdTime = millis(); // Initialise start of command time-out
// Wait for the starting flag with a start time-out
while (wait)
{
delay(0); // Equivalent to yield() for ESP8266;
// Check serial buffer
if (Serial.available() > 0) {
// Read the command byte
uint8_t cmd = Serial.read();
// If it is 'S' (start command) then clear the serial buffer for 100ms and stop waiting
if ( cmd == 'S' ) {
// Precautionary receive buffer garbage flush for 50ms
clearTime = millis() + 50;
while ( millis() < clearTime && Serial.read() >= 0) delay(0); // Equivalent to yield() for ESP8266;
wait = false; // No need to wait anymore
lastCmdTime = millis(); // Set last received command time
// Send screen size etc using a simple header with delimiters for client checks
sendParameters(filename);
}
}
else
{
// Check for time-out
if ( millis() > lastCmdTime + START_TIMEOUT) return false;
}
}
uint8_t color[3 * NPIXELS]; // RGB and 565 format color buffer for N pixels
// Send all the pixels on the whole screen
for ( uint32_t y = 0; y < tft.height(); y++)
{
// Increment x by NPIXELS as we send NPIXELS for every byte received
for ( uint32_t x = 0; x < tft.width(); x += NPIXELS)
{
delay(0); // Equivalent to yield() for ESP8266;
// Wait here for serial data to arrive or a time-out elapses
while ( Serial.available() == 0 )
{
if ( millis() > lastCmdTime + PIXEL_TIMEOUT) return false;
delay(0); // Equivalent to yield() for ESP8266;
}
// Serial data must be available to get here, read 1 byte and
// respond with N pixels, i.e. N x 3 RGB bytes or N x 2 565 format bytes
if ( Serial.read() == 'X' ) {
// X command byte means abort, so clear the buffer and return
clearTime = millis() + 50;
while ( millis() < clearTime && Serial.read() >= 0) delay(0); // Equivalent to yield() for ESP8266;
return false;
}
// Save arrival time of the read command (for later time-out check)
lastCmdTime = millis();
#if defined BITS_PER_PIXEL && BITS_PER_PIXEL >= 24
// Fetch N RGB pixels from x,y and put in buffer
tft.readRectRGB(x, y, NPIXELS, 1, color);
// Send buffer to client
Serial.write(color, 3 * NPIXELS); // Write all pixels in the buffer
#else
// Fetch N 565 format pixels from x,y and put in buffer
tft.readRect(x, y, NPIXELS, 1, (uint16_t *)color);
// Send buffer to client
Serial.write(color, 2 * NPIXELS); // Write all pixels in the buffer
#endif
}
}
Serial.flush(); // Make sure all pixel bytes have been despatched
return true;
}
//====================================================================================
// Send screen size etc using a simple header with delimiters for client checks
//====================================================================================
void sendParameters(String filename)
{
Serial.write('W'); // Width
Serial.write(tft.width() >> 8);
Serial.write(tft.width() & 0xFF);
Serial.write('H'); // Height
Serial.write(tft.height() >> 8);
Serial.write(tft.height() & 0xFF);
Serial.write('Y'); // Bits per pixel (16 or 24)
Serial.write(BITS_PER_PIXEL);
Serial.write('?'); // Filename next
Serial.print(filename);
Serial.write('.'); // End of filename marker
Serial.write(FILE_EXT); // Filename extension identifier
Serial.write(*FILE_TYPE); // First character defines file type j,b,p,t
}
================================================
FILE: examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/TFT_eSPI_OpenWeather.ino
================================================
// Example from OpenWeather library: https://github.com/Bodmer/OpenWeather
// Adapted by Bodmer to use the TFT_eSPI library: https://github.com/Bodmer/TFT_eSPI
// This sketch is compatible with the ESP8266 and ESP32
// >>> IMPORTANT <<<
// Modify setup in All_Settings.h tab to configure your location etc
// >>> EVEN MORE IMPORTANT TO PREVENT CRASHES <<<
//>>>>>> For ESP8266 set SPIFFS to at least 2Mbytes before uploading files <<<<<<
// ESP8266/ESP32 pin connections to the TFT are defined in the TFT_eSPI library.
// Original by Daniel Eichhorn, see license at end of file.
//#define SERIAL_MESSAGES // For serial output weather reports
//#define SCREEN_SERVER // For dumping screen shots from TFT
//#define RANDOM_LOCATION // Test only, selects random weather location every refresh
//#define FORMAT_SPIFFS // Wipe SPIFFS and all files!
// This sketch uses font files created from the Noto family of fonts as bitmaps
// generated from these fonts may be freely distributed:
// https://www.google.com/get/noto/
// A processing sketch to create new fonts can be found in the Tools folder of TFT_eSPI
// https://github.com/Bodmer/TFT_eSPI/tree/master/Tools/Create_Smooth_Font/Create_font
// New fonts can be generated to include language specific characters. The Noto family
// of fonts has an extensive character set coverage.
// Json streaming parser (do not use IDE library manager version) to use is here:
// https://github.com/Bodmer/JSON_Decoder
#define AA_FONT_SMALL "fonts/NotoSansBold15" // 15 point sans serif bold
#define AA_FONT_LARGE "fonts/NotoSansBold36" // 36 point sans serif bold
/***************************************************************************************
** Load the libraries and settings
***************************************************************************************/
#include <Arduino.h>
#include <SPI.h>
#include <TFT_eSPI.h> // https://github.com/Bodmer/TFT_eSPI
// Additional functions
#include "GfxUi.h" // Attached to this sketch
#include "SPIFFS_Support.h" // Attached to this sketch
#ifdef ESP8266
#include <ESP8266WiFi.h>
#else
#include <WiFi.h>
#endif
// check All_Settings.h for adapting to your needs
#include "All_Settings.h"
#include <JSON_Decoder.h> // https://github.com/Bodmer/JSON_Decoder
#include <OpenWeather.h> // Latest here: https://github.com/Bodmer/OpenWeather
#include "NTP_Time.h" // Attached to this sketch, see that tab for library needs
/***************************************************************************************
** Define the globals and class instances
***************************************************************************************/
TFT_eSPI tft = TFT_eSPI(); // Invoke custom library
OW_Weather ow; // Weather forecast library instance
OW_current *current; // Pointers to structs that temporarily holds weather data
OW_hourly *hourly; // Not used
OW_daily *daily;
boolean booted = true;
GfxUi ui = GfxUi(&tft); // Jpeg and bmpDraw functions TODO: pull outside of a class
long lastDownloadUpdate = millis();
/***************************************************************************************
** Declare prototypes
***************************************************************************************/
void updateData();
void drawProgress(uint8_t percentage, String text);
void drawTime();
void drawCurrentWeather();
void drawForecast();
void drawForecastDetail(uint16_t x, uint16_t y, uint8_t dayIndex);
const char* getMeteoconIcon(uint16_t id, bool today);
void drawAstronomy();
void drawSeparator(uint16_t y);
void fillSegment(int x, int y, int start_angle, int sub_angle, int r, unsigned int colour);
String strDate(time_t unixTime);
String strTime(time_t unixTime);
void printWeather(void);
int leftOffset(String text, String sub);
int rightOffset(String text, String sub);
int splitIndex(String text);
/***************************************************************************************
** Setup
***************************************************************************************/
void setup() {
Serial.begin(250000);
tft.begin();
tft.fillScreen(TFT_BLACK);
SPIFFS.begin();
listFiles();
// Enable if you want to erase SPIFFS, this takes some time!
// then disable and reload sketch to avoid reformatting on every boot!
#ifdef FORMAT_SPIFFS
tft.setTextDatum(BC_DATUM); // Bottom Centre datum
tft.drawString("Formatting SPIFFS, so wait!", 120, 195); SPIFFS.format();
#endif
// Draw splash screen
if (SPIFFS.exists("/splash/OpenWeather.jpg") == true) ui.drawJpeg("/splash/OpenWeather.jpg", 0, 40);
delay(2000);
// Clear bottom section of screen
tft.fillRect(0, 206, 240, 320 - 206, TFT_BLACK);
tft.loadFont(AA_FONT_SMALL);
tft.setTextDatum(BC_DATUM); // Bottom Centre datum
tft.setTextColor(TFT_LIGHTGREY, TFT_BLACK);
tft.drawString("Original by: blog.squix.org", 120, 260);
tft.drawString("Adapted by: Bodmer", 120, 280);
tft.setTextColor(TFT_YELLOW, TFT_BLACK);
delay(2000);
tft.fillRect(0, 206, 240, 320 - 206, TFT_BLACK);
tft.drawString("Connecting to WiFi", 120, 240);
tft.setTextPadding(240); // Pad next drawString() text to full width to over-write old text
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println();
tft.setTextDatum(BC_DATUM);
tft.setTextPadding(240); // Pad next drawString() text to full width to over-write old text
tft.drawString(" ", 120, 220); // Clear line above using set padding width
tft.drawString("Fetching weather data...", 120, 240);
// Fetch the time
udp.begin(localPort);
syncTime();
tft.unloadFont();
ow.partialDataSet(true); // Collect a subset of the data available
}
/***************************************************************************************
** Loop
***************************************************************************************/
void loop() {
// Check if we should update weather information
if (booted || (millis() - lastDownloadUpdate > 1000UL * UPDATE_INTERVAL_SECS))
{
updateData();
lastDownloadUpdate = millis();
}
// If minute has changed then request new time from NTP server
if (booted || minute() != lastMinute)
{
// Update displayed time first as we may have to wait for a response
drawTime();
lastMinute = minute();
// Request and synchronise the local clock
syncTime();
#ifdef SCREEN_SERVER
screenServer();
#endif
}
booted = false;
}
/***************************************************************************************
** Fetch the weather data and update screen
***************************************************************************************/
// Update the Internet based information and update screen
void updateData() {
// booted = true; // Test only
// booted = false; // Test only
if (booted) drawProgress(20, "Updating time...");
else fillSegment(22, 22, 0, (int) (20 * 3.6), 16, TFT_NAVY);
if (booted) drawProgress(50, "Updating conditions...");
else fillSegment(22, 22, 0, (int) (50 * 3.6), 16, TFT_NAVY);
// Create the structures that hold the retrieved weather
current = new OW_current;
daily = new OW_daily;
hourly = new OW_hourly;
#ifdef RANDOM_LOCATION // Randomly choose a place on Earth to test icons etc
String latitude = "";
latitude = (random(180) - 90);
String longitude = "";
longitude = (random(360) - 180);
Serial.print("Lat = "); Serial.print(latitude);
Serial.print(", Lon = "); Serial.println(longitude);
#endif
//On the ESP8266 (only) the library by default uses BearSSL, another option is to use AXTLS
//For problems with ESP8266 stability, use AXTLS by adding a false parameter thus vvvvv
//ow.getForecast(current, hourly, daily, api_key, latitude, longitude, units, language, false);
bool parsed = ow.getForecast(current, hourly, daily, api_key, latitude, longitude, units, language);
if (parsed) Serial.println("Data points received");
else Serial.println("Failed to get data points");
Serial.print("Free heap = "); Serial.println(ESP.getFreeHeap(), DEC);
printWeather(); // For debug, turn on output with #define SERIAL_MESSAGES
if (booted)
{
drawProgress(100, "Done...");
delay(2000);
tft.fillScreen(TFT_BLACK);
}
else
{
fillSegment(22, 22, 0, 360, 16, TFT_NAVY);
fillSegment(22, 22, 0, 360, 22, TFT_BLACK);
}
if (parsed)
{
tft.loadFont(AA_FONT_SMALL);
drawCurrentWeather();
drawForecast();
drawAstronomy();
tft.unloadFont();
// Update the temperature here so we don't need to keep
// loading and unloading font which takes time
tft.loadFont(AA_FONT_LARGE);
tft.setTextDatum(TR_DATUM);
tft.setTextColor(TFT_YELLOW, TFT_BLACK);
// Font ASCII code 0xB0 is a degree symbol, but o used instead in small font
tft.setTextPadding(tft.textWidth(" -88")); // Max width of values
String weatherText = "";
weatherText = String(current->temp, 0); // Make it integer temperature
tft.drawString(weatherText, 215, 95); // + "°" symbol is big... use o in small font
tft.unloadFont();
}
else
{
Serial.println("Failed to get weather");
}
// Delete to free up space
delete current;
delete hourly;
delete daily;
}
/***************************************************************************************
** Update progress bar
***************************************************************************************/
void drawProgress(uint8_t percentage, String text) {
tft.loadFont(AA_FONT_SMALL);
tft.setTextDatum(BC_DATUM);
tft.setTextColor(TFT_ORANGE, TFT_BLACK);
tft.setTextPadding(240);
tft.drawString(text, 120, 260);
ui.drawProgressBar(10, 269, 240 - 20, 15, percentage, TFT_WHITE, TFT_BLUE);
tft.setTextPadding(0);
tft.unloadFont();
}
/***************************************************************************************
** Draw the clock digits
***************************************************************************************/
void drawTime() {
tft.loadFont(AA_FONT_LARGE);
// Convert UTC to local time, returns zone code in tz1_Code, e.g "GMT"
time_t local_time = TIMEZONE.toLocal(now(), &tz1_Code);
String timeNow = "";
if (hour(local_time) < 10) timeNow += "0";
timeNow += hour(local_time);
timeNow += ":";
if (minute(local_time) < 10) timeNow += "0";
timeNow += minute(local_time);
tft.setTextDatum(BC_DATUM);
tft.setTextColor(TFT_YELLOW, TFT_BLACK);
tft.setTextPadding(tft.textWidth(" 44:44 ")); // String width + margin
tft.drawString(timeNow, 120, 53);
drawSeparator(51);
tft.setTextPadding(0);
tft.unloadFont();
}
/***************************************************************************************
** Draw the current weather
***************************************************************************************/
void drawCurrentWeather() {
String date = "Updated: " + strDate(current->dt);
String weatherText = "None";
tft.setTextDatum(BC_DATUM);
tft.setTextColor(TFT_ORANGE, TFT_BLACK);
tft.setTextPadding(tft.textWidth(" Updated: Mmm 44 44:44 ")); // String width + margin
tft.drawString(date, 120, 16);
String weatherIcon = "";
String currentSummary = current->main;
currentSummary.toLowerCase();
weatherIcon = getMeteoconIcon(current->id, true);
//uint32_t dt = millis();
ui.drawBmp("/icon/" + weatherIcon + ".bmp", 0, 53);
//Serial.print("Icon draw time = "); Serial.println(millis()-dt);
// Weather Text
if (language == "en")
weatherText = current->main;
else
weatherText = current->description;
tft.setTextDatum(BR_DATUM);
tft.setTextColor(TFT_ORANGE, TFT_BLACK);
int splitPoint = 0;
int xpos = 235;
splitPoint = splitIndex(weatherText);
tft.setTextPadding(xpos - 100); // xpos - icon width
if (splitPoint) tft.drawString(weatherText.substring(0, splitPoint), xpos, 69);
else tft.drawString(" ", xpos, 69);
tft.drawString(weatherText.substring(splitPoint), xpos, 86);
tft.setTextColor(TFT_YELLOW, TFT_BLACK);
tft.setTextDatum(TR_DATUM);
tft.setTextPadding(0);
if (units == "metric") tft.drawString("oC", 237, 95);
else tft.drawString("oF", 237, 95);
//Temperature large digits added in updateData() to save swapping font here
tft.setTextColor(TFT_ORANGE, TFT_BLACK);
weatherText = String(current->wind_speed, 0);
if (units == "metric") weatherText += " m/s";
else weatherText += " mph";
tft.setTextDatum(TC_DATUM);
tft.setTextPadding(tft.textWidth("888 m/s")); // Max string length?
tft.drawString(weatherText, 124, 136);
if (units == "imperial")
{
weatherText = current->pressure * 0.02953;
weatherText += " in";
}
else
{
weatherText = String(current->pressure, 0);
weatherText += " hPa";
}
tft.setTextDatum(TR_DATUM);
tft.setTextPadding(tft.textWidth(" 8888hPa")); // Max string length?
tft.drawString(weatherText, 230, 136);
int windAngle = (current->wind_deg + 22.5) / 45;
if (windAngle > 7) windAngle = 0;
String wind[] = {"N", "NE", "E", "SE", "S", "SW", "W", "NW" };
ui.drawBmp("/wind/" + wind[windAngle] + ".bmp", 101, 86);
drawSeparator(153);
tft.setTextDatum(TL_DATUM); // Reset datum to normal
tft.setTextPadding(0); // Reset padding width to none
}
/***************************************************************************************
** Draw the 4 forecast columns
***************************************************************************************/
// draws the three forecast columns
void drawForecast() {
int8_t dayIndex = 1;
drawForecastDetail( 8, 171, dayIndex++);
drawForecastDetail( 66, 171, dayIndex++); // was 95
drawForecastDetail(124, 171, dayIndex++); // was 180
drawForecastDetail(182, 171, dayIndex ); // was 180
drawSeparator(171 + 69);
}
/***************************************************************************************
** Draw 1 forecast column at x, y
***************************************************************************************/
// helper for the forecast columns
void drawForecastDetail(uint16_t x, uint16_t y, uint8_t dayIndex) {
if (dayIndex >= MAX_DAYS) return;
String day = shortDOW[weekday(TIMEZONE.toLocal(daily->dt[dayIndex], &tz1_Code))];
day.toUpperCase();
tft.setTextDatum(BC_DATUM);
tft.setTextColor(TFT_ORANGE, TFT_BLACK);
tft.setTextPadding(tft.textWidth("WWW"));
tft.drawString(day, x + 25, y);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextPadding(tft.textWidth("-88 -88"));
String highTemp = String(daily->temp_max[dayIndex], 0);
String lowTemp = String(daily->temp_min[dayIndex], 0);
tft.drawString(highTemp + " " + lowTemp, x + 25, y + 17);
String weatherIcon = getMeteoconIcon(daily->id[dayIndex], false);
ui.drawBmp("/icon50/" + weatherIcon + ".bmp", x, y + 18);
tft.setTextPadding(0); // Reset padding width to none
}
/***************************************************************************************
** Draw Sun rise/set, Moon, cloud cover and humidity
***************************************************************************************/
void drawAstronomy() {
tft.setTextDatum(BC_DATUM);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextPadding(tft.textWidth(" Last qtr "));
time_t local_time = TIMEZONE.toLocal(current->dt, &tz1_Code);
uint16_t y = year(local_time);
uint8_t m = month(local_time);
uint8_t d = day(local_time);
uint8_t h = hour(local_time);
int ip;
uint8_t icon = moon_phase(y, m, d, h, &ip);
tft.drawString(moonPhase[ip], 120, 319);
ui.drawBmp("/moon/moonphase_L" + String(icon) + ".bmp", 120 - 30, 318 - 16 - 60);
tft.setTextDatum(BC_DATUM);
tft.setTextColor(TFT_ORANGE, TFT_BLACK);
tft.setTextPadding(0); // Reset padding width to none
tft.drawString(sunStr, 40, 270);
tft.setTextDatum(BR_DATUM);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextPadding(tft.textWidth(" 88:88 "));
String rising = strTime(current->sunrise) + " ";
int dt = rightOffset(rising, ":"); // Draw relative to colon to them aligned
tft.drawString(rising, 40 + dt, 290);
String setting = strTime(current->sunset) + " ";
dt = rightOffset(setting, ":");
tft.drawString(setting, 40 + dt, 305);
tft.setTextDatum(BC_DATUM);
tft.setTextColor(TFT_ORANGE, TFT_BLACK);
tft.drawString(cloudStr, 195, 260);
String cloudCover = "";
cloudCover += current->clouds;
cloudCover += "%";
tft.setTextDatum(BR_DATUM);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextPadding(tft.textWidth(" 100%"));
tft.drawString(cloudCover, 210, 277);
tft.setTextDatum(BC_DATUM);
tft.setTextColor(TFT_ORANGE, TFT_BLACK);
tft.drawString(humidityStr, 195, 300 - 2);
String humidity = "";
humidity += current->humidity;
humidity += "%";
tft.setTextDatum(BR_DATUM);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextPadding(tft.textWidth("100%"));
tft.drawString(humidity, 210, 315);
tft.setTextPadding(0); // Reset padding width to none
}
/***************************************************************************************
** Get the icon file name from the index number
***************************************************************************************/
const char* getMeteoconIcon(uint16_t id, bool today)
{
if ( today && id/100 == 8 && (current->dt < current->sunrise || current->dt > current->sunset)) id += 1000;
if (id/100 == 2) return "thunderstorm";
if (id/100 == 3) return "drizzle";
if (id/100 == 4) return "unknown";
if (id == 500) return "lightRain";
else if (id == 511) return "sleet";
else if (id/100 == 5) return "rain";
if (id >= 611 && id <= 616) return "sleet";
else if (id/100 == 6) return "snow";
if (id/100 == 7) return "fog";
if (id == 800) return "clear-day";
if (id == 801) return "partly-cloudy-day";
if (id == 802) return "cloudy";
if (id == 803) return "cloudy";
if (id == 804) return "cloudy";
if (id == 1800) return "clear-night";
if (id == 1801) return "partly-cloudy-night";
if (id == 1802) return "cloudy";
if (id == 1803) return "cloudy";
if (id == 1804) return "cloudy";
return "unknown";
}
/***************************************************************************************
** Draw screen section separator line
***************************************************************************************/
// if you don't want separators, comment out the tft-line
void drawSeparator(uint16_t y) {
tft.drawFastHLine(10, y, 240 - 2 * 10, 0x4228);
}
/***************************************************************************************
** Determine place to split a line line
***************************************************************************************/
// determine the "space" split point in a long string
int splitIndex(String text)
{
uint16_t index = 0;
while ( (text.indexOf(' ', index) >= 0) && ( index <= text.length() / 2 ) ) {
index = text.indexOf(' ', index) + 1;
}
if (index) index--;
return index;
}
/***************************************************************************************
** Right side offset to a character
***************************************************************************************/
// Calculate coord delta from end of text String to start of sub String contained within that text
// Can be used to vertically right align text so for example a colon ":" in the time value is always
// plotted at same point on the screen irrespective of different proportional character widths,
// could also be used to align decimal points for neat formatting
int rightOffset(String text, String sub)
{
int index = text.indexOf(sub);
return tft.textWidth(text.substring(index));
}
/***************************************************************************************
** Left side offset to a character
***************************************************************************************/
// Calculate coord delta from start of text String to start of sub String contained within that text
// Can be used to vertically left align text so for example a colon ":" in the time value is always
// plotted at same point on the screen irrespective of different proportional character widths,
// could also be used to align decimal points for neat formatting
int leftOffset(String text, String sub)
{
int index = text.indexOf(sub);
return tft.textWidth(text.substring(0, index));
}
/***************************************************************************************
** Draw circle segment
***************************************************************************************/
// Draw a segment of a circle, centred on x,y with defined start_angle and subtended sub_angle
// Angles are defined in a clockwise direction with 0 at top
// Segment has radius r and it is plotted in defined colour
// Can be used for pie charts etc, in this sketch it is used for wind direction
#define DEG2RAD 0.0174532925 // Degrees to Radians conversion factor
#define INC 2 // Minimum segment subtended angle and plotting angle increment (in degrees)
void fillSegment(int x, int y, int start_angle, int sub_angle, int r, unsigned int colour)
{
// Calculate first pair of coordinates for segment start
float sx = cos((start_angle - 90) * DEG2RAD);
float sy = sin((start_angle - 90) * DEG2RAD);
uint16_t x1 = sx * r + x;
uint16_t y1 = sy * r + y;
// Draw colour blocks every INC degrees
for (int i = start_angle; i < start_angle + sub_angle; i += INC) {
// Calculate pair of coordinates for segment end
int x2 = cos((i + 1 - 90) * DEG2RAD) * r + x;
int y2 = sin((i + 1 - 90) * DEG2RAD) * r + y;
tft.fillTriangle(x1, y1, x2, y2, x, y, colour);
// Copy segment end to segment start for next segment
x1 = x2;
y1 = y2;
}
}
/***************************************************************************************
** Print the weather info to the Serial Monitor
***************************************************************************************/
void printWeather(void)
{
#ifdef SERIAL_MESSAGES
Serial.println("Weather from OpenWeather\n");
Serial.println("############### Current weather ###############\n");
Serial.print("dt (time) : "); Serial.println(strDate(current->dt));
Serial.print("sunrise : "); Serial.println(strDate(current->sunrise));
Serial.print("sunset : "); Serial.println(strDate(current->sunset));
Serial.print("main : "); Serial.println(current->main);
Serial.print("temp : "); Serial.println(current->temp);
Serial.print("humidity : "); Serial.println(current->humidity);
Serial.print("pressure : "); Serial.println(current->pressure);
Serial.print("wind_speed : "); Serial.println(current->wind_speed);
Serial.print("wind_deg : "); Serial.println(current->wind_deg);
Serial.print("clouds : "); Serial.println(current->clouds);
Serial.print("id : "); Serial.println(current->id);
Serial.println();
Serial.println("############### Daily weather ###############\n");
Serial.println();
for (int i = 0; i < 5; i++)
{
Serial.print("dt (time) : "); Serial.println(strDate(daily->dt[i]));
Serial.print("id : "); Serial.println(daily->id[i]);
Serial.print("temp_max : "); Serial.println(daily->temp_max[i]);
Serial.print("temp_min : "); Serial.println(daily->temp_min[i]);
Serial.println();
}
#endif
}
/***************************************************************************************
** Convert Unix time to a "local time" time string "12:34"
***************************************************************************************/
String strTime(time_t unixTime)
{
time_t local_time = TIMEZONE.toLocal(unixTime, &tz1_Code);
String localTime = "";
if (hour(local_time) < 10) localTime += "0";
localTime += hour(local_time);
localTime += ":";
if (minute(local_time) < 10) localTime += "0";
localTime += minute(local_time);
return localTime;
}
/***************************************************************************************
** Convert Unix time to a local date + time string "Oct 16 17:18", ends with newline
***************************************************************************************/
String strDate(time_t unixTime)
{
time_t local_time = TIMEZONE.toLocal(unixTime, &tz1_Code);
String localDate = "";
localDate += monthShortStr(month(local_time));
localDate += " ";
localDate += day(local_time);
localDate += " " + strTime(unixTime);
return localDate;
}
/**The MIT License (MIT)
Copyright (c) 2015 by Daniel Eichhorn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYBR_DATUM HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
See more at http://blog.squix.ch
*/
// Changes made by Bodmer:
// Minor changes to text placement and auto-blanking out old text with background colour padding
// Moon phase text added (not provided by OpenWeather)
// Forecast text lines are automatically split onto two lines at a central space (some are long!)
// Time is printed with colons aligned to tidy display
// Min and max forecast temperatures spaced out
// New smart splash startup screen and updated progress messages
// Display does not need to be blanked between updates
// Icons nudged about slightly to add wind direction + speed
// Barometric pressure added
// Adapted to use the OpenWeather library: https://github.com/Bodmer/OpenWeather
// Moon phase/rise/set (not provided by OpenWeather) replace with and cloud cover humidity
// Created and added new 100x100 and 50x50 pixel weather icons, these are in the
// sketch data folder, press Ctrl+K to view
// Add moon icons, eliminate all downloads of icons (may lose server!)
// Adapted to use anti-aliased fonts, tweaked coords
// Added forecast for 4th day
// Added cloud cover and humidity in lieu of Moon rise/set
// Adapted to be compatible with ESP32
================================================
FILE: examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather_LittleFS/All_Settings.h
================================================
// Use the OpenWeather library: https://github.com/Bodmer/OpenWeather
// The weather icons and fonts are in the sketch data folder, press Ctrl+K
// to view.
// The ESP32 board support package 2.0.0 or later must be loaded in the
// Arduino boards manager to provide LittleFS support.
// >>> IMPORTANT TO PREVENT CRASHES <<<
//>>>>>> Set LittleFS to at least 1.5Mbytes before uploading files <<<<<<
// >>> DON'T FORGET THIS <<<
// Upload the fonts and icons to LittleFS using the "Tools" menu option.
// You can change the number of hours and days for the forecast in the
// "User_Setup.h" file inside the OpenWeather library folder.
// By default this is 6 hours (can be up to 48) and 5 days
// (can be up to 8 days = today plus 7 days). This sketch requires
// at least 5 days of forecast. Forecast hours can be set to 1 as
// the hourly forecast data is not used in this sketch.
//////////////////////////////
// Setttings defined below
#define WIFI_SSID "Your_SSID"
#define WIFI_PASSWORD "Your_password"
#define TIMEZONE UK // See NTP_Time.h tab for other "Zone references", UK, usMT etc
// Update every 15 minutes, up to 1000 request per day are free (viz average of ~40 per hour)
const int UPDATE_INTERVAL_SECS = 15UL * 60UL; // 15 minutes
// Pins for the TFT interface are defined in the User_Config.h file inside the TFT_eSPI library
// For units use "metric" or "imperial"
const String units = "metric";
// Sign up for a key and read API configuration info here:
// https://openweathermap.org/, change x's to your API key
const String api_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Set the forecast longitude and latitude to at least 4 decimal places
const String latitude = "27.9881"; // 90.0000 to -90.0000 negative for Southern hemisphere
const String longitude = "86.9250"; // 180.000 to -180.000 negative for West
// For language codes see https://openweathermap.org/current#multi
const String language = "en"; // Default language = en = English
// Short day of week abbreviations used in 4 day forecast (change to your language)
const String shortDOW [8] = {"???", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"};
// Change the labels to your language here:
const char sunStr[] = "Sun";
const char cloudStr[] = "Cloud";
const char humidityStr[] = "Humidity";
const String moonPhase [8] = {"New", "Waxing", "1st qtr", "Waxing", "Full", "Waning", "Last qtr", "Waning"};
// End of user settings
//////////////////////////////
================================================
FILE: examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather_LittleFS/GfxUi.cpp
================================================
/**The MIT License (MIT)
Copyright (c) 2015 by Daniel Eichhorn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
See more at http://blog.squix.ch
*/
// Adapted by Bodmer to use the TFT_eSPI library: https://github.com/Bodmer/TFT_eSPI
// Functions no longer needed weeded out, Jpeg decoder functions added and updated
// drawBMP() updated to buffer input and output pixels and avoid slow seeks
#include "GfxUi.h"
GfxUi::GfxUi(TFT_eSPI *tft) {
_tft = tft;
}
void GfxUi::drawProgressBar(uint16_t x0, uint16_t y0, uint16_t w, uint16_t h, uint8_t percentage, uint16_t frameColor, uint16_t barColor) {
if (percentage == 0) {
_tft->fillRoundRect(x0, y0, w, h, 3, TFT_BLACK);
}
uint8_t margin = 2;
uint16_t barHeight = h - 2 * margin;
uint16_t barWidth = w - 2 * margin;
_tft->drawRoundRect(x0, y0, w, h, 3, frameColor);
_tft->fillRect(x0 + margin, y0 + margin, barWidth * percentage / 100.0, barHeight, barColor);
}
// Bodmer's streamlined x2 faster "no seek" version
void GfxUi::drawBmp(String filename, uint16_t x, uint16_t y)
{
if ((x >= _tft->width()) || (y >= _tft->height())) return;
fs::File bmpFS;
// Check file exists and open it
// Serial.println(filename);
// Note: ESP32 passes "open" test even if file does not exist, whereas ESP8266 returns NULL
if ( !LittleFS.exists(filename) )
{
Serial.println(F(" File not found")); // Can comment out if not needed
return;
}
// Open requested file
bmpFS = LittleFS.open(filename, "r");
uint32_t seekOffset;
uint16_t w, h, row;
uint8_t r, g, b;
bool oldSwap = false;
if (read16(bmpFS) == 0x4D42)
{
read32(bmpFS);
read32(bmpFS);
seekOffset = read32(bmpFS);
read32(bmpFS);
w = read32(bmpFS);
h = read32(bmpFS);
if ((read16(bmpFS) == 1) && (read16(bmpFS) == 24) && (read32(bmpFS) == 0))
{
y += h - 1;
oldSwap = _tft->getSwapBytes();
_tft->setSwapBytes(true);
bmpFS.seek(seekOffset);
// Calculate padding to avoid seek
uint16_t padding = (4 - ((w * 3) & 3)) & 3;
uint8_t lineBuffer[w * 3 + padding];
for (row = 0; row < h; row++) {
bmpFS.read(lineBuffer, sizeof(lineBuffer));
uint8_t* bptr = lineBuffer;
uint16_t* tptr = (uint16_t*)lineBuffer;
// Convert 24 to 16 bit colours using the same line buffer for results
for (uint16_t col = 0; col < w; col++)
{
b = *bptr++;
g = *bptr++;
r = *bptr++;
*tptr++ = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}
// Push the pixel row to screen, pushImage will crop the line if needed
// y is decremented as the BMP image is drawn bottom up
_tft->pushImage(x, y--, w, 1, (uint16_t*)lineBuffer);
}
}
else Serial.println("BMP format not recognized.");
}
_tft->setSwapBytes(oldSwap);
bmpFS.close();
}
// These read 16- and 32-bit types from the SD card file.
// BMP data is stored little-endian, Arduino is little-endian too.
// May need to reverse subscript order if porting elsewhere.
uint16_t GfxUi::read16(fs::File &f) {
uint16_t result;
((uint8_t *)&result)[0] = f.read(); // LSB
((uint8_t *)&result)[1] = f.read(); // MSB
return result;
}
uint32_t GfxUi::read32(fs::File &f) {
uint32_t result;
((uint8_t *)&result)[0] = f.read(); // LSB
((uint8_t *)&result)[1] = f.read();
((uint8_t *)&result)[2] = f.read();
((uint8_t *)&result)[3] = f.read(); // MSB
return result;
}
//====================================================================================
================================================
FILE: examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather_LittleFS/GfxUi.h
================================================
/**The MIT License (MIT)
Copyright (c) 2015 by Daniel Eichhorn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
See more at http://blog.squix.ch
*/
// Adapted by Bodmer to use the TFT_eSPI library:
// https://github.com/Bodmer/TFT_eSPI
#include <TFT_eSPI.h> // Hardware-specific library
#include <FS.h>
#include <LittleFS.h>
// JPEG decoder library
#include <TJpg_Decoder.h>
#ifndef _GFX_UI_H
#define _GFX_UI_H
// Maximum of 85 for BUFFPIXEL as 3 x this value is stored in an 8 bit variable!
// 32 is an efficient size for LittleFS due to SPI hardware pipeline buffer size
// A larger value of 80 is better for SD cards
#define BUFFPIXEL 32
class GfxUi {
public:
GfxUi(TFT_eSPI * tft);
void drawBmp(String filename, uint16_t x, uint16_t y);
void drawProgressBar(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t percentage, uint16_t frameColor, uint16_t barColor);
private:
TFT_eSPI * _tft;
uint16_t read16(fs::File &f);
uint32_t read32(fs::File &f);
};
#endif
================================================
FILE: examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather_LittleFS/MoonPhase.ino
================================================
/* Moon phase calculation for OpenWeather library*/
// Adapted by Bodmer from code here:
// http://www.voidware.com/moon_phase.htm
#include <stdio.h>
#include <math.h>
#define PI 3.1415926535897932384626433832795
#define RAD (PI/180.0)
#define SMALL_FLOAT (1e-12)
double Julian(int year, int month, double day)
{
int a, b = 0, c, e;
if (month < 3) {
year--;
month += 12;
}
if (year > 1582 || (year == 1582 && month > 10) ||
(year == 1582 && month == 10 && day > 15)) {
a = year / 100;
b = 2 - a + a / 4;
}
c = 365.25 * year;
e = 30.6001 * (month + 1);
return b + c + e + day + 1720994.5;
}
double sun_position(double j)
{
double n, x, e, l, dl, v;
int i;
n = 360 / 365.2422 * j;
i = n / 360;
n = n - i * 360.0;
x = n - 3.762863;
if (x < 0) x += 360;
x *= RAD;
e = x;
do {
dl = e - .016718 * sin(e) - x;
e = e - dl / (1 - .016718 * cos(e));
} while (fabs(dl) >= SMALL_FLOAT);
v = 360 / PI * atan(1.01686011182 * tan(e / 2));
l = v + 282.596403;
i = l / 360;
l = l - i * 360.0;
return l;
}
double moon_position(double j, double ls)
{
double ms, l, mm, n, ev, sms, ae, ec;
int i;
/* ls = sun_position(j) */
ms = 0.985647332099 * j - 3.762863;
if (ms < 0) ms += 360.0;
l = 13.176396 * j + 64.975464;
i = l / 360;
l = l - i * 360.0;
if (l < 0) l += 360.0;
mm = l - 0.1114041 * j - 349.383063;
i = mm / 360;
mm -= i * 360.0;
n = 151.950429 - 0.0529539 * j;
i = n / 360;
n -= i * 360.0;
ev = 1.2739 * sin((2 * (l - ls) - mm) * RAD);
sms = sin(ms * RAD);
ae = 0.1858 * sms;
mm += ev - ae - 0.37 * sms;
ec = 6.2886 * sin(mm * RAD);
l += ev + ec - ae + 0.214 * sin(2 * mm * RAD);
l = 0.6583 * sin(2 * (l - ls) * RAD) + l;
return l;
}
uint8_t moon_phase(int year, int month, int day, double hour, int* ip)
{
double j = Julian(year, month, (double)day + hour / 24.0) - 2444238.5;
double ls = sun_position(j);
double lm = moon_position(j, ls);
double t = lm - ls;
if (t < 0) t += 360;
*ip = (int)((t + 22.5)/45) & 0x7; // Moon state 0-7 for moonPhase[] index
return ((int)((t + 7.5)/15) + 23) % 24; // Moon state 0-23 for icon bitmap
//return 100.0 * ((1.0 - cos((lm - ls) * RAD)) / 2) + 0.5; // percent illuminated
}
================================================
FILE: examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather_LittleFS/NTP_Time.h
================================================
//====================================================================================
// Libraries
//====================================================================================
// Time library:
// https://github.com/PaulStoffregen/Time
#include <Time.h>
// Time zone correction library:
// https://github.com/JChristensen/Timezone
#include <Timezone.h>
// Libraries built into IDE
#ifdef ESP8266
#include <ESP8266WiFi.h>
#else
#include <WiFi.h>
#endif
#include <WiFiUdp.h>
// A UDP instance to let us send and receive packets over UDP
WiFiUDP udp;
//====================================================================================
// Settings
//====================================================================================
#ifdef ESP32 // Temporary fix, ESP8266 fails to communicate with some servers...
// Try to use pool url instead so the server IP address is looked up from those available
// (use a pool server in your own country to improve response time and reliability)
//const char* ntpServerName = "time.nist.gov";
//const char* ntpServerName = "pool.ntp.org";
const char* ntpServerName = "time.google.com";
#else
// Try to use pool url instead so the server IP address is looked up from those available
// (use a pool server in your own country to improve response time and reliability)
// const char* ntpServerName = "time.nist.gov";
const char* ntpServerName = "pool.ntp.org";
//const char* ntpServerName = "time.google.com";
#endif
// Try not to use hard-coded IP addresses which might change, you can if you want though...
//IPAddress timeServerIP(129, 6, 15, 30); // time-c.nist.gov NTP server
//IPAddress timeServerIP(24, 56, 178, 140); // wwv.nist.gov NTP server
IPAddress timeServerIP; // Use server pool
// Example time zone and DST rules, see Timezone library documents to see how
// to add more time zones https://github.com/JChristensen/Timezone
// Zone reference "UK" United Kingdom (London, Belfast)
TimeChangeRule BST = {"BST", Last, Sun, Mar, 1, 60}; //British Summer (Daylight saving) Time
TimeChangeRule GMT = {"GMT", Last, Sun, Oct, 2, 0}; //Standard Time
Timezone UK(BST, GMT);
// Zone reference "euCET" Central European Time (Frankfurt, Paris)
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120}; //Central European Summer Time
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60}; //Central European Standard Time
Timezone euCET(CEST, CET);
// Zone reference "ausET" Australia Eastern Time Zone (Sydney, Melbourne)
TimeChangeRule aEDT = {"AEDT", First, Sun, Oct, 2, 660}; //UTC + 11 hours
TimeChangeRule aEST = {"AEST", First, Sun, Apr, 3, 600}; //UTC + 10 hours
Timezone ausET(aEDT, aEST);
// Zone reference "usET US Eastern Time Zone (New York, Detroit)
TimeChangeRule usEDT = {"EDT", Second, Sun, Mar, 2, -240}; //Eastern Daylight Time = UTC - 4 hours
TimeChangeRule usEST = {"EST", First, Sun, Nov, 2, -300}; //Eastern Standard Time = UTC - 5 hours
Timezone usET(usEDT, usEST);
// Zone reference "usCT" US Central Time Zone (Chicago, Houston)
TimeChangeRule usCDT = {"CDT", Second, dowSunday, Mar, 2, -300};
TimeChangeRule usCST = {"CST", First, dowSunday, Nov, 2, -360};
Timezone usCT(usCDT, usCST);
// Zone reference "usMT" US Mountain Time Zone (Denver, Salt Lake City)
TimeChangeRule usMDT = {"MDT", Second, dowSunday, Mar, 2, -360};
TimeChangeRule usMST = {"MST", First, dowSunday, Nov, 2, -420};
Timezone usMT(usMDT, usMST);
// Zone reference "usAZ" Arizona is US Mountain Time Zone but does not use DST
Timezone usAZ(usMST, usMST);
// Zone reference "usPT" US Pacific Time Zone (Las Vegas, Los Angeles)
TimeChangeRule usPDT = {"PDT", Second, dowSunday, Mar, 2, -420};
TimeChangeRule usPST = {"PST", First, dowSunday, Nov, 2, -480};
Timezone usPT(usPDT, usPST);
//====================================================================================
// Variables
//====================================================================================
TimeChangeRule *tz1_Code; // Pointer to the time change rule, use to get the TZ abbrev, e.g. "GMT"
time_t utc = 0;
bool timeValid = false;
unsigned int localPort = 2390; // local port to listen for UDP packets
const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE ]; //buffer to hold incoming and outgoing packets
uint8_t lastMinute = 0;
uint32_t nextSendTime = 0;
uint32_t newRecvTime = 0;
uint32_t lastRecvTime = 0;
uint32_t newTickTime = 0;
uint32_t lastTickTime = 0;
bool rebooted = 1;
uint32_t no_packet_count = 0;
//====================================================================================
// Function prototype
//====================================================================================
void syncTime(void);
void displayTime(void);
void printTime(time_t zone, char *tzCode);
void decodeNTP(void);
void sendNTPpacket(IPAddress& address);
//====================================================================================
// Update Time
//====================================================================================
void syncTime(void)
{
// Don't send too often so we don't trigger Denial of Service
if (nextSendTime < millis()) {
// Get a random server from the pool
WiFi.hostByName(ntpServerName, timeServerIP);
nextSendTime = millis() + 5000;
// Flush old late packets
while (udp.parsePacket() > 0) { // Is a packet there?
Serial.println("Reading delayed NTP packet."); // Yes
udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
}
sendNTPpacket(timeServerIP); // send an NTP packet to a time server
decodeNTP();
}
}
//====================================================================================
// Send an NTP request to the time server at the given address
//====================================================================================
void sendNTPpacket(IPAddress& address)
{
// Serial.println("sending NTP packet...");
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011; // LI, Version, Mode
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
udp.beginPacket(address, 123); //NTP requests are to port 123
udp.write(packetBuffer, NTP_PACKET_SIZE);
udp.endPacket();
}
//====================================================================================
// Decode the NTP message and print status to serial port
//====================================================================================
void decodeNTP(void)
{
timeValid = false;
uint32_t waitTime = millis() + 500;
while (millis() < waitTime && !timeValid)
{
yield();
if (udp.parsePacket())
{
newRecvTime = millis();
// We've received a packet, read the data from it
udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
Serial.print("\nNTP response time was : ");
Serial.print(500 - (waitTime - newRecvTime));
Serial.println(" ms");
Serial.print("Time since last sync is: ");
Serial.print((newRecvTime - lastRecvTime) / 1000.0);
Serial.println(" s");
lastRecvTime = newRecvTime;
// The timestamp starts at byte 40 of the received packet and is four bytes,
// or two words, long. First, extract the two words:
unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
// Combine the four bytes (two words) into a long integer
// this is NTP time (seconds since Jan 1 1900):
unsigned long secsSince1900 = highWord << 16 | lowWord;
// Now convert NTP Unix time (Seconds since Jan 1 1900) into everyday time:
// UTC time starts on Jan 1 1970. In seconds the difference is 2208988800:
utc = secsSince1900 - 2208988800UL;
setTime(utc); // Set system clock to utc time (not time zone compensated)
timeValid = true;
// Print the hour, minute and second:
Serial.print("Received NTP UTC time : ");
uint8_t hh = hour(utc);
Serial.print(hh); // print the hour (86400 equals secs per day)
Serial.print(':');
uint8_t mm = minute(utc);
if (mm < 10 ) Serial.print('0');
Serial.print(mm); // print the minute (3600 equals secs per minute)
Serial.print(':');
uint8_t ss = second(utc);
if ( ss < 10 ) Serial.print('0');
Serial.println(ss); // print the second
}
}
// Keep a count of missing or bad NTP replies
if ( timeValid ) {
no_packet_count = 0;
}
else
{
Serial.println("\nNo NTP reply, trying again in 1 minute...");
no_packet_count++;
}
if (no_packet_count >= 10) {
no_packet_count = 0;
// TODO: Flag the lack of sync on the display
Serial.println("\nNo NTP packet in last 10 minutes");
}
}
//====================================================================================
// Debug use only
//====================================================================================
void printTime(time_t t, char *tzCode)
{
String dateString = dayStr(weekday(t));
dateString += " ";
dateString += day(t);
if (day(t) == 1 || day(t) == 21 || day(t) == 31) dateString += "st";
else if (day(t) == 2 || day(t) == 22) dateString += "nd";
else if (day(t) == 3 || day(t) == 23) dateString += "rd";
else dateString += "th";
dateString += " ";
dateString += monthStr(month(t));
dateString += " ";
dateString += year(t);
// Print time to serial port
Serial.print(hour(t));
Serial.print(":");
Serial.print(minute(t));
Serial.print(":");
Serial.print(second(t));
Serial.print(" ");
// Print time t
Serial.print(tzCode);
Serial.print(" ");
// Print date
Serial.print(day(t));
Serial.print("/");
Serial.print(month(t));
Serial.print("/");
Serial.print(year(t));
Serial.print(" ");
// Now test some other functions that might be useful one day!
Serial.print(dayStr(weekday(t)));
Serial.print(" ");
Serial.print(monthStr(month(t)));
Serial.print(" ");
Serial.print(dayShortStr(weekday(t)));
Serial.print(" ");
Serial.print(monthShortStr(month(t)));
Serial.println();
}
//====================================================================================
================================================
FILE: examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather_LittleFS/ScreenGrabClient.ino
================================================
// This is a copy of the processing sketch that can be used to capture the images
// Not needed by this sketch, used during development with screenSaver() functions.
// Copy the sketch below into the Processing IDE and remove the /* and */ at the beginning and
// end.
// The sketch runs in Processing version 3.3 (or later) on a PC, it can be downloaded here:
// https://processing.org/download/
/*
// This is a Processing sketch, see https://processing.org/ to download the IDE
// The sketch is a client that requests TFT screen-shots from an Arduino board.
// The Arduino must call a screen-shot server function to respond with pixels.
// It has been created to work with the TFT_eSPI library here:
// https://github.com/Bodmer/TFT_eSPI
// The sketch must only be run when the designated serial port is available and enumerated
// otherwise the screen-shot window may freeze and that process will need to be terminated
// This is a limitation of the Processing environment and not the sketch.
// If anyone knows how to determine if a serial port is available at start up the PM me
// on (Bodmer) the Arduino forum.
// The block below contains variables that the user may need to change for a particular setup
// As a minimum set the serial port and baud rate must be defined. The capture window is
// automatically resized for landscape, portrait and different TFT resolutions.
// Captured images are stored in the sketch folder, use the Processing IDE "Sketch" menu
// option "Show Sketch Folder" or press Ctrl+K
// Created by: Bodmer 5/3/17
// Updated by: Bodmer 12/3/17
// Version: 0.07
// MIT licence applies, all text above must be included in derivative works
// ###########################################################################################
// # These are the values to change for a particular setup #
// #
int serial_port = 0; // Use enumerated value from list provided when sketch is run #
// #
// On an Arduino Due Programming Port use a baud rate of:115200) #
// On an Arduino Due Native USB Port use a baud rate of any value #
int serial_baud_rate = 250000; // #
// #
// Change the image file type saved here, comment out all but one #
//String image_type = ".jpg"; // #
String image_type = ".png"; // Lossless compression #
//String image_type = ".bmp"; // #
//String image_type = ".tif"; // #
// #
boolean save_border = true; // Save the image with a border #
int border = 5; // Border pixel width #
boolean fade = false; // Fade out image after saving #
// #
int max_images = 100; // Maximum of numbered file images before over-writing files #
// #
int max_allowed = 1000; // Maximum number of save images allowed before a restart #
// #
// # End of the values to change for a particular setup #
// ###########################################################################################
// These are default values, this sketch obtains the actual values from the Arduino board
int tft_width = 480; // default TFT width (automatic - sent by Arduino)
int tft_height = 480; // default TFT height (automatic - sent by Arduino)
int color_bytes = 2; // 2 for 16 bit, 3 for three RGB bytes (automatic - sent by Arduino)
import processing.serial.*;
Serial serial; // Create an instance called serial
int serialCount = 0; // Count of colour bytes arriving
// Stage window graded background colours
color bgcolor1 = color(0, 100, 104); // Arduino IDE style background color 1
color bgcolor2 = color(77, 183, 187); // Arduino IDE style background color 2
//color bgcolor2 = color(255, 255, 255); // White
// TFT image frame greyscale value (dark grey)
color frameColor = 42;
color buttonStopped = color(255, 0, 0);
color buttonRunning = color(128, 204, 206);
color buttonDimmed = color(180, 0, 0);
boolean dimmed = false;
boolean running = true;
boolean mouseClick = false;
int[] rgb = new int[3]; // Buffer for the colour bytes
int indexRed = 0; // Colour byte index in the array
int indexGreen = 1;
int indexBlue = 2;
int n = 0;
int x_offset = (500 - tft_width) /2; // Image offsets in the window
int y_offset = 20;
int xpos = 0, ypos = 0; // Current pixel position
int beginTime = 0;
int pixelWaitTime = 1000; // Maximum 1000ms wait for image pixels to arrive
int lastPixelTime = 0; // Time that "image send" command was sent
int requestTime = 0;
int requestCount = 0;
int state = 0; // State machine current state
int progress_bar = 0; // Console progress bar dot count
int pixel_count = 0; // Number of pixels read for 1 screen
float percentage = 0; // Percentage of pixels received
int saved_image_count = 0; // Stats - number of images processed
int bad_image_count = 0; // Stats - number of images that had lost pixels
String filename = "";
int drawLoopCount = 0; // Used for the fade out
void setup() {
size(500, 540); // Stage size, can handle 480 pixels wide screen
noStroke(); // No border on the next thing drawn
noSmooth(); // No anti-aliasing to avoid adjacent pixel colour merging
// Graded background and title
drawWindow();
frameRate(2000); // High frame rate so draw() loops fast
// Print a list of the available serial ports
println("-----------------------");
println("Available Serial Ports:");
println("-----------------------");
printArray(Serial.list());
println("-----------------------");
print("Port currently used: [");
print(serial_port);
println("]");
String portName = Serial.list()[serial_port];
serial = new Serial(this, portName, serial_baud_rate);
state = 99;
}
void draw() {
if (mouseClick) buttonClicked();
switch(state) {
case 0: // Init varaibles, send start request
if (running) {
tint(0, 0, 0, 255);
flushBuffer();
println("");
print("Ready: ");
xpos = 0;
ypos = 0;
serialCount = 0;
progress_bar = 0;
pixel_count = 0;
percentage = 0;
drawLoopCount = frameCount;
lastPixelTime = millis() + 1000;
state = 1;
} else {
if (millis() > beginTime) {
beginTime = millis() + 500;
dimmed = !dimmed;
if (dimmed) drawButton(buttonDimmed);
else drawButton(buttonStopped);
}
}
break;
case 1: // Console message, give server some time
print("requesting image ");
serial.write("S");
delay(10);
beginTime = millis();
requestTime = millis() + 1000;
requestCount = 1;
state = 2;
break;
case 2: // Get size and set start time for rendering duration report
if (millis() > requestTime) {
requestCount++;
print("*");
serial.clear();
serial.write("S");
if (requestCount > 32) {
requestCount = 0;
System.err.println(" - no response!");
state = 0;
}
requestTime = millis() + 1000;
}
if ( getSize() == true ) { // Go to next state when we have the size and bits per pixel
getFilename();
flushBuffer(); // Precaution in case image header size increases in later versions
lastPixelTime = millis() + 1000;
beginTime = millis();
state = 3;
}
break;
case 3: // Request pixels and render returned RGB values
state = renderPixels(); // State will change when all pixels are rendered
// Request more pixels, changing the number requested allows the average transfer rate to
// be controlled. The pixel transfer rate is dependant on four things:
// 1. The frame rate defined in this Processing sketch in setup()
// 2. The baud rate of the serial link (~10 bit periods per byte)
// 3. The number of request bytes 'R' sent in the lines below
// 4. The number of pixels sent in a burst by the server sketch (defined via NPIXELS)
//serial.write("RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR"); // 32 x NPIXELS more
serial.write("RRRRRRRRRRRRRRRR"); // 16 x NPIXELS more
//serial.write("RRRRRRRR"); // 8 x NPIXELS more
//serial.write("RRRR"); // 4 x NPIXELS more
//serial.write("RR"); // 2 x NPIXELS more
//serial.write("R"); // 1 x NPIXELS more
if (!running) state = 4;
break;
case 4: // Pixel receive time-out, flush serial buffer
flushBuffer();
state = 6;
break;
case 5: // Save the image to the sketch folder (Ctrl+K to access)
saveScreenshot();
saved_image_count++;
println("Saved image count = " + saved_image_count);
if (bad_image_count > 0) System.err.println(" Bad image count = " + bad_image_count);
drawLoopCount = frameCount; // Reset value ready for counting in step 6
state = 6;
break;
case 6: // Fade the old image if enabled
if ( fadedImage() == true ) state = 0; // Go to next state when image has faded
break;
case 99: // Draw image viewer window
drawWindow();
delay(50); // Delay here seems to be required for the IDE console to get ready
state = 0;
break;
default:
println("");
System.err.println("Error state reached - check sketch!");
break;
}
}
void drawWindow()
{
// Graded background in Arduino colours
for (int i = 0; i < height - 25; i++) {
float inter = map(i, 0, height - 25, 0, 1);
color c = lerpColor(bgcolor1, bgcolor2, inter);
stroke(c);
line(0, i, 500, i);
}
fill(bgcolor2);
rect( 0, height-25, width-1, 24);
textAlign(CENTER);
textSize(20);
fill(0);
text("Bodmer's TFT image viewer", width/2, height-6);
if (running) drawButton(buttonRunning);
else drawButton(buttonStopped);
}
void flushBuffer()
{
//println("Clearing serial pipe after a time-out");
int clearTime = millis() + 50;
while ( millis() < clearTime ) serial.clear();
}
boolean getSize()
{
if ( serial.available() > 6 ) {
println();
char code = (char)serial.read();
if (code == 'W') {
tft_width = serial.read()<<8 | serial.read();
}
code = (char)serial.read();
if (code == 'H') {
tft_height = serial.read()<<8 | serial.read();
}
code = (char)serial.read();
if (code == 'Y') {
int bits_per_pixel = (char)serial.read();
if (bits_per_pixel == 24) color_bytes = 3;
else color_bytes = 2;
}
code = (char)serial.read();
if (code == '?') {
drawWindow();
x_offset = (500 - tft_width) /2;
tint(0, 0, 0, 255);
noStroke();
fill(frameColor);
rect((width - tft_width)/2 - border, y_offset - border, tft_width + 2 * border, tft_height + 2 * border);
return true;
}
}
return false;
}
void saveScreenshot()
{
println();
if (saved_image_count < max_allowed)
{
if (filename == "") filename = "tft_screen_" + (n++);
filename = filename + image_type;
println("Saving image as \"" + filename + "\"");
if (save_border)
{
PImage partialSave = get(x_offset - border, y_offset - border, tft_width + 2*border, tft_height + 2*border);
partialSave.save(filename);
} else {
PImage partialSave = get(x_offset, y_offset, tft_width, tft_height);
partialSave.save(filename);
}
if (n>=max_images) n = 0;
}
else
{
System.err.println(max_allowed + " saved image count exceeded, restart the sketch");
}
}
void getFilename()
{
int readTime = millis() + 20;
int inByte = 0;
filename = "";
while ( serial.available() > 0 && millis() < readTime && inByte != '.')
{
inByte = serial.read();
if (inByte == ' ') inByte = '_';
if ( unicodeCheck(inByte) ) filename += (char)inByte;
}
inByte = serial.read();
if (inByte == '@') filename += "_" + timeCode();
else if (inByte == '#') filename += "_" + saved_image_count%100;
else if (inByte == '%') filename += "_" + millis();
else if (inByte != '*') filename = "";
inByte = serial.read();
if (inByte == 'j') image_type =".jpg";
else if (inByte == 'b') image_type =".bmp";
else if (inByte == 'p') image_type =".png";
else if (inByte == 't') image_type =".tif";
}
boolean unicodeCheck(int unicode)
{
if ( unicode >= '0' && unicode <= '9' ) return true;
if ( (unicode >= 'A' && unicode <= 'Z' ) || (unicode >= 'a' && unicode <= 'z')) return true;
if ( unicode == '_' || unicode == '/' ) return true;
return false;
}
String timeCode()
{
String timeCode = (int)year() + "_" + (int)month() + "_" + (int)day() + "_";
timeCode += (int)hour() + "_" + (int)minute() + "_" + (int)second();
return timeCode;
}
int renderPixels()
{
if ( serial.available() > 0 ) {
// Add the latest byte from the serial port to array:
while (serial.available()>0)
{
rgb[serialCount++] = serial.read();
// If we have 3 colour bytes:
if ( serialCount >= color_bytes ) {
serialCount = 0;
pixel_count++;
if (color_bytes == 3)
{
stroke(rgb[indexRed], rgb[indexGreen], rgb[indexBlue], 1000);
} else
{ // Can cater for various byte orders
//stroke( (rgb[0] & 0x1F)<<3, (rgb[0] & 0xE0)>>3 | (rgb[1] & 0x07)<<5, (rgb[1] & 0xF8));
//stroke( (rgb[1] & 0x1F)<<3, (rgb[1] & 0xE0)>>3 | (rgb[0] & 0x07)<<5, (rgb[0] & 0xF8));
stroke( (rgb[0] & 0xF8), (rgb[1] & 0xE0)>>3 | (rgb[0] & 0x07)<<5, (rgb[1] & 0x1F)<<3);
//stroke( (rgb[1] & 0xF8), (rgb[0] & 0xE0)>>3 | (rgb[1] & 0x07)<<5, (rgb[0] & 0x1F)<<3);
}
// We get some pixel merge aliasing if smooth() is defined, so draw pixel twice
point(xpos + x_offset, ypos + y_offset);
//point(xpos + x_offset, ypos + y_offset);
lastPixelTime = millis();
xpos++;
if (xpos >= tft_width) {
xpos = 0;
progressBar();
ypos++;
if (ypos>=tft_height) {
ypos = 0;
if ((int)percentage <100) {
while (progress_bar++ < 64) print(" ");
percent(100);
}
println("Image fetch time = " + (millis()-beginTime)/1000.0 + " s");
return 5;
}
}
}
}
} else
{
if (millis() > (lastPixelTime + pixelWaitTime))
{
println("");
System.err.println(pixelWaitTime + "ms time-out for pixels exceeded...");
if (pixel_count > 0) {
bad_image_count++;
System.err.print("Pixels missing = " + (tft_width * tft_height - pixel_count));
System.err.println(", corrupted image not saved");
System.err.println("Good image count = " + saved_image_count);
System.err.println(" Bad image count = " + bad_image_count);
}
return 4;
}
}
return 3;
}
void progressBar()
{
progress_bar++;
print(".");
if (progress_bar >63)
{
progress_bar = 0;
percentage = 0.5 + 100 * pixel_count/(0.001 + tft_width * tft_height);
percent(percentage);
}
}
void percent(float percentage)
{
if (percentage > 100) percentage = 100;
println(" [ " + (int)percentage + "% ]");
textAlign(LEFT);
textSize(16);
noStroke();
fill(bgcolor2);
rect(10, height - 25, 70, 20);
fill(0);
text(" [ " + (int)percentage + "% ]", 10, height-8);
}
boolean fadedImage()
{
int opacity = frameCount - drawLoopCount; // So we get increasing fade
if (fade)
{
tint(255, opacity);
//image(tft_img, x_offset, y_offset);
noStroke();
fill(50, 50, 50, opacity);
rect( (width - tft_width)/2, y_offset, tft_width, tft_height);
delay(10);
}
if (opacity > 50) // End fade after 50 cycles
{
return true;
}
return false;
}
void drawButton(color buttonColor)
{
stroke(0);
fill(buttonColor);
rect(500 - 100, 540 - 26, 80, 24);
textAlign(CENTER);
textSize(20);
fill(0);
if (running) text(" Pause ", 500 - 60, height-7);
else text(" Run ", 500 - 60, height-7);
}
void buttonClicked()
{
mouseClick = false;
if (running) {
running = false;
drawButton(buttonStopped);
System.err.println("");
System.err.println("Stopped - click 'Run' button: ");
//noStroke();
//fill(50);
//rect( (width - tft_width)/2, y_offset, tft_width, tft_height);
beginTime = millis() + 500;
dimmed = false;
state = 4;
} else {
running = true;
drawButton(buttonRunning);
}
}
void mousePressed() {
if (mouseX > (500 - 100) && mouseX < (500 - 20) && mouseY > (540 - 26) && mouseY < (540 - 2)) {
mouseClick = true;
}
}
*/
================================================
FILE: examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather_LittleFS/ScreenGrabServer.ino
================================================
// Reads a screen image off the TFT and send it to a processing client sketch
// over the serial port. Use a high baud rate, e.g. for an ESP8266:
// Serial.begin(921600);
// ONLY works if TFT CGRAM can be read back, not all displays support this!
// Tested with ILI9341 display
// At 921600 baud a 320 x 240 image with 16 bit colour transfers can be sent to the
// PC client in ~1.67s and 24 bit colour in ~2.5s which is close to the theoretical
// minimum transfer time.
// This sketch has been created to work with the TFT_eSPI library here:
// https://github.com/Bodmer/TFT_eSPI
// Created by: Bodmer 27/1/17
// Updated by: Bodmer 10/3/17
// Version: 0.07
// MIT licence applies, all text above must be included in derivative works
//====================================================================================
// Definitions
//====================================================================================
#define PIXEL_TIMEOUT 100 // 100ms Time-out between pixel requests
#define START_TIMEOUT 10000 // 10s Maximum time to wait at start transfer
#define BITS_PER_PIXEL 16 // 24 for RGB colour format, 16 for 565 colour format
// File names must be alpha-numeric characters (0-9, a-z, A-Z) or "/" underscore "_"
// other ascii characters are stripped out by client, including / generates
// sub-directories
#define DEFAULT_FILENAME "tft_screenshots/screenshot" // In case none is specified
#define FILE_TYPE "png" // jpg, bmp, png, tif are valid
// Filename extension
// '#' = add 0-9, '@' = add timestamp, '%' add millis() timestamp, '*' = add nothing
// '@' and '%' will generate new unique filenames, so beware of cluttering up your
// hard drive with lots of images! The PC client sketch is set to limit the number of
// saved images to 1000 and will then prompt for a restart.
#define FILE_EXT '%'
// Number of pixels to send in a burst (minimum of 1), no benefit above 8
// NPIXELS values and render times: 1 = 5.0s, 2 = 1.75s, 4 = 1.68s, 8 = 1.67s
#define NPIXELS 8 // Must be integer division of both TFT width and TFT height
boolean screenServer(void);
boolean screenServer(String filename);
boolean serialScreenServer(String filename);
void sendParameters(String filename);
//====================================================================================
// Screen server call with no filename
//====================================================================================
// Start a screen dump server (serial or network) - no filename specified
boolean screenServer(void)
{
// With no filename the screenshot will be saved with a default name e.g. tft_screen_#.xxx
// where # is a number 0-9 and xxx is a file type specified below
return screenServer(DEFAULT_FILENAME);
}
//====================================================================================
// Screen server call with filename
//====================================================================================
// Start a screen dump server (serial or network) - filename specified
boolean screenServer(String filename)
{
boolean result = serialScreenServer(filename); // Screenshot serial port server
//boolean result = wifiScreenServer(filename); // Screenshot WiFi UDP port server (WIP)
delay(0); // Equivalent to yield() for ESP8266;
//Serial.println();
//if (result) Serial.println(F("Screen dump passed :-)"));
//else Serial.println(F("Screen dump failed :-("));
return result;
}
//====================================================================================
// Serial server function that sends the data to the client
//====================================================================================
boolean serialScreenServer(String filename)
{
// Precautionary receive buffer garbage flush for 50ms
uint32_t clearTime = millis() + 50;
while ( millis() < clearTime && Serial.read() >= 0) delay(0); // Equivalent to yield() for ESP8266;
boolean wait = true;
uint32_t lastCmdTime = millis(); // Initialise start of command time-out
// Wait for the starting flag with a start time-out
while (wait)
{
delay(0); // Equivalent to yield() for ESP8266;
// Check serial buffer
if (Serial.available() > 0) {
// Read the command byte
uint8_t cmd = Serial.read();
// If it is 'S' (start command) then clear the serial buffer for 100ms and stop waiting
if ( cmd == 'S' ) {
// Precautionary receive buffer garbage flush for 50ms
clearTime = millis() + 50;
while ( millis() < clearTime && Serial.read() >= 0) delay(0); // Equivalent to yield() for ESP8266;
wait = false; // No need to wait anymore
lastCmdTime = millis(); // Set last received command time
// Send screen size etc using a simple header with delimiters for client checks
sendParameters(filename);
}
}
else
{
// Check for time-out
if ( millis() > lastCmdTime + START_TIMEOUT) return false;
}
}
uint8_t color[3 * NPIXELS]; // RGB and 565 format color buffer for N pixels
// Send all the pixels on the whole screen
for ( int32_t y = 0; y < tft.height(); y++)
{
// Increment x by NPIXELS as we send NPIXELS for every byte received
for ( int32_t x = 0; x < tft.width(); x += NPIXELS)
{
delay(0); // Equivalent to yield() for ESP8266;
// Wait here for serial data to arrive or a time-out elapses
while ( Serial.available() == 0 )
{
if ( millis() > lastCmdTime + PIXEL_TIMEOUT) return false;
delay(0); // Equivalent to yield() for ESP8266;
}
// Serial data must be available to get here, read 1 byte and
// respond with N pixels, i.e. N x 3 RGB bytes or N x 2 565 format bytes
if ( Serial.read() == 'X' ) {
// X command byte means abort, so clear the buffer and return
clearTime = millis() + 50;
while ( millis() < clearTime && Serial.read() >= 0) delay(0); // Equivalent to yield() for ESP8266;
return false;
}
// Save arrival time of the read command (for later time-out check)
lastCmdTime = millis();
#if defined BITS_PER_PIXEL && BITS_PER_PIXEL >= 24
// Fetch N RGB pixels from x,y and put in buffer
tft.readRectRGB(x, y, NPIXELS, 1, color);
// Send buffer to client
Serial.write(color, 3 * NPIXELS); // Write all pixels in the buffer
#else
// Fetch N 565 format pixels from x,y and put in buffer
tft.readRect(x, y, NPIXELS, 1, (uint16_t *)color);
// Send buffer to client
Serial.write(color, 2 * NPIXELS); // Write all pixels in the buffer
#endif
}
}
Serial.flush(); // Make sure all pixel bytes have been despatched
return true;
}
//====================================================================================
// Send screen size etc using a simple header with delimiters for client checks
//====================================================================================
void sendParameters(String filename)
{
Serial.write('W'); // Width
Serial.write(tft.width() >> 8);
Serial.write(tft.width() & 0xFF);
Serial.write('H'); // Height
Serial.write(tft.height() >> 8);
Serial.write(tft.height() & 0xFF);
Serial.write('Y'); // Bits per pixel (16 or 24)
Serial.write(BITS_PER_PIXEL);
Serial.write('?'); // Filename next
Serial.print(filename);
Serial.write('.'); // End of filename marker
Serial.write(FILE_EXT); // Filename extension identifier
Serial.write(*FILE_TYPE); // First character defines file type j,b,p,t
}
================================================
FILE: examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather_LittleFS/TFT_eSPI_OpenWeather_LittleFS.ino
================================================
// Example from OpenWeather library: https://github.com/Bodmer/OpenWeather
// Adapted by Bodmer to use the TFT_eSPI library: https://github.com/Bodmer/TFT_eSPI
// This sketch is compatible with the Pico W, RP2040 Nano Connect, ESP32 and ESP32 S2, it may
// also work on ESP8266 but this has not been tested.
// >>> IMPORTANT <<<
// Modify setup in All_Settings.h tab to configure your location etc
// >>> EVEN MORE IMPORTANT TO PREVENT CRASHES <<<
//>>>>>> For ESP8266 set LittleFS to at least 2Mbytes before uploading files <<<<<<
// ESP8266/ESP32 pin connections to the TFT are defined in the TFT_eSPI library.
// Original by Daniel Eichhorn, see license at end of file.
//#define SERIAL_MESSAGES // For serial output weather reports
//#define SCREEN_SERVER // For dumping screen shots from TFT
//#define RANDOM_LOCATION // Test only, selects random weather location every refresh
//#define FORMAT_LittleFS // Wipe LittleFS and all files!
// This sketch uses font files created from the Noto family of fonts as bitmaps
// generated from these fonts may be freely distributed:
// https://www.google.com/get/noto/
// A processing sketch to create new fonts can be found in the Tools folder of TFT_eSPI
// https://github.com/Bodmer/TFT_eSPI/tree/master/Tools/Create_Smooth_Font/Create_font
// New fonts can be generated to include language specific characters. The Noto family
// of fonts has an extensive character set coverage.
// Json streaming parser (do not use IDE library manager version) to use is here:
// https://github.com/Bodmer/JSON_Decoder
#include <FS.h>
#include <LittleFS.h>
#define AA_FONT_SMALL "fonts/NSBold15" // 15 point Noto sans serif bold
#define AA_FONT_LARGE "fonts/NSBold36" // 36 point Noto sans serif bold
/***************************************************************************************
** Load the libraries and settings
***************************************************************************************/
#include <Arduino.h>
#include <SPI.h>
#include <TFT_eSPI.h> // https://github.com/Bodmer/TFT_eSPI
// Additional functions
#include "GfxUi.h" // Attached to this sketch
// Choose library to load
#ifdef ESP8266
#include <ESP8266WiFi.h>
#elif defined(ARDUINO_ARCH_MBED) || defined(ARDUINO_ARCH_RP2040)
#if defined(ARDUINO_RASPBERRY_PI_PICO_W)
#include <WiFi.h>
#else
#include <WiFiNINA.h>
#endif
#else // ESP32
#include <WiFi.h>
#endif
// check All_Settings.h for adapting to your needs
#include "All_Settings.h"
#include <JSON_Decoder.h> // https://github.com/Bodmer/JSON_Decoder
#include <OpenWeather.h> // Latest here: https://github.com/Bodmer/OpenWeather
#include "NTP_Time.h" // Attached to this sketch, see that tab for library needs
/***************************************************************************************
** Define the globals and class instances
***************************************************************************************/
TFT_eSPI tft = TFT_eSPI(); // Invoke custom library
OW_Weather ow; // Weather forecast library instance
OW_current *current; // Pointers to structs that temporarily holds weather data
OW_hourly *hourly; // Not used
OW_daily *daily;
boolean booted = true;
GfxUi ui = GfxUi(&tft); // Jpeg and bmpDraw functions TODO: pull outside of a class
long lastDownloadUpdate = millis();
/***************************************************************************************
** Declare prototypes
***************************************************************************************/
void updateData();
void drawProgress(uint8_t percentage, String text);
void drawTime();
void drawCurrentWeather();
void drawForecast();
void drawForecastDetail(uint16_t x, uint16_t y, uint8_t dayIndex);
const char* getMeteoconIcon(uint16_t id, bool today);
void drawAstronomy();
void drawSeparator(uint16_t y);
void fillSegment(int x, int y, int start_angle, int sub_angle, int r, unsigned int colour);
String strDate(time_t unixTime);
String strTime(time_t unixTime);
void printWeather(void);
int leftOffset(String text, String sub);
int rightOffset(String text, String sub);
int splitIndex(String text);
bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t* bitmap)
{
// Stop further decoding as image is running off bottom of screen
if ( y >= tft.height() ) return 0;
// This function will clip the image block rendering automatically at the TFT boundaries
tft.pushImage(x, y, w, h, bitmap);
// Return 1 to decode next block
return 1;
}
/***************************************************************************************
** Setup
***************************************************************************************/
void setup() {
Serial.begin(250000);
tft.begin();
tft.setRotation(0); // For 320x480 screen
tft.fillScreen(TFT_BLACK);
if (!LittleFS.begin()) {
Serial.println("Flash FS initialisation failed!");
while (1) yield(); // Stay here twiddling thumbs waiting
}
Serial.println("\nFlash FS available!");
// Enable if you want to erase LittleFS, this takes some time!
// then disable and reload sketch to avoid reformatting on every boot!
#ifdef FORMAT_LittleFS
tft.setTextDatum(BC_DATUM); // Bottom Centre datum
tft.drawString("Formatting LittleFS, so wait!", 120, 195); LittleFS.format();
#endif
TJpgDec.setJpgScale(1);
TJpgDec.setCallback(tft_output);
TJpgDec.setSwapBytes(true); // May need to swap the jpg colour bytes (endianess)
// Draw splash screen
if (LittleFS.exists("/splash/OpenWeather.jpg") == true) {
TJpgDec.drawFsJpg(0, 40, "/splash/OpenWeather.jpg", LittleFS);
}
delay(2000);
// Clear bottom section of screen
tft.fillRect(0, 206, 240, 320 - 206, TFT_BLACK);
tft.loadFont(AA_FONT_SMALL, LittleFS);
tft.setTextDatum(BC_DATUM); // Bottom Centre datum
tft.setTextColor(TFT_LIGHTGREY, TFT_BLACK);
tft.draw
gitextract_ry32umis/ ├── .gitattributes ├── Data_Point_Set.h ├── OpenWeather.cpp ├── OpenWeather.h ├── README.md ├── User_Setup.h ├── examples/ │ ├── Onecall API (subscription required)/ │ │ ├── My_OpenWeather_Test/ │ │ │ ├── My_OpenWeather_Test.ino │ │ │ └── Notes.ino │ │ ├── TFT_eSPI_OpenWeather/ │ │ │ ├── All_Settings.h │ │ │ ├── GfxUi.cpp │ │ │ ├── GfxUi.h │ │ │ ├── MoonPhase.ino │ │ │ ├── NTP_Time.h │ │ │ ├── SPIFFS_Support.h │ │ │ ├── ScreenGrabClient.ino │ │ │ ├── ScreenGrabServer.ino │ │ │ ├── TFT_eSPI_OpenWeather.ino │ │ │ └── data/ │ │ │ └── fonts/ │ │ │ ├── NotoSansBold15.vlw │ │ │ └── NotoSansBold36.vlw │ │ └── TFT_eSPI_OpenWeather_LittleFS/ │ │ ├── All_Settings.h │ │ ├── GfxUi.cpp │ │ ├── GfxUi.h │ │ ├── MoonPhase.ino │ │ ├── NTP_Time.h │ │ ├── ScreenGrabClient.ino │ │ ├── ScreenGrabServer.ino │ │ ├── TFT_eSPI_OpenWeather_LittleFS.ino │ │ └── data/ │ │ └── fonts/ │ │ ├── NSBold15.vlw │ │ └── NSBold36.vlw │ ├── OpenWeather_Forecast_Test/ │ │ ├── Notes.ino │ │ └── OpenWeather_Forecast_Test.ino │ └── TFT_eSPI_OpenWeather_LittleFS/ │ ├── All_Settings.h │ ├── GfxUi.cpp │ ├── GfxUi.h │ ├── MoonPhase.ino │ ├── NTP_Time.h │ ├── ScreenGrabClient.ino │ ├── ScreenGrabServer.ino │ ├── TFT_eSPI_OpenWeather_LittleFS.ino │ └── data/ │ └── fonts/ │ ├── NSBold15.vlw │ └── NSBold36.vlw ├── keywords.txt ├── library.json ├── library.properties └── license.txt
SYMBOL INDEX (22 symbols across 9 files)
FILE: Data_Point_Set.h
type OW_current (line 17) | typedef struct OW_current {
type OW_hourly (line 48) | typedef struct OW_hourly {
type OW_daily (line 76) | typedef struct OW_daily {
type OW_forecast (line 126) | typedef struct OW_forecast {
FILE: OpenWeather.h
function class (line 33) | class OW_Weather: public JsonListener {
FILE: examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/GfxUi.h
function class (line 42) | class GfxUi {
FILE: examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/NTP_Time.h
function syncTime (line 132) | void syncTime(void)
function sendNTPpacket (line 154) | void sendNTPpacket(IPAddress& address)
function decodeNTP (line 183) | void decodeNTP(void)
function printTime (line 262) | void printTime(time_t t, char *tzCode)
FILE: examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/SPIFFS_Support.h
function listFiles (line 11) | void listFiles(void) {
function listDir (line 44) | void listDir(fs::FS &fs, const char * dirname, uint8_t levels) {
FILE: examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather_LittleFS/GfxUi.h
function class (line 42) | class GfxUi {
FILE: examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather_LittleFS/NTP_Time.h
function syncTime (line 132) | void syncTime(void)
function sendNTPpacket (line 154) | void sendNTPpacket(IPAddress& address)
function decodeNTP (line 183) | void decodeNTP(void)
function printTime (line 262) | void printTime(time_t t, char *tzCode)
FILE: examples/TFT_eSPI_OpenWeather_LittleFS/GfxUi.h
function class (line 42) | class GfxUi {
FILE: examples/TFT_eSPI_OpenWeather_LittleFS/NTP_Time.h
function syncTime (line 132) | void syncTime(void)
function sendNTPpacket (line 154) | void sendNTPpacket(IPAddress& address)
function decodeNTP (line 183) | void decodeNTP(void)
function printTime (line 262) | void printTime(time_t t, char *tzCode)
Condensed preview — 45 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (320K chars).
[
{
"path": ".gitattributes",
"chars": 66,
"preview": "# Auto detect text files and perform LF normalization\n* text=auto\n"
},
{
"path": "Data_Point_Set.h",
"chars": 7220,
"preview": "// The structures below are the repository for the data values extracted from the\n// JSON message. The structures are po"
},
{
"path": "OpenWeather.cpp",
"chars": 29862,
"preview": "// Client library for the OpenWeather data-point server\n// https://openweathermap.org/\n\n// Created by Bodmer 09/04/2020\n"
},
{
"path": "OpenWeather.h",
"chars": 6222,
"preview": "// Client library for the OpenWeatherMap data-point server\n// https://openweathermap.org/\n\n// The API server uses https,"
},
{
"path": "README.md",
"chars": 1373,
"preview": "# Raspberry Pico W, RP2040 Nano Connect, ESP8266 and ESP32 OpenWeather client\n\nArduino client library for https://openwe"
},
{
"path": "User_Setup.h",
"chars": 1714,
"preview": "\n// Configuration settings for OpenWeather library\n\n\n// These parameters set the data point count stored in program memo"
},
{
"path": "examples/Onecall API (subscription required)/My_OpenWeather_Test/My_OpenWeather_Test.ino",
"chars": 10407,
"preview": "// Sketch for Pico W, RP2040 Nano Connect, ESP32 and ESP8266 to fetch the Weather\n// Forecast from OpenWeather, an examp"
},
{
"path": "examples/Onecall API (subscription required)/My_OpenWeather_Test/Notes.ino",
"chars": 1139,
"preview": "/*\n\n [units] should be one of the following:\n\n metric\n imperial\n\n lang=[language] optional\n\n Return summa"
},
{
"path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/All_Settings.h",
"chars": 2788,
"preview": "// Use the OpenWeather library: https://github.com/Bodmer/OpenWeather\n\n// The weather icons and fonts are in the sketc"
},
{
"path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/GfxUi.cpp",
"chars": 10386,
"preview": "/**The MIT License (MIT)\nCopyright (c) 2015 by Daniel Eichhorn\nPermission is hereby granted, free of charge, to any pers"
},
{
"path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/GfxUi.h",
"chars": 2153,
"preview": "/**The MIT License (MIT)\nCopyright (c) 2015 by Daniel Eichhorn\nPermission is hereby granted, free of charge, to any pers"
},
{
"path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/MoonPhase.ino",
"chars": 2300,
"preview": "/* Moon phase calculation for OpenWeather library*/\n// Adapted by Bodmer from code here:\n// http://www.voidware.com/moon"
},
{
"path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/NTP_Time.h",
"chars": 11063,
"preview": "//====================================================================================\n// "
},
{
"path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/SPIFFS_Support.h",
"chars": 2602,
"preview": "/*====================================================================================\n This sketch contains support fu"
},
{
"path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/ScreenGrabClient.ino",
"chars": 17463,
"preview": "// This is a copy of the processing sketch that can be used to capture the images\n// Not needed by this sketch, used dur"
},
{
"path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/ScreenGrabServer.ino",
"chars": 7688,
"preview": "// Reads a screen image off the TFT and send it to a processing client sketch\n// over the serial port. Use a high baud r"
},
{
"path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather/TFT_eSPI_OpenWeather.ino",
"chars": 27563,
"preview": "// Example from OpenWeather library: https://github.com/Bodmer/OpenWeather\n// Adapted by Bodmer to use the TFT_eSPI li"
},
{
"path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather_LittleFS/All_Settings.h",
"chars": 2540,
"preview": "// Use the OpenWeather library: https://github.com/Bodmer/OpenWeather\n\n// The weather icons and fonts are in the sketc"
},
{
"path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather_LittleFS/GfxUi.cpp",
"chars": 4550,
"preview": "/**The MIT License (MIT)\nCopyright (c) 2015 by Daniel Eichhorn\nPermission is hereby granted, free of charge, to any pers"
},
{
"path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather_LittleFS/GfxUi.h",
"chars": 1983,
"preview": "/**The MIT License (MIT)\nCopyright (c) 2015 by Daniel Eichhorn\nPermission is hereby granted, free of charge, to any pers"
},
{
"path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather_LittleFS/MoonPhase.ino",
"chars": 2265,
"preview": "/* Moon phase calculation for OpenWeather library*/\n// Adapted by Bodmer from code here:\n// http://www.voidware.com/moon"
},
{
"path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather_LittleFS/NTP_Time.h",
"chars": 11061,
"preview": "//====================================================================================\n// "
},
{
"path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather_LittleFS/ScreenGrabClient.ino",
"chars": 17463,
"preview": "// This is a copy of the processing sketch that can be used to capture the images\n// Not needed by this sketch, used dur"
},
{
"path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather_LittleFS/ScreenGrabServer.ino",
"chars": 7686,
"preview": "// Reads a screen image off the TFT and send it to a processing client sketch\n// over the serial port. Use a high baud r"
},
{
"path": "examples/Onecall API (subscription required)/TFT_eSPI_OpenWeather_LittleFS/TFT_eSPI_OpenWeather_LittleFS.ino",
"chars": 28599,
"preview": "// Example from OpenWeather library: https://github.com/Bodmer/OpenWeather\n// Adapted by Bodmer to use the TFT_eSPI li"
},
{
"path": "examples/OpenWeather_Forecast_Test/Notes.ino",
"chars": 1139,
"preview": "/*\n\n [units] should be one of the following:\n\n metric\n imperial\n\n lang=[language] optional\n\n Return summa"
},
{
"path": "examples/OpenWeather_Forecast_Test/OpenWeather_Forecast_Test.ino",
"chars": 5583,
"preview": "// Sketch for ESP32, ESP8266, RP2040 Pico W, RP2040 Nano Connect\n// it will run on a \"bare\" board ans reports via Serial"
},
{
"path": "examples/TFT_eSPI_OpenWeather_LittleFS/All_Settings.h",
"chars": 2267,
"preview": "// Use the OpenWeather library: https://github.com/Bodmer/OpenWeather\n\n// The weather icons and fonts are in the sketc"
},
{
"path": "examples/TFT_eSPI_OpenWeather_LittleFS/GfxUi.cpp",
"chars": 4550,
"preview": "/**The MIT License (MIT)\nCopyright (c) 2015 by Daniel Eichhorn\nPermission is hereby granted, free of charge, to any pers"
},
{
"path": "examples/TFT_eSPI_OpenWeather_LittleFS/GfxUi.h",
"chars": 1983,
"preview": "/**The MIT License (MIT)\nCopyright (c) 2015 by Daniel Eichhorn\nPermission is hereby granted, free of charge, to any pers"
},
{
"path": "examples/TFT_eSPI_OpenWeather_LittleFS/MoonPhase.ino",
"chars": 2265,
"preview": "/* Moon phase calculation for OpenWeather library*/\n// Adapted by Bodmer from code here:\n// http://www.voidware.com/moon"
},
{
"path": "examples/TFT_eSPI_OpenWeather_LittleFS/NTP_Time.h",
"chars": 11061,
"preview": "//====================================================================================\n// "
},
{
"path": "examples/TFT_eSPI_OpenWeather_LittleFS/ScreenGrabClient.ino",
"chars": 17463,
"preview": "// This is a copy of the processing sketch that can be used to capture the images\n// Not needed by this sketch, used dur"
},
{
"path": "examples/TFT_eSPI_OpenWeather_LittleFS/ScreenGrabServer.ino",
"chars": 7686,
"preview": "// Reads a screen image off the TFT and send it to a processing client sketch\n// over the serial port. Use a high baud r"
},
{
"path": "examples/TFT_eSPI_OpenWeather_LittleFS/TFT_eSPI_OpenWeather_LittleFS.ino",
"chars": 30290,
"preview": "// Example from OpenWeather library: https://github.com/Bodmer/OpenWeather\n// Adapted by Bodmer to use the TFT_eSPI li"
},
{
"path": "keywords.txt",
"chars": 167,
"preview": "OpenWeather\tKEYWORD1\n\ngetForecast\tKEYWORD2\nparseRequest\tKEYWORD2\npartialDataSet\tKEYWORD2\n\nOW_current\tKEYWORD2\nOW_hourly\t"
},
{
"path": "library.json",
"chars": 511,
"preview": "{\n \"name\": \"OpenWeather\",\n \"version\": \"0.3.0\",\n \"keywords\": \"weather, TFT_eSPI, tft, display, ESP8266, NodeMCU, ESP32"
},
{
"path": "library.properties",
"chars": 290,
"preview": "name=OpenWeather\nversion=0.3.0\nauthor=Bodmer\nmaintainer=Bodmer\nsentence=OpenWeather client\nparagraph=A weather retrieval"
},
{
"path": "license.txt",
"chars": 1762,
"preview": "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvStartvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\nSoftware License Agreement (FreeBSD License)\n"
}
]
// ... and 6 more files (download for full content)
About this extraction
This page contains the full source code of the Bodmer/OpenWeather GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 45 files (298.0 KB), approximately 78.6k tokens, and a symbol index with 22 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.