· 4 years ago · Jun 26, 2021, 05:42 PM
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 the library:
27#include <Wire.h>
28#include <LiquidCrystal_I2C.h>
29
30#include <Arduino.h>
31
32#include <ESPWiFi.h>
33#include <ESPHTTPClient.h>
34#include <JsonListener.h>
35
36// time
37#include <time.h> // time() ctime()
38#include <sys/time.h> // struct timeval
39#include <coredecls.h> // settimeofday_cb()
40
41#include "SSD1306Wire.h"
42#include "OLEDDisplayUi.h"
43#include "Wire.h"
44#include "OpenWeatherMapCurrent.h"
45#include "OpenWeatherMapForecast.h"
46#include "WeatherStationFonts.h"
47#include "WeatherStationImages.h"
48
49
50/***************************
51 Begin Settings
52 **************************/
53
54// Create lcd object of class LiquidCrystal_I2C:
55LiquidCrystal_I2C lcd = LiquidCrystal_I2C(0x27, 16, 2); // Change to (0x27,20,4) for 20x4 LCD.
56// Make custom characters:
57byte Heart[] = {
58 B00000,
59 B01010,
60 B11111,
61 B11111,
62 B01110,
63 B00100,
64 B00000,
65 B00000
66};
67byte Bell[] = {
68 B00100,
69 B01110,
70 B01110,
71 B01110,
72 B11111,
73 B00000,
74 B00100,
75 B00000
76};
77
78// WIFI
79const char* WIFI_SSID = "WiFi-2.4-5EE9";
80const char* WIFI_PWD = "wery39djk57zj";
81
82#define TZ 1 // (utc+) TZ in hours
83#define DST_MN 60 // use 60mn for summer time in some countries
84
85// Setup
86const int UPDATE_INTERVAL_SECS = 20 * 60; // Update every 20 minutes
87
88// Display Settings
89const int I2C_DISPLAY_ADDRESS = 0x3C;
90const int SDA_PIN = D3;
91const int SDC_PIN = D5;
92
93// OpenWeatherMap Settings
94// Sign up here to get an API key:
95// https://docs.thingpulse.com/how-tos/openweathermap-key/
96String OPEN_WEATHER_MAP_APP_ID = "bb11c3325f74697d6ad4c282e7107c46";
97/*
98 Go to https://openweathermap.org/find?q= and search for a location. Go through the
99 result set and select the entry closest to the actual location you want to display
100 data for. It'll be a URL like https://openweathermap.org/city/2657896. The number
101 at the end is what you assign to the constant below.
102*/
103String OPEN_WEATHER_MAP_LOCATION_ID = "2800931";
104
105// Pick a language code from this list:
106// Arabic - ar, Bulgarian - bg, Catalan - ca, Czech - cz, German - de, Greek - el,
107// English - en, Persian (Farsi) - fa, Finnish - fi, French - fr, Galician - gl,
108// Croatian - hr, Hungarian - hu, Italian - it, Japanese - ja, Korean - kr,
109// Latvian - la, Lithuanian - lt, Macedonian - mk, Dutch - nl, Polish - pl,
110// Portuguese - pt, Romanian - ro, Russian - ru, Swedish - se, Slovak - sk,
111// Slovenian - sl, Spanish - es, Turkish - tr, Ukrainian - ua, Vietnamese - vi,
112// Chinese Simplified - zh_cn, Chinese Traditional - zh_tw.
113String OPEN_WEATHER_MAP_LANGUAGE = "nl";
114const uint8_t MAX_FORECASTS = 4;
115
116const boolean IS_METRIC = true;
117
118// Adjust according to your language
119const String WDAY_NAMES[] = {"ZON", "MAA", "DIN", "WOE", "DON", "VRI", "ZAT"};
120const String MONTH_NAMES[] = {"JAN", "FEB", "MAR", "APR", "MEI", "JUN", "JUL", "AUG", "SEP", "OKT", "NOV", "DEC"};
121
122/***************************
123 End Settings
124 **************************/
125// Initialize the oled display for address 0x3c
126// sda-pin=14 and sdc-pin=12
127SSD1306Wire display(I2C_DISPLAY_ADDRESS, SDA_PIN, SDC_PIN);
128OLEDDisplayUi ui( &display );
129
130OpenWeatherMapCurrentData currentWeather;
131OpenWeatherMapCurrent currentWeatherClient;
132
133OpenWeatherMapForecastData forecasts[MAX_FORECASTS];
134OpenWeatherMapForecast forecastClient;
135
136#define TZ_MN ((TZ)*60)
137#define TZ_SEC ((TZ)*3600)
138#define DST_SEC ((DST_MN)*60)
139time_t now;
140
141// flag changed in the ticker function every 10 minutes
142bool readyForWeatherUpdate = false;
143
144String lastUpdate = "--";
145
146long timeSinceLastWUpdate = 0;
147
148//declaring prototypes
149void drawProgress(OLEDDisplay *display, int percentage, String label);
150void updateData(OLEDDisplay *display);
151void drawDateTime(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
152void drawCurrentWeather(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
153void drawForecast(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
154void drawForecastDetails(OLEDDisplay *display, int x, int y, int dayIndex);
155void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state);
156void setReadyForWeatherUpdate();
157
158
159// Add frames
160// this array keeps function pointers to all frames
161// frames are the single views that slide from right to left
162FrameCallback frames[] = { drawDateTime, drawCurrentWeather, drawForecast };
163int numberOfFrames = 3;
164
165OverlayCallback overlays[] = { drawHeaderOverlay };
166int numberOfOverlays = 1;
167
168void setup() {
169 // Initialize LCD and turn on the backlight:
170 lcd.begin();
171 // Create new characters:
172 lcd.createChar(0, Heart);
173 lcd.createChar(1, Bell);
174 lcd.setCursor(0, 0);
175 lcd.print("test");
176 lcd.setCursor(0, 1);
177 lcd.write(0);
178 lcd.setCursor(15, 1);
179 lcd.write(1);
180
181 Serial.begin(115200);
182 Serial.println("start");
183 Serial.println();
184
185 // initialize display
186 display.init();
187 display.clear();
188 display.display();
189 Serial.println("Display_init");
190
191 display.flipScreenVertically();
192 display.setFont(ArialMT_Plain_10);
193 display.setTextAlignment(TEXT_ALIGN_CENTER);
194 display.setContrast(255);
195
196 WiFi.begin(WIFI_SSID, WIFI_PWD);
197
198 int counter = 0;
199 while (WiFi.status() != WL_CONNECTED) {
200 delay(500);
201 Serial.print(".");
202 display.clear();
203 display.drawString(64, 10, "Connecting to WiFi");
204 display.drawXbm(46, 30, 8, 8, counter % 3 == 0 ? activeSymbole : inactiveSymbole);
205 display.drawXbm(60, 30, 8, 8, counter % 3 == 1 ? activeSymbole : inactiveSymbole);
206 display.drawXbm(74, 30, 8, 8, counter % 3 == 2 ? activeSymbole : inactiveSymbole);
207 display.display();
208
209 counter++;
210 }
211 // Get time from network time service
212 configTime(TZ_SEC, DST_SEC, "pool.ntp.org");
213 ui.setTargetFPS(30);
214
215 ui.setActiveSymbol(activeSymbole);
216 ui.setInactiveSymbol(inactiveSymbole);
217
218 // You can change this to
219 // TOP, LEFT, BOTTOM, RIGHT
220 ui.setIndicatorPosition(BOTTOM);
221
222 // Defines where the first frame is located in the bar.
223 ui.setIndicatorDirection(LEFT_RIGHT);
224
225 // You can change the transition that is used
226 // SLIDE_LEFT, SLIDE_RIGHT, SLIDE_TOP, SLIDE_DOWN
227 ui.setFrameAnimation(SLIDE_LEFT);
228
229 ui.setFrames(frames, numberOfFrames);
230
231 ui.setOverlays(overlays, numberOfOverlays);
232
233 // Inital UI takes care of initalising the display too.
234 ui.init();
235
236 //Serial.println("");
237
238 updateData(&display);
239
240}
241
242void loop() {
243
244 if (millis() - timeSinceLastWUpdate > (1000L * UPDATE_INTERVAL_SECS)) {
245 setReadyForWeatherUpdate();
246 timeSinceLastWUpdate = millis();
247 }
248
249 if (readyForWeatherUpdate && ui.getUiState()->frameState == FIXED) {
250 updateData(&display);
251 }
252
253 lcd.clear();
254 lcd.setCursor(3,0);
255 lcd.print("test1");
256 lcd.setCursor(10,0);
257 lcd.print("test2");
258 lcd.setCursor(3,1);
259 lcd.print("test3");
260 lcd.setCursor(11,1);
261 lcd.print("test4");
262 delay(100);
263 int remainingTimeBudget = ui.update();
264}
265
266void drawProgress(OLEDDisplay *display, int percentage, String label) {
267 display->clear();
268 display->setTextAlignment(TEXT_ALIGN_CENTER);
269 display->setFont(ArialMT_Plain_10);
270 display->drawString(64, 10, label);
271 display->drawProgressBar(2, 28, 124, 10, percentage);
272 display->display();
273}
274
275void updateData(OLEDDisplay *display) {
276 drawProgress(display, 10, "Updating time...");
277 drawProgress(display, 30, "Updating weather...");
278 currentWeatherClient.setMetric(IS_METRIC);
279 currentWeatherClient.setLanguage(OPEN_WEATHER_MAP_LANGUAGE);
280 currentWeatherClient.updateCurrentById(¤tWeather, OPEN_WEATHER_MAP_APP_ID, OPEN_WEATHER_MAP_LOCATION_ID);
281 drawProgress(display, 50, "Updating forecasts...");
282 forecastClient.setMetric(IS_METRIC);
283 forecastClient.setLanguage(OPEN_WEATHER_MAP_LANGUAGE);
284 uint8_t allowedHours[] = {12};
285 forecastClient.setAllowedHours(allowedHours, sizeof(allowedHours));
286 forecastClient.updateForecastsById(forecasts, OPEN_WEATHER_MAP_APP_ID, OPEN_WEATHER_MAP_LOCATION_ID, MAX_FORECASTS);
287
288 readyForWeatherUpdate = false;
289 drawProgress(display, 100, "Done...");
290 delay(1000);
291}
292
293
294
295void drawDateTime(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
296 now = time(nullptr);
297 struct tm* timeInfo;
298 timeInfo = localtime(&now);
299 char buff[16];
300
301
302 display->setTextAlignment(TEXT_ALIGN_CENTER);
303 display->setFont(ArialMT_Plain_10);
304 String date = WDAY_NAMES[timeInfo->tm_wday];
305
306 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);
307 display->drawString(64 + x, 5 + y, String(buff));
308 display->setFont(ArialMT_Plain_24);
309
310 sprintf_P(buff, PSTR("%02d:%02d:%02d"), timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec);
311 display->drawString(64 + x, 15 + y, String(buff));
312 display->setTextAlignment(TEXT_ALIGN_LEFT);
313}
314
315void drawCurrentWeather(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
316 display->setFont(ArialMT_Plain_10);
317 display->setTextAlignment(TEXT_ALIGN_CENTER);
318 display->drawString(64 + x, 38 + y, currentWeather.description);
319
320 display->setFont(ArialMT_Plain_24);
321 display->setTextAlignment(TEXT_ALIGN_LEFT);
322 String temp = String(currentWeather.temp, 1) + (IS_METRIC ? "°C" : "°F");
323 display->drawString(60 + x, 5 + y, temp);
324
325 display->setFont(Meteocons_Plain_36);
326 display->setTextAlignment(TEXT_ALIGN_CENTER);
327 display->drawString(32 + x, 0 + y, currentWeather.iconMeteoCon);
328}
329
330
331void drawForecast(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
332 drawForecastDetails(display, x, y, 0);
333 drawForecastDetails(display, x + 44, y, 1);
334 drawForecastDetails(display, x + 88, y, 2);
335}
336
337void drawForecastDetails(OLEDDisplay *display, int x, int y, int dayIndex) {
338 time_t observationTimestamp = forecasts[dayIndex].observationTime;
339 struct tm* timeInfo;
340 timeInfo = localtime(&observationTimestamp);
341 display->setTextAlignment(TEXT_ALIGN_CENTER);
342 display->setFont(ArialMT_Plain_10);
343 display->drawString(x + 20, y, WDAY_NAMES[timeInfo->tm_wday]);
344
345 display->setFont(Meteocons_Plain_21);
346 display->drawString(x + 20, y + 12, forecasts[dayIndex].iconMeteoCon);
347 String temp = String(forecasts[dayIndex].temp, 0) + (IS_METRIC ? "°C" : "°F");
348 display->setFont(ArialMT_Plain_10);
349 display->drawString(x + 20, y + 34, temp);
350 display->setTextAlignment(TEXT_ALIGN_LEFT);
351}
352
353void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state) {
354 now = time(nullptr);
355 struct tm* timeInfo;
356 timeInfo = localtime(&now);
357 char buff[14];
358 sprintf_P(buff, PSTR("%02d:%02d"), timeInfo->tm_hour, timeInfo->tm_min);
359
360 display->setColor(WHITE);
361 display->setFont(ArialMT_Plain_10);
362 display->setTextAlignment(TEXT_ALIGN_LEFT);
363 display->drawString(0, 54, String(buff));
364 display->setTextAlignment(TEXT_ALIGN_RIGHT);
365 String temp = String(currentWeather.temp, 1) + (IS_METRIC ? "°C" : "°F");
366 display->drawString(128, 54, temp);
367 display->drawHorizontalLine(0, 52, 128);
368}
369
370void setReadyForWeatherUpdate() {
371 Serial.println("Setting readyForUpdate to true");
372 readyForWeatherUpdate = true;
373}
374