Skip to content

Commit d18dc2e

Browse files
committed
Add VisualCrossing files
1 parent 201fa29 commit d18dc2e

File tree

5 files changed

+1497
-0
lines changed

5 files changed

+1497
-0
lines changed
Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
/**The MIT License (MIT)
2+
3+
Copyright (c) 2018 by Daniel Eichhorn - ThingPulse
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
22+
23+
See more at https://thingpulse.com
24+
*/
25+
26+
#include <Arduino.h>
27+
28+
#if defined(ESP8266)
29+
#include <ESP8266WiFi.h>
30+
#include <coredecls.h> // settimeofday_cb()
31+
#else
32+
#include <WiFi.h>
33+
#endif
34+
#include <ESPHTTPClient.h>
35+
#include <JsonListener.h>
36+
37+
// time
38+
#include <time.h> // time() ctime()
39+
#include <sys/time.h> // struct timeval
40+
41+
#include "SSD1306Wire.h"
42+
#include "OLEDDisplayUi.h"
43+
#include "Wire.h"
44+
#include "VisualCrossing.h"
45+
#include "WeatherStationFonts.h"
46+
#include "WeatherStationImages.h"
47+
48+
49+
/***************************
50+
* Begin Settings
51+
**************************/
52+
53+
// WIFI
54+
const char* WIFI_SSID = "yourssid";
55+
const char* WIFI_PWD = "yourpassw0rd";
56+
57+
// Timezone
58+
// Enter a POSIX timezone string. A current list can be found here:
59+
// https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
60+
#define TZ "CET-1CEST,M3.5.0,M10.5.0/3"
61+
62+
// Setup
63+
const int UPDATE_INTERVAL_SECS = 20 * 60; // Update every 20 minutes
64+
65+
// Display Settings
66+
const int I2C_DISPLAY_ADDRESS = 0x3c;
67+
#if defined(ESP8266)
68+
const int SDA_PIN = D3;
69+
const int SDC_PIN = D4;
70+
#else
71+
const int SDA_PIN = 5; //D3;
72+
const int SDC_PIN = 4; //D4;
73+
#endif
74+
75+
76+
// VisualCrossing Settings
77+
// Sign up here to get an API key:
78+
// https://www.visualcrossing.com/sign-up
79+
String VISUAL_CROSSING_KEY = "XXX";
80+
81+
// Enter a plaintext location.
82+
// Go to https://www.visualcrossing.com/weather-data to test
83+
String VISUAL_CROSSING_LOCATION = "Zurich,Switzerland";
84+
85+
// Pick a language code from this list:
86+
// ar (Arabic), bg (Bulgiarian), cs (Czech), da (Danish), de (German),el (Greek Modern),
87+
// en (English), es (Spanish), fa (Farsi), fi (Finnish), fr (French), he (Hebrew),
88+
// hu, (Hungarian), it (Italian), ja (Japanese), ko (Korean), nl (Dutch), pl (Polish),
89+
// pt (Portuguese), ru (Russian), sk (Slovakian), sr (Serbian), sv (Swedish),
90+
// tr (Turkish), uk (Ukranian), vi (Vietnamese) and zh (Chinese).
91+
String VISUAL_CROSSING_LANGUAGE = "de";
92+
const uint8_t MAX_FORECASTS = 4;
93+
94+
const boolean IS_METRIC = true;
95+
const boolean IS_24HOUR = true;
96+
enum dateOrders {DMY, MDY, YMD};
97+
const dateOrders dateOrder = DMY;
98+
99+
// Adjust according to your language
100+
const String WDAY_NAMES[] = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"};
101+
const String MONTH_NAMES[] = {"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
102+
103+
/***************************
104+
* End Settings
105+
**************************/
106+
// Initialize the oled display for address 0x3c
107+
// sda-pin=14 and sdc-pin=12
108+
SSD1306Wire display(I2C_DISPLAY_ADDRESS, SDA_PIN, SDC_PIN);
109+
OLEDDisplayUi ui( &display );
110+
111+
VisualCrossingData currentWeather;
112+
VisualCrossingData forecasts[MAX_FORECASTS];
113+
VisualCrossing forecastClient;
114+
115+
time_t now;
116+
117+
// flag changed in the ticker function every 10 minutes
118+
bool readyForWeatherUpdate = false;
119+
120+
String lastUpdate = "--";
121+
122+
long timeSinceLastWUpdate = 0;
123+
124+
//declaring prototypes
125+
void drawProgress(OLEDDisplay *display, int percentage, String label);
126+
void updateData(OLEDDisplay *display);
127+
void drawDateTime(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
128+
void drawCurrentWeather(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
129+
void drawForecast(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
130+
void drawForecastDetails(OLEDDisplay *display, int x, int y, int dayIndex);
131+
void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state);
132+
void setReadyForWeatherUpdate();
133+
134+
135+
// Add frames
136+
// this array keeps function pointers to all frames
137+
// frames are the single views that slide from right to left
138+
FrameCallback frames[] = { drawDateTime, drawCurrentWeather, drawForecast };
139+
int numberOfFrames = 3;
140+
141+
OverlayCallback overlays[] = { drawHeaderOverlay };
142+
int numberOfOverlays = 1;
143+
144+
void setup() {
145+
Serial.begin(115200);
146+
Serial.println();
147+
Serial.println();
148+
149+
// initialize dispaly
150+
display.init();
151+
display.clear();
152+
display.display();
153+
154+
//display.flipScreenVertically();
155+
display.setFont(ArialMT_Plain_10);
156+
display.setTextAlignment(TEXT_ALIGN_CENTER);
157+
display.setContrast(255);
158+
159+
WiFi.begin(WIFI_SSID, WIFI_PWD);
160+
161+
int counter = 0;
162+
while (WiFi.status() != WL_CONNECTED) {
163+
delay(500);
164+
Serial.print(".");
165+
display.clear();
166+
display.drawString(64, 10, "Connecting to WiFi");
167+
display.drawXbm(46, 30, 8, 8, counter % 3 == 0 ? activeSymbole : inactiveSymbole);
168+
display.drawXbm(60, 30, 8, 8, counter % 3 == 1 ? activeSymbole : inactiveSymbole);
169+
display.drawXbm(74, 30, 8, 8, counter % 3 == 2 ? activeSymbole : inactiveSymbole);
170+
display.display();
171+
172+
counter++;
173+
}
174+
// Get time from network time service
175+
configTzTime(TZ, "pool.ntp.org");
176+
177+
ui.setTargetFPS(30);
178+
179+
ui.setActiveSymbol(activeSymbole);
180+
ui.setInactiveSymbol(inactiveSymbole);
181+
182+
// You can change this to
183+
// TOP, LEFT, BOTTOM, RIGHT
184+
ui.setIndicatorPosition(BOTTOM);
185+
186+
// Defines where the first frame is located in the bar.
187+
ui.setIndicatorDirection(LEFT_RIGHT);
188+
189+
// You can change the transition that is used
190+
// SLIDE_LEFT, SLIDE_RIGHT, SLIDE_TOP, SLIDE_DOWN
191+
ui.setFrameAnimation(SLIDE_LEFT);
192+
193+
ui.setFrames(frames, numberOfFrames);
194+
195+
ui.setOverlays(overlays, numberOfOverlays);
196+
197+
// Inital UI takes care of initalising the display too.
198+
ui.init();
199+
200+
Serial.println("");
201+
202+
updateData(&display);
203+
}
204+
205+
void loop() {
206+
207+
if (millis() - timeSinceLastWUpdate > (1000L*UPDATE_INTERVAL_SECS)) {
208+
setReadyForWeatherUpdate();
209+
timeSinceLastWUpdate = millis();
210+
}
211+
212+
if (readyForWeatherUpdate && ui.getUiState()->frameState == FIXED) {
213+
updateData(&display);
214+
}
215+
216+
int remainingTimeBudget = ui.update();
217+
218+
if (remainingTimeBudget > 0) {
219+
// You can do some work here
220+
// Don't do stuff if you are below your
221+
// time budget.
222+
delay(remainingTimeBudget);
223+
}
224+
}
225+
226+
void drawProgress(OLEDDisplay *display, int percentage, String label) {
227+
display->clear();
228+
display->setTextAlignment(TEXT_ALIGN_CENTER);
229+
display->setFont(ArialMT_Plain_10);
230+
display->drawString(64, 10, label);
231+
display->drawProgressBar(2, 28, 124, 10, percentage);
232+
display->display();
233+
}
234+
235+
void updateData(OLEDDisplay *display) {
236+
drawProgress(display, 10, "Updating time...");
237+
drawProgress(display, 30, "Updating weather...");
238+
drawProgress(display, 50, "Updating forecasts...");
239+
forecastClient.setMetric(IS_METRIC);
240+
forecastClient.setLanguage(VISUAL_CROSSING_LANGUAGE);
241+
forecastClient.updateForecasts(forecasts, currentWeather, VISUAL_CROSSING_KEY, VISUAL_CROSSING_LOCATION, MAX_FORECASTS);
242+
243+
readyForWeatherUpdate = false;
244+
drawProgress(display, 100, "Done...");
245+
delay(1000);
246+
}
247+
248+
void drawDateTime(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
249+
now = time(nullptr);
250+
struct tm* timeInfo;
251+
timeInfo = localtime(&now);
252+
char buff[16];
253+
254+
display->setTextAlignment(TEXT_ALIGN_CENTER);
255+
display->setFont(ArialMT_Plain_10);
256+
String date = WDAY_NAMES[timeInfo->tm_wday];
257+
258+
if(dateOrder == DMY) sprintf_P(buff, PSTR("%s, %02d/%02d/%04d"), WDAY_NAMES[timeInfo->tm_wday].c_str(), timeInfo->tm_mday, timeInfo->tm_mon+1, timeInfo->tm_year + 1900);
259+
else if (dateOrder == MDY) sprintf_P(buff, PSTR("%s, %d/%d/%04d"), WDAY_NAMES[timeInfo->tm_wday].c_str(), timeInfo->tm_mon+1, timeInfo->tm_mday, timeInfo->tm_year + 1900);
260+
else if (dateOrder == YMD) sprintf_P(buff, PSTR("%s, %04d/%02d/%02d"), WDAY_NAMES[timeInfo->tm_wday].c_str(), timeInfo->tm_year + 1900, timeInfo->tm_mday, timeInfo->tm_mon+1);
261+
display->drawString(64 + x, 5 + y, String(buff));
262+
display->setFont(ArialMT_Plain_24);
263+
264+
if(IS_24HOUR) sprintf_P(buff, PSTR("%02d:%02d:%02d"), timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec);
265+
else {
266+
uint8_t hour;
267+
hour = timeInfo->tm_hour % 12;
268+
if(hour == 0) hour = 12;
269+
sprintf_P(buff, PSTR("%d:%02d:%02d"), hour, timeInfo->tm_min, timeInfo->tm_sec);
270+
}
271+
display->drawString(64 + x, 15 + y, String(buff));
272+
display->setTextAlignment(TEXT_ALIGN_LEFT);
273+
}
274+
275+
void drawCurrentWeather(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
276+
display->setFont(ArialMT_Plain_10);
277+
display->setTextAlignment(TEXT_ALIGN_CENTER);
278+
display->drawString(64 + x, 38 + y, currentWeather.conditions);
279+
280+
display->setFont(ArialMT_Plain_24);
281+
display->setTextAlignment(TEXT_ALIGN_LEFT);
282+
String temp = String(currentWeather.temp, (IS_METRIC ? 1 : 0)) + (IS_METRIC ? "°C" : "°F");
283+
display->drawString(60 + x, 5 + y, temp);
284+
285+
display->setFont(Meteocons_Plain_36);
286+
display->setTextAlignment(TEXT_ALIGN_CENTER);
287+
display->drawString(32 + x, 0 + y, currentWeather.iconMeteoCon);
288+
}
289+
290+
void drawForecast(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
291+
time_t observationTimestamp = forecasts[0].observationTime;
292+
struct tm* obsTimeInfo;
293+
obsTimeInfo = localtime(&observationTimestamp);
294+
now = time(nullptr);
295+
struct tm* curTimeInfo;
296+
curTimeInfo = localtime(&now);
297+
uint8_t start;
298+
299+
// After 1800, stop showing todays forecast
300+
if (curTimeInfo->tm_mday == obsTimeInfo->tm_mday && curTimeInfo->tm_hour > 18) start = 1;
301+
else start = 0;
302+
303+
drawForecastDetails(display, x, y, start);
304+
drawForecastDetails(display, x + 44, y, start+1);
305+
drawForecastDetails(display, x + 88, y, start+2);
306+
}
307+
308+
void drawForecastDetails(OLEDDisplay *display, int x, int y, int dayIndex) {
309+
time_t observationTimestamp = forecasts[dayIndex].observationTime;
310+
struct tm* timeInfo;
311+
timeInfo = localtime(&observationTimestamp);
312+
display->setTextAlignment(TEXT_ALIGN_CENTER);
313+
display->setFont(ArialMT_Plain_10);
314+
display->drawString(x + 20, y, WDAY_NAMES[timeInfo->tm_wday]);
315+
316+
display->setFont(Meteocons_Plain_21);
317+
display->drawString(x + 20, y + 12, forecasts[dayIndex].iconMeteoCon);
318+
String temp = String(forecasts[dayIndex].tempMax, 0) + (IS_METRIC ? "°C" : "°F");
319+
display->setFont(ArialMT_Plain_10);
320+
display->drawString(x + 20, y + 34, temp);
321+
display->setTextAlignment(TEXT_ALIGN_LEFT);
322+
}
323+
324+
void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state) {
325+
now = time(nullptr);
326+
struct tm* timeInfo;
327+
timeInfo = localtime(&now);
328+
char buff[14];
329+
330+
if(IS_24HOUR) sprintf_P(buff, PSTR("%02d:%02d"), timeInfo->tm_hour, timeInfo->tm_min);
331+
else {
332+
uint8_t hour;
333+
hour = timeInfo->tm_hour % 12;
334+
if(hour == 0) hour = 12;
335+
sprintf_P(buff, PSTR("%d:%02d"), hour, timeInfo->tm_min);
336+
}
337+
338+
display->setColor(WHITE);
339+
display->setFont(ArialMT_Plain_10);
340+
display->setTextAlignment(TEXT_ALIGN_LEFT);
341+
display->drawString(0, 54, String(buff));
342+
display->setTextAlignment(TEXT_ALIGN_RIGHT);
343+
String temp = String(currentWeather.temp, (IS_METRIC ? 1 : 0)) + (IS_METRIC ? "°C" : "°F");
344+
display->drawString(128, 54, temp);
345+
display->drawHorizontalLine(0, 52, 128);
346+
}
347+
348+
void setReadyForWeatherUpdate() {
349+
Serial.println("Setting readyForUpdate to true");
350+
readyForWeatherUpdate = true;
351+
}

0 commit comments

Comments
 (0)