· 5 years ago · Jun 26, 2020, 06:36 PM
1/**The MIT License (MIT)
2
3Copyright (c) 2018 by Daniel Eichhorn - ThingPulse
4
5Permission is hereby granted, free of charge, to any person obtaining a copy
6of this software and associated documentation files (the "Software"), to deal
7in the Software without restriction, including without limitation the rights
8to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9copies of the Software, and to permit persons to whom the Software is
10furnished to do so, subject to the following conditions:
11
12The above copyright notice and this permission notice shall be included in all
13copies or substantial portions of the Software.
14
15THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21SOFTWARE.
22
23See more at https://thingpulse.com
24*/
25
26#include <Arduino.h>
27
28#include <ESPWiFi.h>
29#include <ESPHTTPClient.h>
30#include <JsonListener.h>
31
32// time
33#include <time.h> // time() ctime()
34#include <sys/time.h> // struct timeval
35#include <coredecls.h> // settimeofday_cb()
36
37#include "SSD1306Wire.h"
38#include "OLEDDisplayUi.h"
39#include "Wire.h"
40#include "OpenWeatherMapCurrent.h"
41#include "OpenWeatherMapForecast.h"
42#include "WeatherStationFonts.h"
43#include "WeatherStationImages.h"
44#include <WiFiUdp.h>
45
46#include <NTPClient.h>
47
48/***************************
49 * Begin Settings
50 **************************/
51
52// WIFI
53const char* WIFI_SSID = "Secret";
54const char* WIFI_PWD = "Secret";
55
56#define TZ 4 // (utc+) TZ in hours -- set to CST
57#define DST_MN 60 // use 60mn for summer time in some countries
58
59// Setup
60const int UPDATE_INTERVAL_SECS = 20 * 60; // Update every 20 minutes
61
62// Display Settings
63const int I2C_DISPLAY_ADDRESS = 0x3c;
64#if defined(ESP8266)
65const int SDA_PIN = D3;
66const int SDC_PIN = D4;
67#else
68const int SDA_PIN = 5; //D3;
69const int SDC_PIN = 4; //D4;
70#endif
71
72
73// OpenWeatherMap Settings
74// Sign up here to get an API key:
75// https://docs.thingpulse.com/how-tos/openweathermap-key/
76String OPEN_WEATHER_MAP_APP_ID = "secret";
77/*
78Go to https://openweathermap.org/find?q= and search for a location. Go through the
79result set and select the entry closest to the actual location you want to display
80data for. It'll be a URL like https://openweathermap.org/city/2657896. The number
81at the end is what you assign to the constant below.
82 */
83String OPEN_WEATHER_MAP_LOCATION_ID = "secret";
84
85// Pick a language code from this list:
86// Arabic - ar, Bulgarian - bg, Catalan - ca, Czech - cz, German - de, Greek - el,
87// English - en, Persian (Farsi) - fa, Finnish - fi, French - fr, Galician - gl,
88// Croatian - hr, Hungarian - hu, Italian - it, Japanese - ja, Korean - kr,
89// Latvian - la, Lithuanian - lt, Macedonian - mk, Dutch - nl, Polish - pl,
90// Portuguese - pt, Romanian - ro, Russian - ru, Swedish - se, Slovak - sk,
91// Slovenian - sl, Spanish - es, Turkish - tr, Ukrainian - ua, Vietnamese - vi,
92// Chinese Simplified - zh_cn, Chinese Traditional - zh_tw.
93String OPEN_WEATHER_MAP_LANGUAGE = "en";
94const uint8_t MAX_FORECASTS = 4;
95
96const boolean IS_METRIC = true;
97
98// Adjust according to your language
99const String WDAY_NAMES[] = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"};
100const String MONTH_NAMES[] = {"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
101
102/***************************
103 * End Settings
104 **************************/
105 // Initialize the oled display for address 0x3c
106 // sda-pin=14 and sdc-pin=12
107 SSD1306Wire display(I2C_DISPLAY_ADDRESS, SDA_PIN, SDC_PIN);
108 OLEDDisplayUi ui( &display );
109
110OpenWeatherMapCurrentData currentWeather;
111OpenWeatherMapCurrent currentWeatherClient;
112
113OpenWeatherMapForecastData forecasts[MAX_FORECASTS];
114OpenWeatherMapForecast forecastClient;
115
116#define TZ_MN ((TZ)*60)
117#define TZ_SEC ((TZ)*3600)
118#define DST_SEC ((DST_MN)*60)
119time_t now;
120
121// flag changed in the ticker function every 10 minutes
122bool readyForWeatherUpdate = false;
123
124String lastUpdate = "--";
125
126long timeSinceLastWUpdate = 0;
127
128//declaring prototypes
129void drawProgress(OLEDDisplay *display, int percentage, String label);
130void updateData(OLEDDisplay *display);
131void drawDateTime(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
132void drawCurrentWeather(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
133void drawForecast(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
134void drawForecastDetails(OLEDDisplay *display, int x, int y, int dayIndex);
135void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state);
136void setReadyForWeatherUpdate();
137
138const long utcOffsetInSeconds = 3600;
139
140char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
141
142// Define NTP Client to get time
143WiFiUDP ntpUDP;
144NTPClient timeClient(ntpUDP);
145
146// Add frames
147// this array keeps function pointers to all frames
148// frames are the single views that slide from right to left
149FrameCallback frames[] = { drawDateTime, drawCurrentWeather, drawForecast };
150int numberOfFrames = 3;
151
152OverlayCallback overlays[] = { drawHeaderOverlay };
153int numberOfOverlays = 1;
154
155void setup() {
156 Serial.begin(115200);
157 Serial.println();
158 Serial.println();
159
160 // initialize dispaly
161 display.init();
162 display.clear();
163 display.display();
164
165 //display.flipScreenVertically();
166 display.setFont(ArialMT_Plain_10);
167 display.setTextAlignment(TEXT_ALIGN_CENTER);
168 display.setContrast(255);
169
170 WiFi.begin(WIFI_SSID, WIFI_PWD);
171
172 int counter = 0;
173 while (WiFi.status() != WL_CONNECTED) {
174 delay(500);
175 Serial.print(".");
176 display.clear();
177 display.drawString(64, 10, "Connecting to WiFi");
178 display.drawXbm(46, 30, 8, 8, counter % 3 == 0 ? activeSymbole : inactiveSymbole);
179 display.drawXbm(60, 30, 8, 8, counter % 3 == 1 ? activeSymbole : inactiveSymbole);
180 display.drawXbm(74, 30, 8, 8, counter % 3 == 2 ? activeSymbole : inactiveSymbole);
181 display.display();
182
183 counter++;
184 timeClient.begin();
185 }
186 // Get time from network time service
187 configTime(TZ_SEC, DST_SEC, "pool.ntp.org");
188
189 ui.setTargetFPS(30);
190
191 ui.setActiveSymbol(activeSymbole);
192 ui.setInactiveSymbol(inactiveSymbole);
193
194 // You can change this to
195 // TOP, LEFT, BOTTOM, RIGHT
196 ui.setIndicatorPosition(BOTTOM);
197
198 // Defines where the first frame is located in the bar.
199 ui.setIndicatorDirection(LEFT_RIGHT);
200
201 // You can change the transition that is used
202 // SLIDE_LEFT, SLIDE_RIGHT, SLIDE_TOP, SLIDE_DOWN
203 ui.setFrameAnimation(SLIDE_LEFT);
204
205 ui.setFrames(frames, numberOfFrames);
206
207 ui.setOverlays(overlays, numberOfOverlays);
208
209 // Inital UI takes care of initalising the display too.
210 ui.init();
211
212 Serial.println("");
213
214 updateData(&display);
215
216}
217
218void loop() {
219 timeClient.update();
220
221 if (millis() - timeSinceLastWUpdate > (1000L*UPDATE_INTERVAL_SECS)) {
222 setReadyForWeatherUpdate();
223 timeSinceLastWUpdate = millis();
224 }
225
226 if (readyForWeatherUpdate && ui.getUiState()->frameState == FIXED) {
227 updateData(&display);
228 }
229
230 int remainingTimeBudget = ui.update();
231
232 if (remainingTimeBudget > 0) {
233 // You can do some work here
234 // Don't do stuff if you are below your
235 // time budget.
236 delay(remainingTimeBudget);
237 }
238
239
240}
241
242void drawProgress(OLEDDisplay *display, int percentage, String label) {
243 display->clear();
244 display->setTextAlignment(TEXT_ALIGN_CENTER);
245 display->setFont(ArialMT_Plain_10);
246 display->drawString(64, 10, label);
247 display->drawProgressBar(2, 28, 124, 10, percentage);
248 display->display();
249}
250
251void updateData(OLEDDisplay *display) {
252 drawProgress(display, 10, "Updating time...");
253 drawProgress(display, 30, "Updating weather...");
254 currentWeatherClient.setMetric(IS_METRIC);
255 currentWeatherClient.setLanguage(OPEN_WEATHER_MAP_LANGUAGE);
256 currentWeatherClient.updateCurrentById(¤tWeather, OPEN_WEATHER_MAP_APP_ID, OPEN_WEATHER_MAP_LOCATION_ID);
257 drawProgress(display, 50, "Updating forecasts...");
258 forecastClient.setMetric(IS_METRIC);
259 forecastClient.setLanguage(OPEN_WEATHER_MAP_LANGUAGE);
260 uint8_t allowedHours[] = {12};
261 forecastClient.setAllowedHours(allowedHours, sizeof(allowedHours));
262 forecastClient.updateForecastsById(forecasts, OPEN_WEATHER_MAP_APP_ID, OPEN_WEATHER_MAP_LOCATION_ID, MAX_FORECASTS);
263
264 readyForWeatherUpdate = false;
265 drawProgress(display, 100, "Done...");
266 delay(1000);
267}
268
269
270
271void drawDateTime(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
272 timeClient.update();
273 now = time(nullptr);
274 struct tm* timeInfo;
275 timeInfo = localtime(&now);
276 char buff[16];
277
278
279 display->setTextAlignment(TEXT_ALIGN_CENTER);
280 display->setFont(ArialMT_Plain_10);
281 String date = WDAY_NAMES[timeInfo->tm_wday];
282
283 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);
284 display->drawString(64 + x, 5 + y, String(buff));
285 display->setFont(ArialMT_Plain_24);
286
287 sprintf_P(buff, PSTR("%02d:%02d:%02d"), timeClient.getHours(), timeClient.getMinutes(), timeClient.getSeconds() );
288 display->drawString(64 + x, 15 + y, String(buff));
289 display->setTextAlignment(TEXT_ALIGN_LEFT);
290}
291
292void drawCurrentWeather(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
293 display->setFont(ArialMT_Plain_10);
294 display->setTextAlignment(TEXT_ALIGN_CENTER);
295 display->drawString(64 + x, 38 + y, currentWeather.description);
296
297 display->setFont(ArialMT_Plain_24);
298 display->setTextAlignment(TEXT_ALIGN_LEFT);
299 String temp = String(currentWeather.temp, 1) + (IS_METRIC ? "°C" : "°F");
300 display->drawString(60 + x, 5 + y, temp);
301
302 display->setFont(Meteocons_Plain_36);
303 display->setTextAlignment(TEXT_ALIGN_CENTER);
304 display->drawString(32 + x, 0 + y, currentWeather.iconMeteoCon);
305}
306
307
308void drawForecast(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
309 drawForecastDetails(display, x, y, 0);
310 drawForecastDetails(display, x + 44, y, 1);
311 drawForecastDetails(display, x + 88, y, 2);
312}
313
314void drawForecastDetails(OLEDDisplay *display, int x, int y, int dayIndex) {
315 time_t observationTimestamp = forecasts[dayIndex].observationTime;
316 struct tm* timeInfo;
317 timeInfo = localtime(&observationTimestamp);
318 display->setTextAlignment(TEXT_ALIGN_CENTER);
319 display->setFont(ArialMT_Plain_10);
320 display->drawString(x + 20, y, WDAY_NAMES[timeInfo->tm_wday]);
321
322 display->setFont(Meteocons_Plain_21);
323 display->drawString(x + 20, y + 12, forecasts[dayIndex].iconMeteoCon);
324 String temp = String(forecasts[dayIndex].temp, 0) + (IS_METRIC ? "°C" : "°F");
325 display->setFont(ArialMT_Plain_10);
326 display->drawString(x + 20, y + 34, temp);
327 display->setTextAlignment(TEXT_ALIGN_LEFT);
328}
329
330void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state) {
331 now = time(nullptr);
332 struct tm* timeInfo;
333 timeInfo = localtime(&now);
334 char buff[14];
335 sprintf_P(buff, PSTR("%02d:%02d"), timeInfo->tm_hour, timeInfo->tm_min);
336
337 display->setColor(WHITE);
338 display->setFont(ArialMT_Plain_10);
339 display->setTextAlignment(TEXT_ALIGN_LEFT);
340 display->drawString(0, 54, String(buff));
341 display->setTextAlignment(TEXT_ALIGN_RIGHT);
342 String temp = String(currentWeather.temp, 1) + (IS_METRIC ? "°C" : "°F");
343 display->drawString(128, 54, temp);
344 display->drawHorizontalLine(0, 52, 128);
345}
346
347void setReadyForWeatherUpdate() {
348 Serial.println("Setting readyForUpdate to true");
349 readyForWeatherUpdate = true;
350}