概述
太高興了
天氣怎么樣?
海龜,“高興起來”
在此項目中,Adafruit Metro M4 Express Airlift和Tri-Color ePaper Shield可以愉快地共同創建一個具有當前天氣狀況的氣象站
發布Metro M4 Express AirLift時發生了一件很棒的事情。它打開了Arduino草圖到互聯網,使它們擺脫了封閉環境的限制。現在,它正式成為物聯網(IoT)世界中的“事物”。在此項目中,Metro M4 Express AirLift將從互聯網上獲取您當地的天氣數據,并在易于閱讀的ePaper顯示屏上顯示
與該項目一起使用的Adafruit三色ePaper Shield是一款2.7“ ePaper防護罩,顯示黑色,白色和紅色像素。由于接頭已經在Metro和ePaper防護罩上組裝好,因此無需焊接即可輕松連接到Metro。
功能
此DIY氣象站顯示您所在地區的天氣信息ePaper顯示屏上的攝氏度或華氏度單位。顯示的信息包括:
當前日期
當前溫度
當前天氣狀況
接下來的12小時的預報數據,以6小時為增量
天氣圖標以顯示天氣情況和天氣預報
城市名稱
顯示當前和預測的高溫紅色
日出和日落時間
當前月相的描述和圖標
零件
構建該項目無需焊接,僅使用兩個零件:Adafruit Metro M4 Express AirLift Lite和Adafruit 2.7“ Tri-Color eInk/ePaper用SRAM屏蔽。要使該項目可移植,您可以添加USB電池組或Adafruit PowerBoost 500 Shield和一塊Li-Po電池,然后將其插入Metro和ePaper屏蔽之間。
如果您對其他項目的ePaper顯示器感興趣,查看Adafruit的ePaper顯示的整個行。
Adafruit Metro M4 Express AirLift(WiFi) -Lite
產品ID:4000
使用AirLift為您的下一個項目提供電梯-我們為這個Metro M4增添了ESP32協處理器的機智名稱。您已經知道theAdafruit Metro 。..
$ 34.95
入庫存
添加到購物車
Adafruit 2.7“三色電子墨水/ePaper帶有Shield的SRAM
產品ID:4229
輕松的電子紙終于出現在微控制器上,這種突破的目的是使添加三色電子墨水顯示器變得輕而易舉。您是否看到過其中之一。..
$ 39.95
入庫存
添加到購物車
USB電纜-USB A到Micro -B
產品ID:592
這是您的標準A到Micro-B USB電纜,用于USB 1.1或2.0。非常適合將PC連接到Metro,Feather, Raspberry Pi或其他開發板或。..
$ 2.95
入庫存
添加到購物車
Metro M4 Express配備了AirLift協處理器板!好吧,至少對于此草圖,它將獲取您的本地天氣并將其顯示在ePaper防護板上。有許多具有天氣API的站點可以獲取您當地的當前狀況和即將到來的預報。這些網站中有較少的網站出于非商業目的免費提供此數據。我們使用了OpenWeatherMap.com API,該API提供了有關當前狀況和天氣預報的免費天氣數據。
對于天氣和月亮圖標,我們利用了Adafruit GFX庫對自定義字體的支持。字體是此類顯示的理想選擇,因為我們需要簡單,不同點大小的線形圖標,這些圖標在單色ePaper顯示器上看起來不錯。對于天氣圖標,我們選擇了Meteocon字體集,這是一種包含40多個天氣圖標的免費字體。對于月相字體,我們使用了Curtis Clark的Moon Phases字體,該字體可免費用于非商業用途。字體文件需要進行轉換才能與GFX庫一起使用,但是請放心,我們已經以各種磅數完成了此操作,因此您可以在本項目和其他項目中使用這些字體。
月相,OpenWeatherMap在其API中不包含月相。但是,這不是問題,因為我們使用數學來計算當前的月相。我們知道,平均每29.5305882天就有一個新月。從OpenWeatherMap中獲取當前日期和時間,如果我們知道新月發生的時間點,則可以計算當前日期的月相。
OpenWeatherMap中的天氣數據包括當前未來天氣預報的時間和時間,例如距現在3小時的天氣預報。這次是UTC格式。我們需要將其轉換為本地時間,這樣才能準確顯示時間。幸運的是,OpenWeatherMap最近將數據添加了本地時區偏移量(實際上,正如我們編寫本指南一樣!),因此現在可以通過采用UTC的當前時間并添加本地時區偏移量來輕松確定本地時間。
Meteoicon字體包含40多種與天氣相關的字體。
下一步是將它們放在一起:獲取天氣數據并格式化顯示。我們修改了Daniel Eichhorn出色的Weather Station項目中的部分代碼,因此它并不是特定于微控制器的專有代碼。這樣,包括空運協處理器在內的其他處理器就可以從Internet檢索天氣數據。
Arduino設置
如果尚未安裝,則需要在計算機上安裝Arduino IDE才能將此草圖上傳到Metro M4 Express AirLift。在本學習指南中找到有關安裝Arduino的信息。您還需要配置Metro M4 Express AirLift開發板以與Arduino IDE配合使用。 《 Adafruit學習指南》網站上有一篇很棒的文章,介紹了如何使用Arduino和其他環境設置此開發板。/span》
安裝庫
對于此草圖,您將需要安裝以下庫:
Adafruit EPD(電子紙顯示)庫
Adafruit GFX庫
Adafruit NeoPixel庫
Arduino JSON庫
Adafruit的變體WiFiNINA庫
這些庫應位于最新的Arduino IDE(1.8.7及更高版本)的Arduino庫管理器中。
打開Arduino庫管理器:
搜索 Adafruit EPD 庫并安裝
搜索 Adafruit GFX 庫并安裝它
搜索 Adafruit NeoPixel 庫并安裝
搜索 ArduinoJSON 庫并安裝
您必須通過下面的鏈接手動安裝WiFiNina
下載Adafruit版本的WiFiNina
注冊OpenWeatherMap API密鑰
您需要OpenWeatherMap的API密鑰才能下載來自其站點的天氣數據。您可以在OpenWeatherMap.org上免費注冊。免費帳戶允許訪問當前天氣和5天/3小時天氣API。
下載代碼并修改secrets.h文件
此下載可在GitHub上獲得。它包含天氣和月相圖標的代碼和字體文件。
必須修改 secrets.h ,使其包括WiFi設置,OpenWeatherMap API密鑰以及所在城市您需要天氣數據。如果您的城市名稱出現在多個國家(例如意大利威尼斯和佛羅里達州威尼斯),則可能需要包含2個字母的國家/地區代碼。因此,例如,佛羅里達州的威尼斯將被輸入為“美國威尼斯”。
此文件中的其他選項包括選擇溫度的公制或英語單位以及天氣描述的語言。
下載:Project Zip 或 secrets.h | 在Github上查看
復制代碼
#pragma once
// secrets.h
// Define your WIFI and OpenWeatherMap API key and location in this file
#define WIFI_SSID “{wifi ssid}”
#define WIFI_PASSWORD “{wifi password}”
#define OWM_KEY “{OpenWeatherMap.com key}”
#define OWM_LOCATION “Your City”
//example
//#define OWM_LOCATION “New York,US”
// update the weather at this interval, in minutes
#define UPDATE_INTERVAL 15
// Set to true to show temperatures in Celsius, false for Fahrenheit
#define OWM_METRIC false
// temperature will display in red at or above this temperature
// set to a high number (i.e. 》200) to not show temperatures in red
#define METRIC_HOT 32
#define ENGLISH_HOT 90
/*
Arabic - ar, Bulgarian - bg, Catalan - ca, Czech - cz, German - de, Greek - el,
English - en, Persian (Farsi) - fa, Finnish - fi, French - fr, Galician - gl,
Croatian - hr, Hungarian - hu, Italian - it, Japanese - ja, Korean - kr,
Latvian - la, Lithuanian - lt, Macedonian - mk, Dutch - nl, Polish - pl,
Portuguese - pt, Romanian - ro, Russian - ru, Swedish - se, Slovak - sk,
Slovenian - sl, Spanish - es, Turkish - tr, Ukrainian - ua, Vietnamese - vi,
Chinese Simplified - zh_cn, Chinese Traditional - zh_tw.
*/
#define OWM_LANGUAGE “en”
#pragma once
// secrets.h
// Define your WIFI and OpenWeatherMap API key and location in this file
#define WIFI_SSID “{wifi ssid}”
#define WIFI_PASSWORD “{wifi password}”
#define OWM_KEY “{OpenWeatherMap.com key}”
#define OWM_LOCATION “Your City”
//example
//#define OWM_LOCATION “New York,US”
// update the weather at this interval, in minutes
#define UPDATE_INTERVAL 15
// Set to true to show temperatures in Celsius, false for Fahrenheit
#define OWM_METRIC false
// temperature will display in red at or above this temperature
// set to a high number (i.e. 》200) to not show temperatures in red
#define METRIC_HOT 32
#define ENGLISH_HOT 90
/*
Arabic - ar, Bulgarian - bg, Catalan - ca, Czech - cz, German - de, Greek - el,
English - en, Persian (Farsi) - fa, Finnish - fi, French - fr, Galician - gl,
Croatian - hr, Hungarian - hu, Italian - it, Japanese - ja, Korean - kr,
Latvian - la, Lithuanian - lt, Macedonian - mk, Dutch - nl, Polish - pl,
Portuguese - pt, Romanian - ro, Russian - ru, Swedish - se, Slovak - sk,
Slovenian - sl, Spanish - es, Turkish - tr, Ukrainian - ua, Vietnamese - vi,
Chinese Simplified - zh_cn, Chinese Traditional - zh_tw.
*/
#define OWM_LANGUAGE “en”
下載:項目Zip 或 adafruit_epd_weather.ino | 在Github上查看
復制代碼
#include
#include // Core graphics library
#include
#include
#include //https://github.com/bblanchon/ArduinoJson
#include
#include
#include “secrets.h”
#include “OpenWeatherMap.h”
#include “Fonts/meteocons48pt7b.h”
#include “Fonts/meteocons24pt7b.h”
#include “Fonts/meteocons20pt7b.h”
#include “Fonts/meteocons16pt7b.h”
#include “Fonts/moon_phases20pt7b.h”
#include “Fonts/moon_phases36pt7b.h”
#include
#include
#include
#include
#include
#define SRAM_CS 8
#define EPD_CS 10
#define EPD_DC 9
#define EPD_RESET -1
#define EPD_BUSY -1
#define NEOPIXELPIN 40
// This is for the 2.7“ tricolor EPD
Adafruit_IL91874 gfx(264, 176 ,EPD_DC, EPD_RESET, EPD_CS, SRAM_CS, EPD_BUSY);
AirliftOpenWeatherMap owclient(&Serial);
OpenWeatherMapCurrentData owcdata;
OpenWeatherMapForecastData owfdata[3];
Adafruit_NeoPixel neopixel = Adafruit_NeoPixel(1, NEOPIXELPIN, NEO_GRB + NEO_KHZ800);
const char *moonphasenames[29] = {
”New Moon“,
”Waxing Crescent“,
”Waxing Crescent“,
”Waxing Crescent“,
”Waxing Crescent“,
”Waxing Crescent“,
”Waxing Crescent“,
”Quarter“,
”Waxing Gibbous“,
”Waxing Gibbous“,
”Waxing Gibbous“,
”Waxing Gibbous“,
”Waxing Gibbous“,
”Waxing Gibbous“,
”Full Moon“,
”Waning Gibbous“,
”Waning Gibbous“,
”Waning Gibbous“,
”Waning Gibbous“,
”Waning Gibbous“,
”Waning Gibbous“,
”Last Quarter“,
”Waning Crescent“,
”Waning Crescent“,
”Waning Crescent“,
”Waning Crescent“,
”Waning Crescent“,
”Waning Crescent“,
”Waning Crescent“
};
int8_t readButtons(void) {
uint16_t reading = analogRead(A3);
//Serial.println(reading);
if (reading 》 600) {
return 0; // no buttons pressed
}
if (reading 》 400) {
return 4; // button D pressed
}
if (reading 》 250) {
return 3; // button C pressed
}
if (reading 》 125) {
return 2; // button B pressed
}
return 1; // Button A pressed
}
bool wifi_connect(){
Serial.print(”Connecting to WiFi.。. “);
WiFi.setPins(SPIWIFI_SS, SPIWIFI_ACK, ESP32_RESETN, ESP32_GPIO0, &SPIWIFI);
// check for the WiFi module:
if(WiFi.status() == WL_NO_MODULE) {
Serial.println(”Communication with WiFi module failed!“);
displayError(”Communication with WiFi module failed!“);
while(true);
}
String fv = WiFi.firmwareVersion();
if (fv 《 ”1.0.0“) {
Serial.println(”Please upgrade the firmware“);
}
neopixel.setPixelColor(0, neopixel.Color(0, 0, 255));
neopixel.show();
if(WiFi.begin(WIFI_SSID, WIFI_PASSWORD) == WL_CONNECT_FAILED)
{
Serial.println(”WiFi connection failed!“);
displayError(”WiFi connection failed!“);
return false;
}
int wifitimeout = 15;
int wifistatus;
while ((wifistatus = WiFi.status()) != WL_CONNECTED && wifitimeout 》 0) {
delay(1000);
Serial.print(”。“);
wifitimeout--;
}
if(wifitimeout == 0)
{
Serial.println(”WiFi connection timeout with error “ + String(wifistatus));
displayError(”WiFi connection timeout with error “ + String(wifistatus));
neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
neopixel.show();
return false;
}
neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
neopixel.show();
Serial.println(”Connected“);
return true;
}
void wget(String &url, int port, char *buff)
{
int pos1 = url.indexOf(”/“,0);
int pos2 = url.indexOf(”/“,8);
String host = url.substring(pos1+2,pos2);
String path = url.substring(pos2);
Serial.println(”to wget(“ + host + ”,“ + path + ”,“ + port + ”)“);
wget(host, path, port, buff);
}
void wget(String &host, String &path, int port, char *buff)
{
//WiFiSSLClient client;
WiFiClient client;
neopixel.setPixelColor(0, neopixel.Color(0, 0, 255));
neopixel.show();
client.stop();
if (client.connect(host.c_str(), port)) {
Serial.println(”connected to server“);
// Make a HTTP request:
client.println(String(”GET “) + path + String(” HTTP/1.0“));
client.println(”Host: “ + host);
client.println(”Connection: close“);
client.println();
uint32_t bytes = 0;
int capturepos = 0;
bool capture = false;
int linelength = 0;
char lastc = ‘’;
while(true)
{
while (client.available()) {
char c = client.read();
//Serial.print(c);
if((c == ‘ ’) && (lastc == ‘ ’))
{
if(linelength == 0)
{
capture = true;
}
linelength = 0;
}
else if(capture)
{
buff[capturepos++] = c;
//Serial.write(c);
}
else
{
if((c != ‘ ’) && (c != ‘ ’))
linelength++;
}
lastc = c;
bytes++;
}
// if the server‘s disconnected, stop the client:
if (!client.connected()) {
//Serial.println();
Serial.println(”disconnecting from server.“);
client.stop();
buff[capturepos] = ’‘;
Serial.println(”captured “ + String(capturepos) + ” bytes“);
break;
}
}
}
else
{
Serial.println(”problem connecting to “ + host + ”:“ + String(port));
buff[0] = ’‘;
}
neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
neopixel.show();
}
int getStringLength(String s)
{
int16_t x = 0, y = 0;
uint16_t w, h;
gfx.getTextBounds(s, 0, 0, &x, &y, &w, &h);
return w + x;
}
/*
return value is percent of moon cycle ( from 0.0 to 0.999999), i.e.:
0.0: New Moon
0.125: Waxing Crescent Moon
0.25: Quarter Moon
0.375: Waxing Gibbous Moon
0.5: Full Moon
0.625: Waning Gibbous Moon
0.75: Last Quarter Moon
0.875: Waning Crescent Moon
*/
float getMoonPhase(time_t tdate)
{
time_t newmoonref = 1263539460; //known new moon date (2010-01-15 07:11)
// moon phase is 29.5305882 days, which is 2551442.82048 seconds
float phase = abs( tdate - newmoonref) / (double)2551442.82048;
phase -= (int)phase; // leave only the remainder
if(newmoonref 》 tdate)
phase = 1 - phase;
return phase;
}
void displayError(String str)
{
// show error on display
neopixel.setPixelColor(0, neopixel.Color(255, 0, 0));
neopixel.show();
Serial.println(str);
gfx.setTextColor(EPD_BLACK);
gfx.powerUp();
gfx.clearBuffer();
gfx.setTextWrap(true);
gfx.setCursor(10,60);
gfx.setFont(&FreeSans12pt7b);
gfx.print(str);
gfx.display();
neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
neopixel.show();
}
void displayHeading(OpenWeatherMapCurrentData &owcdata)
{
time_t local = owcdata.observationTime + owcdata.timezone;
struct tm *timeinfo = gmtime(&local);
char datestr[80];
// date
//strftime(datestr,80,”%a, %d %b %Y“,timeinfo);
strftime(datestr,80,”%a, %b %d“,timeinfo);
gfx.setFont(&FreeSans18pt7b);
gfx.setCursor((gfx.width()-getStringLength(datestr))/2,30);
gfx.print(datestr);
// city
strftime(datestr,80,”%A“,timeinfo);
gfx.setFont(&FreeSansBold12pt7b);
gfx.setCursor((gfx.width()-getStringLength(owcdata.cityName))/2,60);
gfx.print(owcdata.cityName);
}
void displayForecastDays(OpenWeatherMapCurrentData &owcdata, OpenWeatherMapForecastData owfdata[], int count = 3)
{
for(int i=0; i 《 count; i++)
{
// day
time_t local = owfdata[i].observationTime + owcdata.timezone;
struct tm *timeinfo = gmtime(&local);
char strbuff[80];
strftime(strbuff,80,”%I“,timeinfo);
String datestr = String(atoi(strbuff));
strftime(strbuff,80,”%p“,timeinfo);
// convert AM/PM to lowercase
strbuff[0] = tolower(strbuff[0]);
strbuff[1] = tolower(strbuff[1]);
datestr = datestr + ” “ + String(strbuff);
gfx.setFont(&FreeSans9pt7b);
gfx.setCursor(i*gfx.width()/3 + (gfx.width()/3-getStringLength(datestr))/2,94);
gfx.print(datestr);
// weather icon
String wicon = owclient.getMeteoconIcon(owfdata[i].icon);
gfx.setFont(&meteocons20pt7b);
gfx.setCursor(i*gfx.width()/3 + (gfx.width()/3-getStringLength(wicon))/2,134);
gfx.print(wicon);
// weather main description
gfx.setFont(&FreeSans9pt7b);
gfx.setCursor(i*gfx.width()/3 + (gfx.width()/3-getStringLength(owfdata[i].main))/2,154);
gfx.print(owfdata[i].main);
// temperature
int itemp = (int)(owfdata[i].temp + .5);
int color = EPD_BLACK;
if((OWM_METRIC && itemp 》= METRIC_HOT)|| (!OWM_METRIC && itemp 》= ENGLISH_HOT))
color = EPD_RED;
gfx.setTextColor(color);
gfx.setFont(&FreeSans9pt7b);
gfx.setCursor(i*gfx.width()/3 + (gfx.width()/3-getStringLength(String(itemp)))/2,172);
gfx.print(itemp);
gfx.drawCircle(i*gfx.width()/3 + (gfx.width()/3-getStringLength(String(itemp)))/2 + getStringLength(String(itemp)) + 6,163,3,color);
gfx.drawCircle(i*gfx.width()/3 + (gfx.width()/3-getStringLength(String(itemp)))/2 + getStringLength(String(itemp)) + 6,163,2,color);
gfx.setTextColor(EPD_BLACK);
}
}
void displayForecast(OpenWeatherMapCurrentData &owcdata, OpenWeatherMapForecastData owfdata[], int count = 3)
{
gfx.powerUp();
gfx.clearBuffer();
neopixel.setPixelColor(0, neopixel.Color(0, 255, 0));
neopixel.show();
gfx.setTextColor(EPD_BLACK);
displayHeading(owcdata);
displayForecastDays(owcdata, owfdata, count);
gfx.display();
gfx.powerDown();
neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
neopixel.show();
}
void displayAllWeather(OpenWeatherMapCurrentData &owcdata, OpenWeatherMapForecastData owfdata[], int count = 3)
{
gfx.powerUp();
gfx.clearBuffer();
neopixel.setPixelColor(0, neopixel.Color(0, 255, 0));
neopixel.show();
gfx.setTextColor(EPD_BLACK);
// date string
time_t local = owcdata.observationTime + owcdata.timezone;
struct tm *timeinfo = gmtime(&local);
char datestr[80];
// date
//strftime(datestr,80,”%a, %d %b %Y“,timeinfo);
strftime(datestr,80,”%a, %b %d %Y“,timeinfo);
gfx.setFont(&FreeSans9pt7b);
gfx.setCursor((gfx.width()-getStringLength(datestr))/2,14);
gfx.print(datestr);
// weather icon
String wicon = owclient.getMeteoconIcon(owcdata.icon);
gfx.setFont(&meteocons24pt7b);
gfx.setCursor((gfx.width()/3-getStringLength(wicon))/2,56);
gfx.print(wicon);
// weather main description
gfx.setFont(&FreeSans9pt7b);
gfx.setCursor((gfx.width()/3-getStringLength(owcdata.main))/2,72);
gfx.print(owcdata.main);
// temperature
gfx.setFont(&FreeSansBold24pt7b);
int itemp = owcdata.temp + .5;
int color = EPD_BLACK;
if((OWM_METRIC && (int)itemp 》= METRIC_HOT)|| (!OWM_METRIC && (int)itemp 》= ENGLISH_HOT))
color = EPD_RED;
gfx.setTextColor(color);
gfx.setCursor(gfx.width()/3 + (gfx.width()/3-getStringLength(String(itemp)))/2,58);
gfx.print(itemp);
gfx.setTextColor(EPD_BLACK);
// draw temperature degree as a circle (not available as font character
gfx.drawCircle(gfx.width()/3 + (gfx.width()/3 + getStringLength(String(itemp)))/2 + 8, 58-30,4,color);
gfx.drawCircle(gfx.width()/3 + (gfx.width()/3 + getStringLength(String(itemp)))/2 + 8, 58-30,3,color);
// draw moon
// draw Moon Phase
float moonphase = getMoonPhase(owcdata.observationTime);
int moonage = 29.5305882 * moonphase;
//Serial.println(”moon age: “ + String(moonage));
// convert to appropriate icon
String moonstr = String((char)((int)’A‘ + (int)(moonage*25./30)));
gfx.setFont(&moon_phases20pt7b);
// font lines look a little thin at this size, drawing it a few times to thicken the lines
gfx.setCursor(2*gfx.width()/3 + (gfx.width()/3-getStringLength(moonstr))/2,56);
gfx.print(moonstr);
gfx.setCursor(2*gfx.width()/3 + (gfx.width()/3-getStringLength(moonstr))/2+1,56);
gfx.print(moonstr);
gfx.setCursor(2*gfx.width()/3 + (gfx.width()/3-getStringLength(moonstr))/2,56-1);
gfx.print(moonstr);
// draw moon phase name
int currentphase = moonphase * 28. + .5;
gfx.setFont(); // system font (smallest available)
gfx.setCursor(2*gfx.width()/3 + max(0,(gfx.width()/3 - getStringLength(moonphasenames[currentphase]))/2),62);
gfx.print(moonphasenames[currentphase]);
displayForecastDays(owcdata, owfdata, count);
gfx.display();
gfx.powerDown();
neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
neopixel.show();
}
void displayCurrentConditions(OpenWeatherMapCurrentData &owcdata)
{
gfx.powerUp();
gfx.clearBuffer();
neopixel.setPixelColor(0, neopixel.Color(0, 255, 0));
neopixel.show();
gfx.setTextColor(EPD_BLACK);
displayHeading(owcdata);
// weather icon
String wicon = owclient.getMeteoconIcon(owcdata.icon);
gfx.setFont(&meteocons48pt7b);
gfx.setCursor((gfx.width()/2-getStringLength(wicon))/2,156);
gfx.print(wicon);
// weather main description
gfx.setFont(&FreeSans9pt7b);
gfx.setCursor(gfx.width()/2 + (gfx.width()/2-getStringLength(owcdata.main))/2,160);
gfx.print(owcdata.main);
// temperature
gfx.setFont(&FreeSansBold24pt7b);
int itemp = owcdata.temp + .5;
int color = EPD_BLACK;
if((OWM_METRIC && (int)itemp 》= METRIC_HOT)|| (!OWM_METRIC && (int)itemp 》= ENGLISH_HOT))
color = EPD_RED;
gfx.setTextColor(color);
gfx.setCursor(gfx.width()/2 + (gfx.width()/2-getStringLength(String(itemp)))/2,130);
gfx.print(itemp);
gfx.setTextColor(EPD_BLACK);
// draw temperature degree as a circle (not available as font character
gfx.drawCircle(gfx.width()/2 + (gfx.width()/2 + getStringLength(String(itemp)))/2 + 10, 130-26,4,color);
gfx.drawCircle(gfx.width()/2 + (gfx.width()/2 + getStringLength(String(itemp)))/2 + 10, 130-26,3,color);
gfx.display();
gfx.powerDown();
neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
neopixel.show();
}
void displaySunMoon(OpenWeatherMapCurrentData &owcdata)
{
gfx.powerUp();
gfx.clearBuffer();
neopixel.setPixelColor(0, neopixel.Color(0, 255, 0));
neopixel.show();
gfx.setTextColor(EPD_BLACK);
displayHeading(owcdata);
// draw Moon Phase
float moonphase = getMoonPhase(owcdata.observationTime);
int moonage = 29.5305882 * moonphase;
// convert to appropriate icon
String moonstr = String((char)((int)’A‘ + (int)(moonage*25./30)));
gfx.setFont(&moon_phases36pt7b);
gfx.setCursor((gfx.width()/3-getStringLength(moonstr))/2,140);
gfx.print(moonstr);
// draw moon phase name
int currentphase = moonphase * 28. + .5;
gfx.setFont(&FreeSans9pt7b);
gfx.setCursor(gfx.width()/3 + max(0,(gfx.width()*2/3 - getStringLength(moonphasenames[currentphase]))/2),110);
gfx.print(moonphasenames[currentphase]);
// draw sunrise/sunset
// sunrise/sunset times
// sunrise
time_t local = owcdata.sunrise + owcdata.timezone + 30; // round to nearest minute
struct tm *timeinfo = gmtime(&local);
char strbuff[80];
strftime(strbuff,80,”%I“,timeinfo);
String datestr = String(atoi(strbuff));
strftime(strbuff,80,”:%M %p“,timeinfo);
datestr = datestr + String(strbuff) + ” - “;
// sunset
local = owcdata.sunset + owcdata.timezone + 30; // round to nearest minute
timeinfo = gmtime(&local);
strftime(strbuff,80,”%I“,timeinfo);
datestr = datestr + String(atoi(strbuff));
strftime(strbuff,80,”:%M %p“,timeinfo);
datestr = datestr + String(strbuff);
gfx.setFont(&FreeSans9pt7b);
int datestrlen = getStringLength(datestr);
int xpos = (gfx.width() - datestrlen)/2;
gfx.setCursor(xpos,166);
gfx.print(datestr);
// draw sunrise icon
// sun icon is ”B“
String wicon = ”B“;
gfx.setFont(&meteocons16pt7b);
gfx.setCursor(xpos - getStringLength(wicon) - 12,174);
gfx.print(wicon);
// draw sunset icon
// sunset icon is ”A“
wicon = ”A“;
gfx.setCursor(xpos + datestrlen + 12,174);
gfx.print(wicon);
gfx.display();
gfx.powerDown();
neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
neopixel.show();
}
void setup() {
neopixel.begin();
neopixel.show();
gfx.begin();
Serial.println(”ePaper display initialized“);
gfx.setRotation(2);
gfx.setTextWrap(false);
}
void loop() {
char data[4000];
static uint32_t timer = millis();
static uint8_t lastbutton = 1;
static bool firsttime = true;
int button = readButtons();
// update weather data at specified interval or when button 4 is pressed
if((millis() 》= (timer + 1000*60*UPDATE_INTERVAL)) || (button == 4) || firsttime)
{
Serial.println(”getting weather data“);
firsttime = false;
timer = millis();
int retry = 6;
while(!wifi_connect())
{
delay(5000);
retry--;
if(retry 《 0)
{
displayError(”Can not connect to WiFi, press reset to restart“);
while(1);
}
}
String urlc = owclient.buildUrlCurrent(OWM_KEY,OWM_LOCATION);
Serial.println(urlc);
retry = 6;
do
{
retry--;
wget(urlc,80,data);
if(strlen(data) == 0 && retry 《 0)
{
displayError(”Can not get weather data, press reset to restart“);
while(1);
}
}
while(strlen(data) == 0);
Serial.println(”data retrieved:“);
Serial.println(data);
retry = 6;
while(!owclient.updateCurrent(owcdata,data))
{
retry--;
if(retry 《 0)
{
displayError(owclient.getError());
while(1);
}
delay(5000);
}
String urlf = owclient.buildUrlForecast(OWM_KEY,OWM_LOCATION);
Serial.println(urlf);
wget(urlf,80,data);
Serial.println(”data retrieved:“);
Serial.println(data);
if(!owclient.updateForecast(owfdata[0],data,0))
{
displayError(owclient.getError());
while(1);
}
if(!owclient.updateForecast(owfdata[1],data,2))
{
displayError(owclient.getError());
while(1);
}
if(!owclient.updateForecast(owfdata[2],data,4))
{
displayError(owclient.getError());
while(1);
}
switch(lastbutton)
{
case 1:
displayAllWeather(owcdata,owfdata,3);
break;
case 2:
displayCurrentConditions(owcdata);
break;
case 3:
displaySunMoon(owcdata);
break;
}
}
if (button == 0) {
return;
}
Serial.print(”Button “); Serial.print(button); Serial.println(” pressed“);
if (button == 1) {
displayAllWeather(owcdata,owfdata,3);
lastbutton = button;
}
if (button == 2) {
//displayForecast(owcdata,owfdata,3);
displayCurrentConditions(owcdata);
lastbutton = button;
}
if (button == 3) {
displaySunMoon(owcdata);
lastbutton = button;
}
// wait until button is released
while (readButtons()) {
delay(10);
}
}
#include
#include // Core graphics library
#include
#include
#include //https://github.com/bblanchon/ArduinoJson
#include
#include
#include ”secrets.h“
#include ”OpenWeatherMap.h“
#include ”Fonts/meteocons48pt7b.h“
#include ”Fonts/meteocons24pt7b.h“
#include ”Fonts/meteocons20pt7b.h“
#include ”Fonts/meteocons16pt7b.h“
#include ”Fonts/moon_phases20pt7b.h“
#include ”Fonts/moon_phases36pt7b.h“
#include
#include
#include
#include
#include
#define SRAM_CS 8
#define EPD_CS 10
#define EPD_DC 9
#define EPD_RESET -1
#define EPD_BUSY -1
#define NEOPIXELPIN 40
// This is for the 2.7” tricolor EPD
Adafruit_IL91874 gfx(264, 176 ,EPD_DC, EPD_RESET, EPD_CS, SRAM_CS, EPD_BUSY);
AirliftOpenWeatherMap owclient(&Serial);
OpenWeatherMapCurrentData owcdata;
OpenWeatherMapForecastData owfdata[3];
Adafruit_NeoPixel neopixel = Adafruit_NeoPixel(1, NEOPIXELPIN, NEO_GRB + NEO_KHZ800);
const char *moonphasenames[29] = {
“New Moon”,
“Waxing Crescent”,
“Waxing Crescent”,
“Waxing Crescent”,
“Waxing Crescent”,
“Waxing Crescent”,
“Waxing Crescent”,
“Quarter”,
“Waxing Gibbous”,
“Waxing Gibbous”,
“Waxing Gibbous”,
“Waxing Gibbous”,
“Waxing Gibbous”,
“Waxing Gibbous”,
“Full Moon”,
“Waning Gibbous”,
“Waning Gibbous”,
“Waning Gibbous”,
“Waning Gibbous”,
“Waning Gibbous”,
“Waning Gibbous”,
“Last Quarter”,
“Waning Crescent”,
“Waning Crescent”,
“Waning Crescent”,
“Waning Crescent”,
“Waning Crescent”,
“Waning Crescent”,
“Waning Crescent”
};
int8_t readButtons(void) {
uint16_t reading = analogRead(A3);
//Serial.println(reading);
if (reading 》 600) {
return 0; // no buttons pressed
}
if (reading 》 400) {
return 4; // button D pressed
}
if (reading 》 250) {
return 3; // button C pressed
}
if (reading 》 125) {
return 2; // button B pressed
}
return 1; // Button A pressed
}
bool wifi_connect(){
Serial.print(“Connecting to WiFi.。. ”);
WiFi.setPins(SPIWIFI_SS, SPIWIFI_ACK, ESP32_RESETN, ESP32_GPIO0, &SPIWIFI);
// check for the WiFi module:
if(WiFi.status() == WL_NO_MODULE) {
Serial.println(“Communication with WiFi module failed!”);
displayError(“Communication with WiFi module failed!”);
while(true);
}
String fv = WiFi.firmwareVersion();
if (fv 《 “1.0.0”) {
Serial.println(“Please upgrade the firmware”);
}
neopixel.setPixelColor(0, neopixel.Color(0, 0, 255));
neopixel.show();
if(WiFi.begin(WIFI_SSID, WIFI_PASSWORD) == WL_CONNECT_FAILED)
{
Serial.println(“WiFi connection failed!”);
displayError(“WiFi connection failed!”);
return false;
}
int wifitimeout = 15;
int wifistatus;
while ((wifistatus = WiFi.status()) != WL_CONNECTED && wifitimeout 》 0) {
delay(1000);
Serial.print(“。”);
wifitimeout--;
}
if(wifitimeout == 0)
{
Serial.println(“WiFi connection timeout with error ” + String(wifistatus));
displayError(“WiFi connection timeout with error ” + String(wifistatus));
neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
neopixel.show();
return false;
}
neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
neopixel.show();
Serial.println(“Connected”);
return true;
}
void wget(String &url, int port, char *buff)
{
int pos1 = url.indexOf(“/”,0);
int pos2 = url.indexOf(“/”,8);
String host = url.substring(pos1+2,pos2);
String path = url.substring(pos2);
Serial.println(“to wget(” + host + “,” + path + “,” + port + “)”);
wget(host, path, port, buff);
}
void wget(String &host, String &path, int port, char *buff)
{
//WiFiSSLClient client;
WiFiClient client;
neopixel.setPixelColor(0, neopixel.Color(0, 0, 255));
neopixel.show();
client.stop();
if (client.connect(host.c_str(), port)) {
Serial.println(“connected to server”);
// Make a HTTP request:
client.println(String(“GET ”) + path + String(“ HTTP/1.0”));
client.println(“Host: ” + host);
client.println(“Connection: close”);
client.println();
uint32_t bytes = 0;
int capturepos = 0;
bool capture = false;
int linelength = 0;
char lastc = ’‘;
while(true)
{
while (client.available()) {
char c = client.read();
//Serial.print(c);
if((c == ’ ‘) && (lastc == ’ ‘))
{
if(linelength == 0)
{
capture = true;
}
linelength = 0;
}
else if(capture)
{
buff[capturepos++] = c;
//Serial.write(c);
}
else
{
if((c != ’ ‘) && (c != ’ ‘))
linelength++;
}
lastc = c;
bytes++;
}
// if the server’s disconnected, stop the client:
if (!client.connected()) {
//Serial.println();
Serial.println(“disconnecting from server.”);
client.stop();
buff[capturepos] = ‘’;
Serial.println(“captured ” + String(capturepos) + “ bytes”);
break;
}
}
}
else
{
Serial.println(“problem connecting to ” + host + “:” + String(port));
buff[0] = ‘’;
}
neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
neopixel.show();
}
int getStringLength(String s)
{
int16_t x = 0, y = 0;
uint16_t w, h;
gfx.getTextBounds(s, 0, 0, &x, &y, &w, &h);
return w + x;
}
/*
return value is percent of moon cycle ( from 0.0 to 0.999999), i.e.:
0.0: New Moon
0.125: Waxing Crescent Moon
0.25: Quarter Moon
0.375: Waxing Gibbous Moon
0.5: Full Moon
0.625: Waning Gibbous Moon
0.75: Last Quarter Moon
0.875: Waning Crescent Moon
*/
float getMoonPhase(time_t tdate)
{
time_t newmoonref = 1263539460; //known new moon date (2010-01-15 07:11)
// moon phase is 29.5305882 days, which is 2551442.82048 seconds
float phase = abs( tdate - newmoonref) / (double)2551442.82048;
phase -= (int)phase; // leave only the remainder
if(newmoonref 》 tdate)
phase = 1 - phase;
return phase;
}
void displayError(String str)
{
// show error on display
neopixel.setPixelColor(0, neopixel.Color(255, 0, 0));
neopixel.show();
Serial.println(str);
gfx.setTextColor(EPD_BLACK);
gfx.powerUp();
gfx.clearBuffer();
gfx.setTextWrap(true);
gfx.setCursor(10,60);
gfx.setFont(&FreeSans12pt7b);
gfx.print(str);
gfx.display();
neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
neopixel.show();
}
void displayHeading(OpenWeatherMapCurrentData &owcdata)
{
time_t local = owcdata.observationTime + owcdata.timezone;
struct tm *timeinfo = gmtime(&local);
char datestr[80];
// date
//strftime(datestr,80,“%a, %d %b %Y”,timeinfo);
strftime(datestr,80,“%a, %b %d”,timeinfo);
gfx.setFont(&FreeSans18pt7b);
gfx.setCursor((gfx.width()-getStringLength(datestr))/2,30);
gfx.print(datestr);
// city
strftime(datestr,80,“%A”,timeinfo);
gfx.setFont(&FreeSansBold12pt7b);
gfx.setCursor((gfx.width()-getStringLength(owcdata.cityName))/2,60);
gfx.print(owcdata.cityName);
}
void displayForecastDays(OpenWeatherMapCurrentData &owcdata, OpenWeatherMapForecastData owfdata[], int count = 3)
{
for(int i=0; i 《 count; i++)
{
// day
time_t local = owfdata[i].observationTime + owcdata.timezone;
struct tm *timeinfo = gmtime(&local);
char strbuff[80];
strftime(strbuff,80,“%I”,timeinfo);
String datestr = String(atoi(strbuff));
strftime(strbuff,80,“%p”,timeinfo);
// convert AM/PM to lowercase
strbuff[0] = tolower(strbuff[0]);
strbuff[1] = tolower(strbuff[1]);
datestr = datestr + “ ” + String(strbuff);
gfx.setFont(&FreeSans9pt7b);
gfx.setCursor(i*gfx.width()/3 + (gfx.width()/3-getStringLength(datestr))/2,94);
gfx.print(datestr);
// weather icon
String wicon = owclient.getMeteoconIcon(owfdata[i].icon);
gfx.setFont(&meteocons20pt7b);
gfx.setCursor(i*gfx.width()/3 + (gfx.width()/3-getStringLength(wicon))/2,134);
gfx.print(wicon);
// weather main description
gfx.setFont(&FreeSans9pt7b);
gfx.setCursor(i*gfx.width()/3 + (gfx.width()/3-getStringLength(owfdata[i].main))/2,154);
gfx.print(owfdata[i].main);
// temperature
int itemp = (int)(owfdata[i].temp + .5);
int color = EPD_BLACK;
if((OWM_METRIC && itemp 》= METRIC_HOT)|| (!OWM_METRIC && itemp 》= ENGLISH_HOT))
color = EPD_RED;
gfx.setTextColor(color);
gfx.setFont(&FreeSans9pt7b);
gfx.setCursor(i*gfx.width()/3 + (gfx.width()/3-getStringLength(String(itemp)))/2,172);
gfx.print(itemp);
gfx.drawCircle(i*gfx.width()/3 + (gfx.width()/3-getStringLength(String(itemp)))/2 + getStringLength(String(itemp)) + 6,163,3,color);
gfx.drawCircle(i*gfx.width()/3 + (gfx.width()/3-getStringLength(String(itemp)))/2 + getStringLength(String(itemp)) + 6,163,2,color);
gfx.setTextColor(EPD_BLACK);
}
}
void displayForecast(OpenWeatherMapCurrentData &owcdata, OpenWeatherMapForecastData owfdata[], int count = 3)
{
gfx.powerUp();
gfx.clearBuffer();
neopixel.setPixelColor(0, neopixel.Color(0, 255, 0));
neopixel.show();
gfx.setTextColor(EPD_BLACK);
displayHeading(owcdata);
displayForecastDays(owcdata, owfdata, count);
gfx.display();
gfx.powerDown();
neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
neopixel.show();
}
void displayAllWeather(OpenWeatherMapCurrentData &owcdata, OpenWeatherMapForecastData owfdata[], int count = 3)
{
gfx.powerUp();
gfx.clearBuffer();
neopixel.setPixelColor(0, neopixel.Color(0, 255, 0));
neopixel.show();
gfx.setTextColor(EPD_BLACK);
// date string
time_t local = owcdata.observationTime + owcdata.timezone;
struct tm *timeinfo = gmtime(&local);
char datestr[80];
// date
//strftime(datestr,80,“%a, %d %b %Y”,timeinfo);
strftime(datestr,80,“%a, %b %d %Y”,timeinfo);
gfx.setFont(&FreeSans9pt7b);
gfx.setCursor((gfx.width()-getStringLength(datestr))/2,14);
gfx.print(datestr);
// weather icon
String wicon = owclient.getMeteoconIcon(owcdata.icon);
gfx.setFont(&meteocons24pt7b);
gfx.setCursor((gfx.width()/3-getStringLength(wicon))/2,56);
gfx.print(wicon);
// weather main description
gfx.setFont(&FreeSans9pt7b);
gfx.setCursor((gfx.width()/3-getStringLength(owcdata.main))/2,72);
gfx.print(owcdata.main);
// temperature
gfx.setFont(&FreeSansBold24pt7b);
int itemp = owcdata.temp + .5;
int color = EPD_BLACK;
if((OWM_METRIC && (int)itemp 》= METRIC_HOT)|| (!OWM_METRIC && (int)itemp 》= ENGLISH_HOT))
color = EPD_RED;
gfx.setTextColor(color);
gfx.setCursor(gfx.width()/3 + (gfx.width()/3-getStringLength(String(itemp)))/2,58);
gfx.print(itemp);
gfx.setTextColor(EPD_BLACK);
// draw temperature degree as a circle (not available as font character
gfx.drawCircle(gfx.width()/3 + (gfx.width()/3 + getStringLength(String(itemp)))/2 + 8, 58-30,4,color);
gfx.drawCircle(gfx.width()/3 + (gfx.width()/3 + getStringLength(String(itemp)))/2 + 8, 58-30,3,color);
// draw moon
// draw Moon Phase
float moonphase = getMoonPhase(owcdata.observationTime);
int moonage = 29.5305882 * moonphase;
//Serial.println(“moon age: ” + String(moonage));
// convert to appropriate icon
String moonstr = String((char)((int)‘A’ + (int)(moonage*25./30)));
gfx.setFont(&moon_phases20pt7b);
// font lines look a little thin at this size, drawing it a few times to thicken the lines
gfx.setCursor(2*gfx.width()/3 + (gfx.width()/3-getStringLength(moonstr))/2,56);
gfx.print(moonstr);
gfx.setCursor(2*gfx.width()/3 + (gfx.width()/3-getStringLength(moonstr))/2+1,56);
gfx.print(moonstr);
gfx.setCursor(2*gfx.width()/3 + (gfx.width()/3-getStringLength(moonstr))/2,56-1);
gfx.print(moonstr);
// draw moon phase name
int currentphase = moonphase * 28. + .5;
gfx.setFont(); // system font (smallest available)
gfx.setCursor(2*gfx.width()/3 + max(0,(gfx.width()/3 - getStringLength(moonphasenames[currentphase]))/2),62);
gfx.print(moonphasenames[currentphase]);
displayForecastDays(owcdata, owfdata, count);
gfx.display();
gfx.powerDown();
neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
neopixel.show();
}
void displayCurrentConditions(OpenWeatherMapCurrentData &owcdata)
{
gfx.powerUp();
gfx.clearBuffer();
neopixel.setPixelColor(0, neopixel.Color(0, 255, 0));
neopixel.show();
gfx.setTextColor(EPD_BLACK);
displayHeading(owcdata);
// weather icon
String wicon = owclient.getMeteoconIcon(owcdata.icon);
gfx.setFont(&meteocons48pt7b);
gfx.setCursor((gfx.width()/2-getStringLength(wicon))/2,156);
gfx.print(wicon);
// weather main description
gfx.setFont(&FreeSans9pt7b);
gfx.setCursor(gfx.width()/2 + (gfx.width()/2-getStringLength(owcdata.main))/2,160);
gfx.print(owcdata.main);
// temperature
gfx.setFont(&FreeSansBold24pt7b);
int itemp = owcdata.temp + .5;
int color = EPD_BLACK;
if((OWM_METRIC && (int)itemp 》= METRIC_HOT)|| (!OWM_METRIC && (int)itemp 》= ENGLISH_HOT))
color = EPD_RED;
gfx.setTextColor(color);
gfx.setCursor(gfx.width()/2 + (gfx.width()/2-getStringLength(String(itemp)))/2,130);
gfx.print(itemp);
gfx.setTextColor(EPD_BLACK);
// draw temperature degree as a circle (not available as font character
gfx.drawCircle(gfx.width()/2 + (gfx.width()/2 + getStringLength(String(itemp)))/2 + 10, 130-26,4,color);
gfx.drawCircle(gfx.width()/2 + (gfx.width()/2 + getStringLength(String(itemp)))/2 + 10, 130-26,3,color);
gfx.display();
gfx.powerDown();
neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
neopixel.show();
}
void displaySunMoon(OpenWeatherMapCurrentData &owcdata)
{
gfx.powerUp();
gfx.clearBuffer();
neopixel.setPixelColor(0, neopixel.Color(0, 255, 0));
neopixel.show();
gfx.setTextColor(EPD_BLACK);
displayHeading(owcdata);
// draw Moon Phase
float moonphase = getMoonPhase(owcdata.observationTime);
int moonage = 29.5305882 * moonphase;
// convert to appropriate icon
String moonstr = String((char)((int)‘A’ + (int)(moonage*25./30)));
gfx.setFont(&moon_phases36pt7b);
gfx.setCursor((gfx.width()/3-getStringLength(moonstr))/2,140);
gfx.print(moonstr);
// draw moon phase name
int currentphase = moonphase * 28. + .5;
gfx.setFont(&FreeSans9pt7b);
gfx.setCursor(gfx.width()/3 + max(0,(gfx.width()*2/3 - getStringLength(moonphasenames[currentphase]))/2),110);
gfx.print(moonphasenames[currentphase]);
// draw sunrise/sunset
// sunrise/sunset times
// sunrise
time_t local = owcdata.sunrise + owcdata.timezone + 30; // round to nearest minute
struct tm *timeinfo = gmtime(&local);
char strbuff[80];
strftime(strbuff,80,“%I”,timeinfo);
String datestr = String(atoi(strbuff));
strftime(strbuff,80,“:%M %p”,timeinfo);
datestr = datestr + String(strbuff) + “ - ”;
// sunset
local = owcdata.sunset + owcdata.timezone + 30; // round to nearest minute
timeinfo = gmtime(&local);
strftime(strbuff,80,“%I”,timeinfo);
datestr = datestr + String(atoi(strbuff));
strftime(strbuff,80,“:%M %p”,timeinfo);
datestr = datestr + String(strbuff);
gfx.setFont(&FreeSans9pt7b);
int datestrlen = getStringLength(datestr);
int xpos = (gfx.width() - datestrlen)/2;
gfx.setCursor(xpos,166);
gfx.print(datestr);
// draw sunrise icon
// sun icon is “B”
String wicon = “B”;
gfx.setFont(&meteocons16pt7b);
gfx.setCursor(xpos - getStringLength(wicon) - 12,174);
gfx.print(wicon);
// draw sunset icon
// sunset icon is “A”
wicon = “A”;
gfx.setCursor(xpos + datestrlen + 12,174);
gfx.print(wicon);
gfx.display();
gfx.powerDown();
neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
neopixel.show();
}
void setup() {
neopixel.begin();
neopixel.show();
gfx.begin();
Serial.println(“ePaper display initialized”);
gfx.setRotation(2);
gfx.setTextWrap(false);
}
void loop() {
char data[4000];
static uint32_t timer = millis();
static uint8_t lastbutton = 1;
static bool firsttime = true;
int button = readButtons();
// update weather data at specified interval or when button 4 is pressed
if((millis() 》= (timer + 1000*60*UPDATE_INTERVAL)) || (button == 4) || firsttime)
{
Serial.println(“getting weather data”);
firsttime = false;
timer = millis();
int retry = 6;
while(!wifi_connect())
{
delay(5000);
retry--;
if(retry 《 0)
{
displayError(“Can not connect to WiFi, press reset to restart”);
while(1);
}
}
String urlc = owclient.buildUrlCurrent(OWM_KEY,OWM_LOCATION);
Serial.println(urlc);
retry = 6;
do
{
retry--;
wget(urlc,80,data);
if(strlen(data) == 0 && retry 《 0)
{
displayError(“Can not get weather data, press reset to restart”);
while(1);
}
}
while(strlen(data) == 0);
Serial.println(“data retrieved:”);
Serial.println(data);
retry = 6;
while(!owclient.updateCurrent(owcdata,data))
{
retry--;
if(retry 《 0)
{
displayError(owclient.getError());
while(1);
}
delay(5000);
}
String urlf = owclient.buildUrlForecast(OWM_KEY,OWM_LOCATION);
Serial.println(urlf);
wget(urlf,80,data);
Serial.println(“data retrieved:”);
Serial.println(data);
if(!owclient.updateForecast(owfdata[0],data,0))
{
displayError(owclient.getError());
while(1);
}
if(!owclient.updateForecast(owfdata[1],data,2))
{
displayError(owclient.getError());
while(1);
}
if(!owclient.updateForecast(owfdata[2],data,4))
{
displayError(owclient.getError());
while(1);
}
switch(lastbutton)
{
case 1:
displayAllWeather(owcdata,owfdata,3);
break;
case 2:
displayCurrentConditions(owcdata);
break;
case 3:
displaySunMoon(owcdata);
break;
}
}
if (button == 0) {
return;
}
Serial.print(“Button ”); Serial.print(button); Serial.println(“ pressed”);
if (button == 1) {
displayAllWeather(owcdata,owfdata,3);
lastbutton = button;
}
if (button == 2) {
//displayForecast(owcdata,owfdata,3);
displayCurrentConditions(owcdata);
lastbutton = button;
}
if (button == 3) {
displaySunMoon(owcdata);
lastbutton = button;
}
// wait until button is released
while (readButtons()) {
delay(10);
}
}
下載:Project Zip 或 OpenWeatherMap.h | 在Github上查看
復制代碼
#pragma once
#include “secrets.h”
#include //https://github.com/bblanchon/ArduinoJson
typedef struct OpenWeatherMapCurrentData {
// “lon”: 8.54,
float lon;
// “lat”: 47.37
float lat;
// “id”: 521,
uint16_t weatherId;
// “main”: “Rain”,
String main;
// “description”: “shower rain”,
String description;
// “icon”: “09d”
String icon;
String iconMeteoCon;
// “temp”: 290.56,
float temp;
// “pressure”: 1013,
uint16_t pressure;
// “humidity”: 87,
uint8_t humidity;
// “temp_min”: 289.15,
float tempMin;
// “temp_max”: 292.15
float tempMax;
// visibility: 10000,
uint16_t visibility;
// “wind”: {“speed”: 1.5},
float windSpeed;
// “wind”: {deg: 226.505},
float windDeg;
// “clouds”: {“all”: 90},
uint8_t clouds;
// “dt”: 1527015000,
time_t observationTime;
// “country”: “CH”,
String country;
// “sunrise”: 1526960448,
time_t sunrise;
// “sunset”: 1527015901
time_t sunset;
// “name”: “Zurich”,
String cityName;
time_t timezone;
} OpenWeatherMapCurrentData;
typedef struct OpenWeatherMapForecastData {
// {“dt”:1527066000,
time_t observationTime;
// “main”:{
// “temp”:17.35,
float temp;
// “temp_min”:16.89,
float tempMin;
// “temp_max”:17.35,
float tempMax;
// “pressure”:970.8,
float pressure;
// “sea_level”:1030.62,
float pressureSeaLevel;
// “grnd_level”:970.8,
float pressureGroundLevel;
// “humidity”:97,
uint8_t humidity;
// “temp_kf”:0.46
// },“weather”:[{
// “id”:802,
uint16_t weatherId;
// “main”:“Clouds”,
String main;
// “description”:“scattered clouds”,
String description;
// “icon”:“03d”
String icon;
String iconMeteoCon;
// }],“clouds”:{“all”:44},
uint8_t clouds;
// “wind”:{
// “speed”:1.77,
float windSpeed;
// “deg”:207.501
float windDeg;
// rain: {3h: 0.055},
float rain;
// },“sys”:{“pod”:“d”}
// dt_txt: “2018-05-23 09:00:00”
String observationTimeText;
} OpenWeatherMapForecastData;
class AirliftOpenWeatherMap{
private:
Stream *Serial;
String currentKey;
String currentParent;
//OpenWeatherMapCurrentData *data;
uint8_t weatherItemCounter = 0;
bool metric = true;
String language;
String _error;
public:
AirliftOpenWeatherMap(Stream *serial){Serial = serial;};
String buildUrlCurrent(String appId, String locationParameter);
String buildUrlForecast(String appId, String locationParameter);
bool updateCurrent(OpenWeatherMapCurrentData &data,String json);
bool updateForecast(OpenWeatherMapForecastData &data,String json, int day = 0);
void setMetric(bool metric) {this-》metric = metric;}
bool isMetric() { return metric; }
void setLanguage(String language) { this-》language = language; }
String getLanguage() { return language; }
void setError(String error){_error = error;}
String getError(){return _error;}
String getMeteoconIcon(String icon);
};
#pragma once
#include “secrets.h”
#include //https://github.com/bblanchon/ArduinoJson
typedef struct OpenWeatherMapCurrentData {
// “lon”: 8.54,
float lon;
// “lat”: 47.37
float lat;
// “id”: 521,
uint16_t weatherId;
// “main”: “Rain”,
String main;
// “description”: “shower rain”,
String description;
// “icon”: “09d”
String icon;
String iconMeteoCon;
// “temp”: 290.56,
float temp;
// “pressure”: 1013,
uint16_t pressure;
// “humidity”: 87,
uint8_t humidity;
// “temp_min”: 289.15,
float tempMin;
// “temp_max”: 292.15
float tempMax;
// visibility: 10000,
uint16_t visibility;
// “wind”: {“speed”: 1.5},
float windSpeed;
// “wind”: {deg: 226.505},
float windDeg;
// “clouds”: {“all”: 90},
uint8_t clouds;
// “dt”: 1527015000,
time_t observationTime;
// “country”: “CH”,
String country;
// “sunrise”: 1526960448,
time_t sunrise;
// “sunset”: 1527015901
time_t sunset;
// “name”: “Zurich”,
String cityName;
time_t timezone;
} OpenWeatherMapCurrentData;
typedef struct OpenWeatherMapForecastData {
// {“dt”:1527066000,
time_t observationTime;
// “main”:{
// “temp”:17.35,
float temp;
// “temp_min”:16.89,
float tempMin;
// “temp_max”:17.35,
float tempMax;
// “pressure”:970.8,
float pressure;
// “sea_level”:1030.62,
float pressureSeaLevel;
// “grnd_level”:970.8,
float pressureGroundLevel;
// “humidity”:97,
uint8_t humidity;
// “temp_kf”:0.46
// },“weather”:[{
// “id”:802,
uint16_t weatherId;
// “main”:“Clouds”,
String main;
// “description”:“scattered clouds”,
String description;
// “icon”:“03d”
String icon;
String iconMeteoCon;
// }],“clouds”:{“all”:44},
uint8_t clouds;
// “wind”:{
// “speed”:1.77,
float windSpeed;
// “deg”:207.501
float windDeg;
// rain: {3h: 0.055},
float rain;
// },“sys”:{“pod”:“d”}
// dt_txt: “2018-05-23 09:00:00”
String observationTimeText;
} OpenWeatherMapForecastData;
class AirliftOpenWeatherMap{
private:
Stream *Serial;
String currentKey;
String currentParent;
//OpenWeatherMapCurrentData *data;
uint8_t weatherItemCounter = 0;
bool metric = true;
String language;
String _error;
public:
AirliftOpenWeatherMap(Stream *serial){Serial = serial;};
String buildUrlCurrent(String appId, String locationParameter);
String buildUrlForecast(String appId, String locationParameter);
bool updateCurrent(OpenWeatherMapCurrentData &data,String json);
bool updateForecast(OpenWeatherMapForecastData &data,String json, int day = 0);
void setMetric(bool metric) {this-》metric = metric;}
bool isMetric() { return metric; }
void setLanguage(String language) { this-》language = language; }
String getLanguage() { return language; }
void setError(String error){_error = error;}
String getError(){return _error;}
String getMeteoconIcon(String icon);
};
下載:項目Zip 或 OpenWeatherMap.cpp | 在Github上查看復制代碼
#include “OpenWeatherMap.h”
String AirliftOpenWeatherMap::buildUrlCurrent(String appId, String location) {
String units = OWM_METRIC ? “metric” : “imperial”;
return “http://api.openweathermap.org/data/2.5/weather?q=” + location + “&appid=” + appId + “&units=” + units + “&lang=” + String(OWM_LANGUAGE);
}
String AirliftOpenWeatherMap::buildUrlForecast(String appId, String location) {
String units = OWM_METRIC ? “metric” : “imperial”;
return “http://api.openweathermap.org/data/2.5/forecast?q=” + location + “&cnt=6&appid=” + appId + “&units=” + units + “&lang=” + String(OWM_LANGUAGE);
}
String AirliftOpenWeatherMap::getMeteoconIcon(String icon) {
// clear sky
// 01d
if (icon == “01d”) {
return “B”;
}
// 01n
if (icon == “01n”) {
return “C”;
}
// few clouds
// 02d
if (icon == “02d”) {
return “H”;
}
// 02n
if (icon == “02n”) {
return “4”;
}
// scattered clouds
// 03d
if (icon == “03d”) {
return “N”;
}
// 03n
if (icon == “03n”) {
return “5”;
}
// broken clouds
// 04d
if (icon == “04d”) {
return “Y”;
}
// 04n
if (icon == “04n”) {
return “%”;
}
// shower rain
// 09d
if (icon == “09d”) {
return “R”;
}
// 09n
if (icon == “09n”) {
return “8”;
}
// rain
// 10d
if (icon == “10d”) {
return “Q”;
}
// 10n
if (icon == “10n”) {
return “7”;
}
// thunderstorm
// 11d
if (icon == “11d”) {
return “P”;
}
// 11n
if (icon == “11n”) {
return “6”;
}
// snow
// 13d
if (icon == “13d”) {
return “W”;
}
// 13n
if (icon == “13n”) {
return “#”;
}
// mist
// 50d
if (icon == “50d”) {
return “M”;
}
// 50n
if (icon == “50n”) {
return “M”;
}
// Nothing matched: N/A
return “)”;
}
bool AirliftOpenWeatherMap::updateCurrent(OpenWeatherMapCurrentData &data, String json)
{
Serial-》println(“updateCurrent()”);
DynamicJsonDocument doc(2000);
//StaticJsonDocument《2000》 doc;
DeserializationError error = deserializeJson(doc, json);
if (error) {
Serial-》println(String(“deserializeJson() failed: ”) + (const char *)error.c_str());
Serial-》println(json);
setError(String(“deserializeJson() failed: ”) + error.c_str());
return false;
}
int code = (int) doc[“cod”];
if(code != 200)
{
Serial-》println(String(“OpenWeatherMap error: ”) + (const char *)doc[“message”]);
setError(String(“OpenWeatherMap error: ”) + (const char *)doc[“message”]);
return false;
}
data.lat = (float) doc[“coord”][“lat”];
data.lon = (float) doc[“coord”][“lon”];
data.main = (const char*) doc[“weather”][0][“main”];
data.description = (const char*) doc[“weather”][0][“description”];
data.icon = (const char*) doc[“weather”][0][“icon”];
data.cityName = (const char*) doc[“name”];
data.visibility = (uint16_t) doc[“visibility”];
data.timezone = (time_t) doc[“timezone”];
data.country = (const char*) doc[“sys”][“country”];
data.observationTime = (time_t) doc[“dt”];
data.sunrise = (time_t) doc[“sys”][“sunrise”];
data.sunset = (time_t) doc[“sys”][“sunset”];
data.temp = (float) doc[“main”][“temp”];
data.pressure = (uint16_t) doc[“main”][“pressure”];
data.humidity = (uint8_t) doc[“main”][“humidity”];
data.tempMin = (float) doc[“main”][“temp_min”];
data.tempMax = (float) doc[“main”][“temp_max”];
data.windSpeed = (float) doc[“wind”][“speed”];
data.windDeg = (float) doc[“wind”][“deg”];
return true;
}
bool AirliftOpenWeatherMap::updateForecast(OpenWeatherMapForecastData &data, String json, int day)
{
Serial-》println(“updateForecast()”);
DynamicJsonDocument doc(5000);
//StaticJsonDocument《5000》 doc;
DeserializationError error = deserializeJson(doc, json);
if (error) {
Serial-》println(String(“deserializeJson() failed: ”) + (const char *)error.c_str());
Serial-》println(json);
setError(String(“deserializeJson() failed: ”) + error.c_str());
return false;
}
int code = (int) doc[“cod”];
if(code != 200)
{
Serial-》println(String(“OpenWeatherMap error: ”) + (const char *)doc[“message”]);
setError(String(“OpenWeatherMap error: ”) + (const char *)doc[“message”]);
return false;
}
data.observationTime = (time_t) doc[“list”][day][“dt”];
data.temp = (float) doc[“list”][day][“main”][“temp”];
data.pressure = (uint16_t) doc[“list”][day][“main”][“pressure”];
data.humidity = (uint8_t) doc[“list”][day][“main”][“humidity”];
data.tempMin = (float) doc[“list”][day][“main”][“temp_min”];
data.tempMax = (float) doc[“list”][day][“main”][“temp_max”];
data.main = (const char*) doc[“list”][day][“weather”][0][“main”];
data.description = (const char*) doc[“list”][day][“weather”][0][“description”];
data.icon = (const char*) doc[“list”][day][“weather”][0][“icon”];
return true;
}
#include “OpenWeatherMap.h”
String AirliftOpenWeatherMap::buildUrlCurrent(String appId, String location) {
String units = OWM_METRIC ? “metric” : “imperial”;
return “http://api.openweathermap.org/data/2.5/weather?q=” + location + “&appid=” + appId + “&units=” + units + “&lang=” + String(OWM_LANGUAGE);
}
String AirliftOpenWeatherMap::buildUrlForecast(String appId, String location) {
String units = OWM_METRIC ? “metric” : “imperial”;
return “http://api.openweathermap.org/data/2.5/forecast?q=” + location + “&cnt=6&appid=” + appId + “&units=” + units + “&lang=” + String(OWM_LANGUAGE);
}
String AirliftOpenWeatherMap::getMeteoconIcon(String icon) {
// clear sky
// 01d
if (icon == “01d”) {
return “B”;
}
// 01n
if (icon == “01n”) {
return “C”;
}
// few clouds
// 02d
if (icon == “02d”) {
return “H”;
}
// 02n
if (icon == “02n”) {
return “4”;
}
// scattered clouds
// 03d
if (icon == “03d”) {
return “N”;
}
// 03n
if (icon == “03n”) {
return “5”;
}
// broken clouds
// 04d
if (icon == “04d”) {
return “Y”;
}
// 04n
if (icon == “04n”) {
return “%”;
}
// shower rain
// 09d
if (icon == “09d”) {
return “R”;
}
// 09n
if (icon == “09n”) {
return “8”;
}
// rain
// 10d
if (icon == “10d”) {
return “Q”;
}
// 10n
if (icon == “10n”) {
return “7”;
}
// thunderstorm
// 11d
if (icon == “11d”) {
return “P”;
}
// 11n
if (icon == “11n”) {
return “6”;
}
// snow
// 13d
if (icon == “13d”) {
return “W”;
}
// 13n
if (icon == “13n”) {
return “#”;
}
// mist
// 50d
if (icon == “50d”) {
return “M”;
}
// 50n
if (icon == “50n”) {
return “M”;
}
// Nothing matched: N/A
return “)”;
}
bool AirliftOpenWeatherMap::updateCurrent(OpenWeatherMapCurrentData &data, String json)
{
Serial-》println(“updateCurrent()”);
DynamicJsonDocument doc(2000);
//StaticJsonDocument《2000》 doc;
DeserializationError error = deserializeJson(doc, json);
if (error) {
Serial-》println(String(“deserializeJson() failed: ”) + (const char *)error.c_str());
Serial-》println(json);
setError(String(“deserializeJson() failed: ”) + error.c_str());
return false;
}
int code = (int) doc[“cod”];
if(code != 200)
{
Serial-》println(String(“OpenWeatherMap error: ”) + (const char *)doc[“message”]);
setError(String(“OpenWeatherMap error: ”) + (const char *)doc[“message”]);
return false;
}
data.lat = (float) doc[“coord”][“lat”];
data.lon = (float) doc[“coord”][“lon”];
data.main = (const char*) doc[“weather”][0][“main”];
data.description = (const char*) doc[“weather”][0][“description”];
data.icon = (const char*) doc[“weather”][0][“icon”];
data.cityName = (const char*) doc[“name”];
data.visibility = (uint16_t) doc[“visibility”];
data.timezone = (time_t) doc[“timezone”];
data.country = (const char*) doc[“sys”][“country”];
data.observationTime = (time_t) doc[“dt”];
data.sunrise = (time_t) doc[“sys”][“sunrise”];
data.sunset = (time_t) doc[“sys”][“sunset”];
data.temp = (float) doc[“main”][“temp”];
data.pressure = (uint16_t) doc[“main”][“pressure”];
data.humidity = (uint8_t) doc[“main”][“humidity”];
data.tempMin = (float) doc[“main”][“temp_min”];
data.tempMax = (float) doc[“main”][“temp_max”];
data.windSpeed = (float) doc[“wind”][“speed”];
data.windDeg = (float) doc[“wind”][“deg”];
return true;
}
bool AirliftOpenWeatherMap::updateForecast(OpenWeatherMapForecastData &data, String json, int day)
{
Serial-》println(“updateForecast()”);
DynamicJsonDocument doc(5000);
//StaticJsonDocument《5000》 doc;
DeserializationError error = deserializeJson(doc, json);
if (error) {
Serial-》println(String(“deserializeJson() failed: ”) + (const char *)error.c_str());
Serial-》println(json);
setError(String(“deserializeJson() failed: ”) + error.c_str());
return false;
}
int code = (int) doc[“cod”];
if(code != 200)
{
Serial-》println(String(“OpenWeatherMap error: ”) + (const char *)doc[“message”]);
setError(String(“OpenWeatherMap error: ”) + (const char *)doc[“message”]);
return false;
}
data.observationTime = (time_t) doc[“list”][day][“dt”];
data.temp = (float) doc[“list”][day][“main”][“temp”];
data.pressure = (uint16_t) doc[“list”][day][“main”][“pressure”];
data.humidity = (uint8_t) doc[“list”][day][“main”][“humidity”];
data.tempMin = (float) doc[“list”][day][“main”][“temp_min”];
data.tempMax = (float) doc[“list”][day][“main”][“temp_max”];
data.main = (const char*) doc[“list”][day][“weather”][0][“main”];
data.description = (const char*) doc[“list”][day][“weather”][0][“description”];
data.icon = (const char*) doc[“list”][day][“weather”][0][“icon”];
return true;
}
字體文件可在此處的GitHub存儲庫中找到。
使用
防護罩包括4個可編程按鈕(標有“ A”至“ D”)和一個復位按鈕。這些按鈕已被編程以顯示不同的天氣顯示。首次啟動設備時,顯示屏將顯示當前和天氣預報。您也可以通過按“ A”或重置按鈕查看此顯示。按鈕“ B”顯示當前的天氣狀況,按鈕“ C”顯示當前的月相以及一天中的日出和日落時間。 “ D”按鈕將從互聯網上檢索最新的天氣數據,并使用最后的顯示模式進行顯示。如果更新間隔較長,并且想用最新的天氣數據更新顯示,則此功能特別有用。
使用按鈕顯示不同的天氣視圖。
狀態LED
TheMetro M4 Express Airlift還配備了一個NeoPixel。此項目使用NeoPixel作為項目狀態指示器。
A 藍色 NeoPixel狀態表示草圖當前正在訪問Internet,或者連接到WiFi熱點或獲取日期和時間。 。
A 綠色 NeoPixel狀態表示草圖當前正在更新顯示,完成后將關閉。由于ePaper顯示屏刷新屏幕的速度不是很快,因此可能要花費幾秒鐘。
A 紅色 NeoPixel狀態表示存在網絡通信問題。如果NeoPixel顯示這些狀態顏色之一,則按按鈕將無效。等待NeoPixel關閉,然后按其中一個按鈕。
NeoPixel用作狀態指示器。此處顯示為綠色表示正在更新顯示。
按下按鈕A或啟動設備會顯示當前和天氣預報。
按下按鈕B顯示當前的天氣情況。
按下按鈕C顯示當前的月相以及日出和日落時間。
責任編輯:wv
-
氣象站
+關注
關注
1文章
756瀏覽量
15695
發布評論請先 登錄
相關推薦
評論