概述
太高興了
天氣怎么樣?
海龜,“高興起來”
在此項目中,Adafruit Metro M4 Express Airlift和Tri-Color ePaper Shield可以愉快地共同創(chuàng)建一個具有當(dāng)前天氣狀況的氣象站
發(fā)布Metro M4 Express AirLift時發(fā)生了一件很棒的事情。它打開了Arduino草圖到互聯(lián)網(wǎng),使它們擺脫了封閉環(huán)境的限制。現(xiàn)在,它正式成為物聯(lián)網(wǎng)(IoT)世界中的“事物”。在此項目中,Metro M4 Express AirLift將從互聯(lián)網(wǎng)上獲取您當(dāng)?shù)氐奶鞖鈹?shù)據(jù),并在易于閱讀的ePaper顯示屏上顯示
與該項目一起使用的Adafruit三色ePaper Shield是一款2.7“ ePaper防護罩,顯示黑色,白色和紅色像素。由于接頭已經(jīng)在Metro和ePaper防護罩上組裝好,因此無需焊接即可輕松連接到Metro。
功能
此DIY氣象站顯示您所在地區(qū)的天氣信息ePaper顯示屏上的攝氏度或華氏度單位。顯示的信息包括:
當(dāng)前日期
當(dāng)前溫度
當(dāng)前天氣狀況
接下來的12小時的預(yù)報數(shù)據(jù),以6小時為增量
天氣圖標(biāo)以顯示天氣情況和天氣預(yù)報
城市名稱
顯示當(dāng)前和預(yù)測的高溫紅色
日出和日落時間
當(dāng)前月相的描述和圖標(biāo)
零件
構(gòu)建該項目無需焊接,僅使用兩個零件: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
產(chǎn)品ID:4000
使用AirLift為您的下一個項目提供電梯-我們?yōu)檫@個Metro M4增添了ESP32協(xié)處理器的機智名稱。您已經(jīng)知道theAdafruit Metro 。..
$ 34.95
入庫存
添加到購物車
Adafruit 2.7“三色電子墨水/ePaper帶有Shield的SRAM
產(chǎn)品ID:4229
輕松的電子紙終于出現(xiàn)在微控制器上,這種突破的目的是使添加三色電子墨水顯示器變得輕而易舉。您是否看到過其中之一。..
$ 39.95
入庫存
添加到購物車
USB電纜-USB A到Micro -B
產(chǎn)品ID:592
這是您的標(biāo)準(zhǔn)A到Micro-B USB電纜,用于USB 1.1或2.0。非常適合將PC連接到Metro,F(xiàn)eather, Raspberry Pi或其他開發(fā)板或。..
$ 2.95
入庫存
添加到購物車
Metro M4 Express配備了AirLift協(xié)處理器板!好吧,至少對于此草圖,它將獲取您的本地天氣并將其顯示在ePaper防護板上。有許多具有天氣API的站點可以獲取您當(dāng)?shù)氐漠?dāng)前狀況和即將到來的預(yù)報。這些網(wǎng)站中有較少的網(wǎng)站出于非商業(yè)目的免費提供此數(shù)據(jù)。我們使用了OpenWeatherMap.com API,該API提供了有關(guān)當(dāng)前狀況和天氣預(yù)報的免費天氣數(shù)據(jù)。
對于天氣和月亮圖標(biāo),我們利用了Adafruit GFX庫對自定義字體的支持。字體是此類顯示的理想選擇,因為我們需要簡單,不同點大小的線形圖標(biāo),這些圖標(biāo)在單色ePaper顯示器上看起來不錯。對于天氣圖標(biāo),我們選擇了Meteocon字體集,這是一種包含40多個天氣圖標(biāo)的免費字體。對于月相字體,我們使用了Curtis Clark的Moon Phases字體,該字體可免費用于非商業(yè)用途。字體文件需要進行轉(zhuǎn)換才能與GFX庫一起使用,但是請放心,我們已經(jīng)以各種磅數(shù)完成了此操作,因此您可以在本項目和其他項目中使用這些字體。
月相,OpenWeatherMap在其API中不包含月相。但是,這不是問題,因為我們使用數(shù)學(xué)來計算當(dāng)前的月相。我們知道,平均每29.5305882天就有一個新月。從OpenWeatherMap中獲取當(dāng)前日期和時間,如果我們知道新月發(fā)生的時間點,則可以計算當(dāng)前日期的月相。
OpenWeatherMap中的天氣數(shù)據(jù)包括當(dāng)前未來天氣預(yù)報的時間和時間,例如距現(xiàn)在3小時的天氣預(yù)報。這次是UTC格式。我們需要將其轉(zhuǎn)換為本地時間,這樣才能準(zhǔn)確顯示時間。幸運的是,OpenWeatherMap最近將數(shù)據(jù)添加了本地時區(qū)偏移量(實際上,正如我們編寫本指南一樣!),因此現(xiàn)在可以通過采用UTC的當(dāng)前時間并添加本地時區(qū)偏移量來輕松確定本地時間。
Meteoicon字體包含40多種與天氣相關(guān)的字體。
下一步是將它們放在一起:獲取天氣數(shù)據(jù)并格式化顯示。我們修改了Daniel Eichhorn出色的Weather Station項目中的部分代碼,因此它并不是特定于微控制器的專有代碼。這樣,包括空運協(xié)處理器在內(nèi)的其他處理器就可以從Internet檢索天氣數(shù)據(jù)。
Arduino設(shè)置
如果尚未安裝,則需要在計算機上安裝Arduino IDE才能將此草圖上傳到Metro M4 Express AirLift。在本學(xué)習(xí)指南中找到有關(guān)安裝Arduino的信息。您還需要配置Metro M4 Express AirLift開發(fā)板以與Arduino IDE配合使用。 《 Adafruit學(xué)習(xí)指南》網(wǎng)站上有一篇很棒的文章,介紹了如何使用Arduino和其他環(huán)境設(shè)置此開發(fā)板。/span》
安裝庫
對于此草圖,您將需要安裝以下庫:
Adafruit EPD(電子紙顯示)庫
Adafruit GFX庫
Adafruit NeoPixel庫
Arduino JSON庫
Adafruit的變體WiFiNINA庫
這些庫應(yīng)位于最新的Arduino IDE(1.8.7及更高版本)的Arduino庫管理器中。
打開Arduino庫管理器:
搜索 Adafruit EPD 庫并安裝
搜索 Adafruit GFX 庫并安裝它
搜索 Adafruit NeoPixel 庫并安裝
搜索 ArduinoJSON 庫并安裝
您必須通過下面的鏈接手動安裝WiFiNina
下載Adafruit版本的WiFiNina
注冊O(shè)penWeatherMap API密鑰
您需要OpenWeatherMap的API密鑰才能下載來自其站點的天氣數(shù)據(jù)。您可以在OpenWeatherMap.org上免費注冊。免費帳戶允許訪問當(dāng)前天氣和5天/3小時天氣API。
下載代碼并修改secrets.h文件
此下載可在GitHub上獲得。它包含天氣和月相圖標(biāo)的代碼和字體文件。
必須修改 secrets.h ,使其包括WiFi設(shè)置,OpenWeatherMap API密鑰以及所在城市您需要天氣數(shù)據(jù)。如果您的城市名稱出現(xiàn)在多個國家(例如意大利威尼斯和佛羅里達(dá)州威尼斯),則可能需要包含2個字母的國家/地區(qū)代碼。因此,例如,佛羅里達(dá)州的威尼斯將被輸入為“美國威尼斯”。
此文件中的其他選項包括選擇溫度的公制或英語單位以及天氣描述的語言。
下載:Project Zip 或 secrets.h | 在Github上查看
復(fù)制代碼
#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上查看
復(fù)制代碼
#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 ”O(jiān)penWeatherMap.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上查看
復(fù)制代碼
#pragma once
#include “secrets.h”
#include //https://github.com/bblanchon/ArduinoJson
typedef struct OpenWeatherMapCurrentData {
// “l(fā)on”: 8.54,
float lon;
// “l(fā)at”: 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 {
// “l(fā)on”: 8.54,
float lon;
// “l(fā)at”: 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上查看復(fù)制代碼
#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”][“l(fā)at”];
data.lon = (float) doc[“coord”][“l(fā)on”];
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[“l(fā)ist”][day][“dt”];
data.temp = (float) doc[“l(fā)ist”][day][“main”][“temp”];
data.pressure = (uint16_t) doc[“l(fā)ist”][day][“main”][“pressure”];
data.humidity = (uint8_t) doc[“l(fā)ist”][day][“main”][“humidity”];
data.tempMin = (float) doc[“l(fā)ist”][day][“main”][“temp_min”];
data.tempMax = (float) doc[“l(fā)ist”][day][“main”][“temp_max”];
data.main = (const char*) doc[“l(fā)ist”][day][“weather”][0][“main”];
data.description = (const char*) doc[“l(fā)ist”][day][“weather”][0][“description”];
data.icon = (const char*) doc[“l(fā)ist”][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”][“l(fā)at”];
data.lon = (float) doc[“coord”][“l(fā)on”];
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[“l(fā)ist”][day][“dt”];
data.temp = (float) doc[“l(fā)ist”][day][“main”][“temp”];
data.pressure = (uint16_t) doc[“l(fā)ist”][day][“main”][“pressure”];
data.humidity = (uint8_t) doc[“l(fā)ist”][day][“main”][“humidity”];
data.tempMin = (float) doc[“l(fā)ist”][day][“main”][“temp_min”];
data.tempMax = (float) doc[“l(fā)ist”][day][“main”][“temp_max”];
data.main = (const char*) doc[“l(fā)ist”][day][“weather”][0][“main”];
data.description = (const char*) doc[“l(fā)ist”][day][“weather”][0][“description”];
data.icon = (const char*) doc[“l(fā)ist”][day][“weather”][0][“icon”];
return true;
}
字體文件可在此處的GitHub存儲庫中找到。
使用
防護罩包括4個可編程按鈕(標(biāo)有“ A”至“ D”)和一個復(fù)位按鈕。這些按鈕已被編程以顯示不同的天氣顯示。首次啟動設(shè)備時,顯示屏將顯示當(dāng)前和天氣預(yù)報。您也可以通過按“ A”或重置按鈕查看此顯示。按鈕“ B”顯示當(dāng)前的天氣狀況,按鈕“ C”顯示當(dāng)前的月相以及一天中的日出和日落時間。 “ D”按鈕將從互聯(lián)網(wǎng)上檢索最新的天氣數(shù)據(jù),并使用最后的顯示模式進行顯示。如果更新間隔較長,并且想用最新的天氣數(shù)據(jù)更新顯示,則此功能特別有用。
使用按鈕顯示不同的天氣視圖。
狀態(tài)LED
TheMetro M4 Express Airlift還配備了一個NeoPixel。此項目使用NeoPixel作為項目狀態(tài)指示器。
A 藍(lán)色 NeoPixel狀態(tài)表示草圖當(dāng)前正在訪問Internet,或者連接到WiFi熱點或獲取日期和時間。 。
A 綠色 NeoPixel狀態(tài)表示草圖當(dāng)前正在更新顯示,完成后將關(guān)閉。由于ePaper顯示屏刷新屏幕的速度不是很快,因此可能要花費幾秒鐘。
A 紅色 NeoPixel狀態(tài)表示存在網(wǎng)絡(luò)通信問題。如果NeoPixel顯示這些狀態(tài)顏色之一,則按按鈕將無效。等待NeoPixel關(guān)閉,然后按其中一個按鈕。
NeoPixel用作狀態(tài)指示器。此處顯示為綠色表示正在更新顯示。
按下按鈕A或啟動設(shè)備會顯示當(dāng)前和天氣預(yù)報。
按下按鈕B顯示當(dāng)前的天氣情況。
按下按鈕C顯示當(dāng)前的月相以及日出和日落時間。
責(zé)任編輯:wv
-
氣象站
+關(guān)注
關(guān)注
1文章
766瀏覽量
16051
發(fā)布評論請先 登錄

解密交通氣象站:數(shù)據(jù)驅(qū)動的交通氣象服務(wù)革新

自動氣象站的工作原理與應(yīng)用
綠色校園,智慧氣象:走進我們的氣象站
自動超聲波氣象站:智慧氣象的重要組成部分
多功能自動氣象站-支持定制監(jiān)測數(shù)據(jù)
交通氣象站:實時監(jiān)測道路氣象環(huán)境

車載小型氣象站:實時、準(zhǔn)確地監(jiān)測氣象
小型車載自動氣象站:提供全面、及時的氣象信息


什么是自動氣象站?

評論