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