Created
November 16, 2018 08:52
-
-
Save Bolukan/09de24a84fc79be1396378b7cfbd4385 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Last udpate: Added enumerated weather types, improved efficiency | |
* Last update: 07-Aug-17, with improved forecast rules | |
* | |
* ESP32 and BMP180 or BME280 and OLED SH1106 or SSD1306 display Weather Forecaster | |
* Using air pressure changes to predict weather based on an advanced set of forecasting rules. | |
* The 'MIT License (MIT) Copyright (c) 2016 by David Bird'. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated | |
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, | |
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the | |
* following conditions: | |
* The above copyright ('as annotated') notice and this permission notice shall be included in all copies or substantial portions of the Software and where the | |
* software use is visible to an end-user. | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR OR COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
* See more at http://dsbird.org.uk | |
*/ | |
#include "SSD1306.h" //https://github.com/squix78/esp8266-oled-ssd1306 | |
#include "OLEDDisplayUi.h" //https://github.com/squix78/esp8266-oled-ssd1306 | |
#include <WiFi.h> | |
#include "time.h" | |
#include <Wire.h> | |
#include <Adafruit_BME280.h> | |
//#include <Adafruit_BMP085.h> // If using BMP180 or BMP085 and update line: 173/174 accordingly e.g. BME and BME or BMP and BMP | |
#define icon_width 40 | |
#define icon_height 40 | |
// Define each of the *icons for display | |
const char rain_icon[] PROGMEM = { | |
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
0x81, 0xFF, 0xFF, 0xFF, 0x3F, 0x04, 0xFE, 0xFF, 0xFF, 0xDF, 0xF0, 0xFC, | |
0xFF, 0xFF, 0xE7, 0xFF, 0xFB, 0xFF, 0xFF, 0xFB, 0xFF, 0xF3, 0xFF, 0xFF, | |
0xFD, 0xFF, 0xE7, 0xFF, 0xFF, 0xFD, 0xFF, 0x0F, 0xFE, 0x0F, 0xF8, 0xFF, | |
0x8F, 0xFC, 0xE7, 0xFB, 0xFF, 0xC7, 0xF9, 0xF7, 0xE3, 0xFF, 0xC3, 0xF3, | |
0xF3, 0xDF, 0xFF, 0xE8, 0xE7, 0xF9, 0xFF, 0xFF, 0xFE, 0xEF, 0xFD, 0xFF, | |
0xFF, 0xFF, 0xE7, 0xF9, 0xFF, 0xFF, 0xFF, 0xF7, 0xFB, 0xFF, 0xFF, 0xFF, | |
0xF3, 0xFB, 0xFF, 0xFF, 0xFF, 0xF9, 0xF7, 0xFF, 0xFF, 0xFF, 0xFC, 0xCF, | |
0xFF, 0xFF, 0x3F, 0xFE, 0x3F, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0x7D, 0xBF, | |
0xEF, 0xFF, 0xFF, 0xBE, 0xDF, 0xF7, 0xFF, 0x7F, 0xDF, 0xEF, 0xFB, 0xFF, | |
0xBF, 0xEF, 0xF7, 0xFD, 0xFF, 0xDF, 0xF7, 0xFB, 0xFE, 0xFF, 0xEF, 0xFB, | |
0x7D, 0xFF, 0xFF, 0xF7, 0xFD, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; | |
const char sunny_icon[] PROGMEM = { | |
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xFF, 0xFF, | |
0xE3, 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xFF, | |
0xFF, 0xFF, 0xFF, 0xE3, 0xFF, 0xFD, 0xDF, 0xFF, 0xE3, 0xFF, 0xF8, 0x8F, | |
0xFF, 0xE3, 0x7F, 0xF0, 0x07, 0xFF, 0xE3, 0x3F, 0xF8, 0x0F, 0xFE, 0xFF, | |
0x1F, 0xFC, 0x1F, 0x7C, 0x00, 0x0E, 0xFE, 0x3F, 0x18, 0x00, 0x1C, 0xFF, | |
0x7F, 0xCC, 0xFF, 0xB1, 0xFF, 0xFF, 0xE6, 0xFF, 0xE7, 0xFF, 0xFF, 0xF3, | |
0xFF, 0xCF, 0xFF, 0xFF, 0xF1, 0xFF, 0x9F, 0xFF, 0xFF, 0xF9, 0xFF, 0x9F, | |
0xFF, 0xFF, 0xF9, 0xFF, 0x9F, 0xFF, 0xFF, 0xF9, 0xFF, 0x9F, 0xFF, 0x01, | |
0xF9, 0xFF, 0x9F, 0x80, 0x01, 0xF9, 0xFF, 0x9F, 0x80, 0x01, 0xF9, 0xFF, | |
0x9F, 0x80, 0xFF, 0xF9, 0xFF, 0x9F, 0xFF, 0xFF, 0xF1, 0xFF, 0x8F, 0xFF, | |
0xFF, 0xF1, 0xFF, 0x8F, 0xFF, 0xFF, 0xE3, 0xFF, 0xE7, 0xFF, 0xFF, 0xC7, | |
0xFF, 0xF3, 0xFF, 0xFF, 0x8D, 0xFF, 0xD8, 0xFF, 0xFF, 0x38, 0x00, 0x8C, | |
0xFF, 0x7F, 0x70, 0x00, 0x07, 0xFF, 0x3F, 0xF8, 0xFF, 0x0F, 0xFE, 0x1F, | |
0xFC, 0xE3, 0x1F, 0xFC, 0x0F, 0xFE, 0xE3, 0x3F, 0xF8, 0x07, 0xFF, 0xE3, | |
0x7F, 0xF0, 0x8F, 0xFF, 0xE3, 0xFF, 0xF8, 0xDF, 0xFF, 0xE3, 0xFF, 0xFD, | |
0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xFF, 0xFF, | |
0xE3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; | |
const char mostlysunny_icon[] PROGMEM = { | |
0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, 0xFF, 0xFF, 0xFD, 0x7E, | |
0xFF, 0xFF, 0xFF, 0xFB, 0xBF, 0xEF, 0xFF, 0xFF, 0x17, 0xE0, 0xF7, 0xFF, | |
0xFF, 0xCF, 0x9F, 0xF9, 0xFF, 0xFF, 0xE6, 0x3F, 0xFD, 0xFF, 0xFF, 0xF5, | |
0x7F, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xFE, 0xFF, 0xFF, 0xF9, 0xFF, 0x00, | |
0xFF, 0xFF, 0xFD, 0x7F, 0x08, 0xFC, 0xFF, 0xFD, 0xBF, 0xE1, 0xF9, 0xFF, | |
0xFD, 0xCF, 0xFF, 0xF7, 0xFF, 0xFD, 0xF7, 0xFF, 0xE7, 0xFF, 0xF9, 0xFB, | |
0xFF, 0xCF, 0xFF, 0xF3, 0xFB, 0xFF, 0x1F, 0xFC, 0x17, 0xF0, 0xFF, 0x1F, | |
0xF9, 0xC7, 0xF7, 0xFF, 0x8F, 0xF3, 0xEF, 0xC7, 0xFF, 0x87, 0xE7, 0xE7, | |
0xBF, 0xFF, 0xD1, 0xCF, 0xF3, 0xFF, 0xFF, 0xFD, 0xDF, 0xFB, 0xFF, 0xFF, | |
0xFF, 0xCF, 0xF3, 0xFF, 0xFF, 0xFF, 0xEF, 0xF7, 0xFF, 0xFF, 0xFF, 0xE7, | |
0xF7, 0xFF, 0xFF, 0xFF, 0xF3, 0xEF, 0xFF, 0xFF, 0xFF, 0xF9, 0x9F, 0xFF, | |
0xFF, 0x7F, 0xFC, 0x7F, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; | |
const char cloudy_icon[] PROGMEM = { | |
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xFF, 0xFF, 0xFF, 0x7F, 0x78, 0xFC, 0xFF, | |
0xFF, 0xBF, 0xFF, 0xF9, 0xFF, 0xFF, 0xCF, 0xFF, 0xF7, 0xFF, 0xFF, 0xF7, | |
0xFF, 0xE7, 0xFF, 0xFF, 0xFB, 0xFF, 0xCF, 0xFF, 0xFF, 0xFB, 0xFF, 0x1F, | |
0xFC, 0x3F, 0xF0, 0xFF, 0xE7, 0xFB, 0xCF, 0xF7, 0xFF, 0xF3, 0xF7, 0xEF, | |
0xCF, 0xFF, 0xF9, 0xEF, 0xF7, 0xBF, 0xFF, 0xFD, 0xCF, 0xF3, 0xFF, 0xFF, | |
0xFD, 0xDF, 0xFB, 0xFF, 0xFF, 0xFF, 0xDF, 0xFB, 0xFF, 0xFF, 0xFF, 0xEF, | |
0xF7, 0xFF, 0xFF, 0xFF, 0xE7, 0xF7, 0xFF, 0xFF, 0xFF, 0xF3, 0xEF, 0xFF, | |
0xFF, 0xFF, 0xF9, 0x9F, 0xFF, 0xFF, 0x7F, 0xFC, 0x7F, 0x00, 0x00, 0x00, | |
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; | |
const char tstorms_icon[] PROGMEM = { | |
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
0x81, 0xFF, 0xFF, 0xFF, 0x3F, 0x04, 0xFE, 0xFF, 0xFF, 0xDF, 0xF0, 0xFC, | |
0xFF, 0xFF, 0xE7, 0xFF, 0xFB, 0xFF, 0xFF, 0xFB, 0xFF, 0xF3, 0xFF, 0xFF, | |
0xFD, 0xFF, 0xE7, 0xFF, 0xFF, 0xFD, 0xFF, 0x0F, 0xFE, 0x0F, 0xF8, 0xFF, | |
0x8F, 0xFC, 0xE7, 0xFB, 0xFF, 0xC7, 0xF9, 0xF7, 0xE3, 0xFF, 0xC3, 0xF3, | |
0xF3, 0xDF, 0xFF, 0xE8, 0xE7, 0xF9, 0xFF, 0xFF, 0xFE, 0xEF, 0xFD, 0xFF, | |
0xFF, 0xFF, 0xE7, 0xF9, 0xFF, 0xFF, 0xFF, 0xF7, 0xFB, 0xFF, 0xFF, 0xFF, | |
0xF3, 0xFB, 0xFF, 0xFF, 0xFF, 0xF9, 0xF7, 0xFF, 0xFF, 0xFF, 0xFC, 0xCF, | |
0xFF, 0xFF, 0x3F, 0xFE, 0x3F, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0x7F, 0x7F, | |
0xFF, 0xFF, 0xFF, 0xBF, 0xBF, 0xFF, 0xFF, 0xFF, 0xDF, 0x8F, 0xFF, 0xFF, | |
0xFF, 0xEF, 0xF7, 0xFF, 0xFF, 0xFF, 0xF7, 0xFB, 0xFF, 0xFF, 0xFF, 0x4F, | |
0xF7, 0xFF, 0xFF, 0xFF, 0xBF, 0xEF, 0xFF, 0xFF, 0xFF, 0xDF, 0xF1, 0xFF, | |
0xFF, 0xFF, 0xDF, 0xFE, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, | |
0x8F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF, 0xF3, 0xFF, | |
0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; | |
String time_str, weather_text, weather_extra_text; | |
int last_reading_hour, reading_hour, hr_cnt; | |
const char* ssid = "yourWIFI_SSID"; | |
const char* password = "yourWIFI_PASSWORD"; | |
enum image_names { // enumerated table used to point to images | |
rain_img, sunny_img, mostlysunny_img, cloudy_img, tstorms_img, | |
} image; | |
// Define and enumerated type and assign values to expected weather types. | |
// These values help to determine the average weather preceeding a 'no-change' forecast e.g. rain, rain then mostlysun = -1 (-1 + -1 + 1) resulting on balance = more rain | |
enum weather_type {unknown = 4, | |
sunny = 2, | |
mostlysunny = 1, | |
cloudy = 0, | |
rain = -1, | |
tstorms = -2 | |
}; | |
enum weather_description {GoodClearWeather, BecomingClearer, | |
NoChange, ClearSpells, ClearingWithin12hrs, ClearingAndColder, | |
GettingWarmer, WarmerIn2daysRainLikely, | |
ExpectRain, WarmerRainWithin36hrs, RainIn18hrs, RainHighWindsClearAndCool, | |
GalesHeavyRainSnowInWinter | |
}; | |
weather_type current_wx; // Enable the current wx to be recorded | |
const char* image_table[] PROGMEM = {rain_icon, sunny_icon, mostlysunny_icon, cloudy_icon, tstorms_icon}; // An array of image icons | |
// An array structure to record pressure, temperaturre, humidity and weather state | |
typedef struct { | |
float pressure; // air pressure at the designated hour | |
float temperature; // temperature at the designated hour | |
float humidity; // humidity at the designated hour | |
weather_type wx_state_1hr; // weather state at 1-hour | |
weather_type wx_state_3hr; // weather state at 3-hour point | |
} wx_record_type; | |
wx_record_type reading[24]; // An array covering 24-hours to enable P, T, % and Wx state to be recorded for every hour | |
int wx_average_1hr, wx_average_3hr; // Indicators of average weather | |
bool look_3hr = true; | |
bool look_1hr = false; | |
SSD1306 display(0x3c, 5,4); // OLED display object definition (address, SDA, SCL) | |
OLEDDisplayUi ui ( &display ); | |
Adafruit_BME280 bme; | |
//Adafruit_BMP085 bme; // If using BMP180 or BMP085 | |
WiFiClient client; // wifi client object | |
#define pressure_offset 3.3 // Used to adjust sensor reading to correct pressure for your location | |
///////////////////////////////////////////////////////////////////////// | |
// What's displayed along the top line | |
void msOverlay(OLEDDisplay *display, OLEDDisplayUiState* state) { | |
display->setTextAlignment(TEXT_ALIGN_LEFT); | |
display->drawString(0,0, time_str.substring(0,8)); //HH:MM:SS Sat 05-07-17 | |
display->setTextAlignment(TEXT_ALIGN_RIGHT); | |
display->drawString(128,0, time_str.substring(9)); | |
display->setTextAlignment(TEXT_ALIGN_LEFT); | |
} | |
// This frame draws a weather icon based on 3-hours of data for the prediction | |
void drawFrame1(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) { | |
float trend = reading[23].pressure - reading[20].pressure; // Trend over the last 3-hours | |
ForecastToImgTxt(get_forecast_text(reading[23].pressure, trend, look_3hr)); // From forecast and trend determine what image to display | |
display->drawXbm(x+0,y+15, icon_width, icon_height, image_table[image]); // Display corresponding image | |
display->drawStringMaxWidth(x+45,y+12,90,String(reading[23].pressure,1)+" hPA"); // Show current air pressure | |
display->drawStringMaxWidth(x+45,y+25,90,String(trend,1)+" "+get_trend_text(trend)); // and pressure trend | |
} | |
// This frame shows a weather description based on 3-hours of data for the prediction | |
void drawFrame2(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) { | |
float trend = reading[23].pressure - reading[20].pressure; // Get current trend over last 3-hours | |
weather_description wx_text = get_forecast_text(reading[23].pressure, trend, look_3hr); // Convert to forecast text based on 3-hours | |
ForecastToImgTxt(wx_text); // Display corresponding text | |
display->setFont(ArialMT_Plain_16); | |
display->drawStringMaxWidth(x+0,y+10,127,weather_text); | |
display->setFont(ArialMT_Plain_10); | |
} | |
// This frame draws a graph of pressure (delta) change for the last 24-hours, see Annex* for more details | |
void drawFrame3(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) { | |
int gwidth = 75; // Graph width in pixels | |
int gscale = 30; // Graph height in pixels | |
int num_bars = 8; // Number of bars to display | |
#define yscale 8 // Graph +/- y-axis scale e.g. 8 displays +/-8 and scales data accordingly | |
float bar_width = gwidth / (num_bars+1); // Determine bar width based on graph width | |
x = 30; // Sets position of graph on screen | |
y = 15; // Sets position of graph on screen | |
display->drawVerticalLine(x, y, gscale+1); | |
display->drawString(x-18,y-6,">+"+String(yscale)); | |
display->drawString(x-8,y+gscale/2-6,"0"); | |
display->drawString(x-15,y+gscale-6,"<-"+String(yscale)); | |
display->drawString(x-30,y+gscale/2-6,String(hr_cnt%24)); | |
display->drawString(x+2+(bar_width+3)*0, y+gscale,"-24"); // 24hr marker at bar 0 | |
display->drawString(x+2+(bar_width+3)*2, y+gscale,"-12"); // 12hr marker at bar 2 | |
display->drawString(x+2+(bar_width+3)*5, y+gscale,"-2"); // 2hr marker at bar 5 | |
display->drawString(x+2+(bar_width+3)*7, y+gscale,"0"); // 0hr marker at bar 7 | |
int display_points [8] = {0,5,11,17,20,21,22,23}; // Only display time for hours 0,5,11,17,20,21,22,23 | |
float value; | |
for (int bar_num = 0; bar_num < num_bars; bar_num++){ // Now display a bar at each hour position -24, -18, -12, -6, -3, -2, -1 and 0 hour | |
value = map(reading[display_points[bar_num]].pressure, reading[23].pressure-yscale, reading[23].pressure+yscale, gscale, 0); | |
if (value > gscale) value = gscale; // Screen scale is 0 to e.g. 40pixels, this stops drawing beyond graph bounds | |
if (value < 0 ) value = 0; // 0 is top of graph, this stops drawing beyond graph bounds | |
display->drawHorizontalLine(x+bar_num*(bar_width+3)+2, y+value, bar_width); | |
for (int yplus=gscale; yplus > value; yplus = yplus - 1) { | |
display->drawHorizontalLine(x+bar_num*(bar_width+3)+2, y + yplus, bar_width); | |
} | |
} | |
} | |
// This frame draws a weather icon based on 1-hour of data for the prediction | |
void drawFrame4(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) { | |
reading[23].pressure = (reading[23].pressure + read_pressure())/2; // Update rolling average, gets reset on the hour transition | |
float trend = reading[23].pressure - reading[22].pressure; // Get short-term trend for the last 1-hour | |
weather_description wx_text = get_forecast_text(read_pressure(), trend, look_1hr); // Convert to forecast text based on 1-hours | |
ForecastToImgTxt(wx_text); | |
display->drawXbm(x+0,y+15, icon_width, icon_height, image_table[image]); // Display corresponding image | |
display->drawStringMaxWidth(x+45,y+12,90,"1-Hr forecast"); | |
display->drawStringMaxWidth(x+45,y+22,90,String(read_pressure(),1)+" hPA"); | |
display->drawStringMaxWidth(x+47,y+32,90,String(trend,1)+" "+get_trend_text(trend)); | |
} | |
// This frame shows a weather description based on 1-hour of data for the prediction | |
void drawFrame5(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) { | |
reading[23].pressure = (reading[23].pressure + read_pressure())/2; // Update rolling average | |
float trend = reading[23].pressure - reading[22].pressure; // Get short-term trend | |
weather_description wx_text = get_forecast_text(read_pressure(), trend, look_1hr); // Convert to forecast text based on 1-hours | |
ForecastToImgTxt(wx_text); | |
display->drawString(x+0,y+10,"Short-term forecast:"); | |
display->setFont(ArialMT_Plain_16); | |
display->drawStringMaxWidth(x+0,y+18,127,weather_text); | |
display->setFont(ArialMT_Plain_10); | |
} | |
float read_pressure(){ | |
int reading = (bme.readPressure()/100.0F+pressure_offset)*10; // Rounded result to 1-decimal place | |
return (float)reading/10; | |
} | |
// Convert pressure trend to text | |
String get_trend_text(float trend){ | |
String trend_str = "Steady"; // Default weather state | |
if (trend > 3.5) { trend_str = "Rising fast"; } | |
else if (trend > 1.5 && trend <= 3.5) { trend_str = "Rising"; } | |
else if (trend > 0.25 && trend <= 1.5) { trend_str = "Rising slow"; } | |
else if (trend > -0.25 && trend < 0.25) { trend_str = "Steady"; } | |
else if (trend >= -1.5 && trend < -0.25) { trend_str = "Falling slow"; } | |
else if (trend >= -3.5 && trend < -1.5) { trend_str = "Falling"; } | |
else if (trend <= -3.5) { trend_str = "Falling fast"; } | |
return trend_str; | |
} | |
// Convert forecast text to a corresponding image for display together with a record of the current weather | |
void ForecastToImgTxt(weather_description wx_text){ | |
if (wx_text == GoodClearWeather) {image = sunny_img; current_wx = sunny; weather_text = "Good clear weather";} | |
else if (wx_text == BecomingClearer) {image = mostlysunny_img; current_wx = mostlysunny; weather_text = "Becoming clearer";} | |
else if (wx_text == NoChange) {image = cloudy_img; current_wx = cloudy; weather_text = "No change, clearing";} | |
else if (wx_text == ClearSpells) {image = mostlysunny_img; current_wx = mostlysunny; weather_text = "Clear spells";} | |
else if (wx_text == ClearingWithin12hrs) {image = mostlysunny_img; current_wx = mostlysunny; weather_text = "Clearing within 12-hrs";} | |
else if (wx_text == ClearingAndColder) {image = mostlysunny_img; current_wx = mostlysunny; weather_text = "Clearing and colder";} | |
else if (wx_text == GettingWarmer) {image = mostlysunny_img; current_wx = mostlysunny; weather_text = "Getting warmer";} | |
else if (wx_text == WarmerIn2daysRainLikely) {image = rain_img; current_wx = rain; weather_text = "Warmer in 2-days, rain likely";} | |
else if (wx_text == ExpectRain) {image = rain_img; current_wx = rain; weather_text = "Expect rain";} | |
else if (wx_text == WarmerRainWithin36hrs) {image = rain_img; current_wx = rain; weather_text = "Warmer, rain within 36-hrs";} | |
else if (wx_text == RainIn18hrs) {image = rain_img; current_wx = rain; weather_text = "Rain in 18-hrs";} | |
else if (wx_text == RainHighWindsClearAndCool) {image = rain_img; current_wx = rain; weather_text = "Rain, high winds, clear and cool";} | |
else if (wx_text == GalesHeavyRainSnowInWinter) {image = tstorms_img; current_wx = tstorms; weather_text = "Gales, heavy rain, in winter snow";} | |
} | |
// Convert pressure and trend to a weather description either for 1 or 3 hours with the boolean true/false switch | |
weather_description get_forecast_text(float pressure_now, float trend, bool range) { | |
String trend_str = get_trend_text(trend); | |
weather_description wx_text = NoChange; //As a default forecast | |
image = cloudy_img; // Generally when there is 'no change' then cloudy is the conditions | |
if (pressure_now >= 1022.68 ) {wx_text = GoodClearWeather;} | |
if (pressure_now >= 1022.7 && trend_str == "Falling fast") {wx_text = WarmerRainWithin36hrs;} | |
if (pressure_now >= 1013.2 && pressure_now <= 1022.68 && | |
(trend_str == "Steady" || trend_str == "Rising slow")) {wx_text = NoChange; (range?wx_history_3hr():wx_history_1hr()); } | |
if (pressure_now >= 1013.2 && pressure_now <= 1022.68 && | |
(trend_str == "Rising" || trend_str == "Rising fast")) {wx_text = GettingWarmer;} | |
if (pressure_now >= 1013.2 && pressure_now <= 1022.68 && trend_str == "Rising slow") {wx_text = BecomingClearer;} | |
if (pressure_now >= 1013.2 && pressure_now <= 1022.68 && | |
(trend_str == "Falling slow" || trend_str == "Falling fast")) {wx_text = ExpectRain;} | |
if (pressure_now >= 1013.2 && pressure_now <= 1022.68 && trend_str == "Steady") {wx_text = ClearSpells; (range?wx_history_3hr():wx_history_1hr());}; | |
if (pressure_now <= 1013.2 && (trend_str == "Falling slow" || trend_str == "Falling")) {wx_text = RainIn18hrs;} | |
if (pressure_now <= 1013.2 && trend_str == "Falling fast") {wx_text = RainHighWindsClearAndCool;} | |
if (pressure_now <= 1013.2 && | |
(trend_str == "Rising" || trend_str=="Rising slow"||trend_str=="Rising fast")) {wx_text = ClearingWithin12hrs;} | |
if (pressure_now <= 1009.14 && trend_str == "Falling fast") {wx_text = GalesHeavyRainSnowInWinter;} | |
if (pressure_now <= 1009.14 && trend_str == "Rising fast") {wx_text = ClearingAndColder;} | |
return wx_text; | |
} | |
// Convert 1-hr weather history to text | |
void wx_history_1hr() { | |
if (wx_average_1hr > 0) weather_extra_text = ", expect sun"; | |
else if (wx_average_1hr == 0) weather_extra_text = ", mainly cloudy"; | |
else if (wx_average_1hr < 0) weather_extra_text = ", expect rain"; | |
else weather_extra_text = ""; | |
} | |
// Convert 3-hr weather history to text | |
void wx_history_3hr() { | |
if (wx_average_3hr > 0) weather_extra_text = ", expect sun"; | |
else if (wx_average_3hr == 0) weather_extra_text = ", mainly cloudy"; | |
else if (wx_average_3hr < 0) weather_extra_text = ", expect rain"; | |
else weather_extra_text = ""; | |
} | |
/////////////////////////////////////////////////////////////////////////////////////////////////////// | |
// This array keeps function pointers to all frames | |
// frames are the single views that slide in | |
FrameCallback frames[] = { drawFrame1, drawFrame2, drawFrame3, drawFrame4, drawFrame5}; | |
// how many frames are there? | |
int frameCount = 5; | |
// Overlays are statically drawn on top of a frame eg. a clock | |
OverlayCallback overlays[] = { msOverlay }; | |
int overlaysCount = 1; | |
void setup() { | |
float p,t; | |
Serial.begin(115200); | |
Wire.begin(5,4); | |
if (!StartWiFi(ssid,password)) Serial.println("Failed to start WiFi Service after 20 attempts");; | |
configTime(1, 3600, "pool.ntp.org"); | |
if (!bme.begin()) { Serial.println("Could not find a sensor, check wiring!");} | |
else | |
{ | |
Serial.println("Found a sensor continuing"); | |
while (isnan(bme.readPressure())) { Serial.println(bme.readPressure()); } | |
} | |
while (!update_time()); //Get the latest time | |
for (int i = 0; i <= 23; i++){ // At the start all array values are the same as a baseline | |
reading[i].pressure = read_pressure(); // A rounded to 1-decimal place version of pressure | |
reading[i].temperature = bme.readTemperature(); // Although not used, but avialable | |
reading[i].humidity = bme.readHumidity(); // Although not used, but avialable | |
reading[i].wx_state_1hr = unknown; // To begin with | |
reading[i].wx_state_3hr = unknown; // To begin with | |
} // Note that only 0,5,11,17,20,21,22,23 are used as display positions | |
last_reading_hour = reading_hour; | |
wx_average_1hr = 0; // Until we get a better idea | |
wx_average_3hr = 0; // Until we get a better idea | |
// An ESP is capable of rendering 60fps in 80Mhz mode but leaves little time for anything else, run at 160Mhz mode or just set it to about 30 fps | |
ui.setTargetFPS(20); | |
ui.setIndicatorPosition(BOTTOM); // You can change this to TOP, LEFT, BOTTOM, RIGHT | |
ui.setIndicatorDirection(LEFT_RIGHT); // Defines where the first frame is located in the bar | |
ui.setFrameAnimation(SLIDE_LEFT); // You can change the transition that is used SLIDE_LEFT, SLIDE_RIGHT, SLIDE_UP, SLIDE_DOWN | |
ui.setFrames(frames, frameCount); // Add frames | |
ui.setOverlays(overlays, overlaysCount); // Add overlays | |
ui.init(); // Initialising the UI will init the display too. | |
display.flipScreenVertically(); | |
display.setFont(ArialMT_Plain_10); | |
display.setTextAlignment(TEXT_ALIGN_LEFT); | |
} | |
void loop() { | |
int remainingTimeBudget = ui.update(); | |
update_time_and_data(); | |
if (remainingTimeBudget > 0) { // Do some work here if required | |
//for (int i = 0; i < 24;i++){ | |
// Serial.println(String(i)+" "+String(reading[pressure][i])); | |
//} | |
delay(remainingTimeBudget); | |
} | |
} | |
void update_time_and_data(){ | |
while (!update_time()); | |
if (reading_hour != last_reading_hour) { // If the hour has advanced, then shift readings left and record new values at array element [23] | |
for (int i = 0; i < 23;i++){ | |
reading[i].pressure = reading[i+1].pressure; | |
reading[i].temperature = reading[i+1].temperature; | |
reading[i].wx_state_1hr = reading[i+1].wx_state_1hr; | |
reading[i].wx_state_3hr = reading[i+1].wx_state_3hr; | |
} | |
reading[23].pressure = read_pressure(); // Update time=now with current value of pressure | |
reading[23].wx_state_1hr = current_wx; | |
reading[23].wx_state_3hr = current_wx; | |
last_reading_hour = reading_hour; | |
hr_cnt++; | |
wx_average_1hr = reading[22].wx_state_1hr + current_wx; // Used to predict 1-hour forecast extra text | |
wx_average_3hr = 0; | |
for (int i=23;i >= 21; i--){ // Used to predict 3-hour forecast extra text | |
wx_average_3hr = wx_average_3hr + (int)reading[i].wx_state_3hr; // On average the last 3-hours of weather is used for the 'no change' forecast - e.g. more of the same? | |
} | |
} | |
} | |
bool update_time(){ | |
struct tm timeinfo; | |
if(!getLocalTime(&timeinfo)){ | |
Serial.println("Failed to obtain time"); | |
return false; | |
} | |
//See http://www.cplusplus.com/reference/ctime/strftime/ | |
Serial.println(&timeinfo, "%A, %d %B %y %H:%M:%S"); // Displays: Saturday, 24 June 17 14:05:49 | |
char strftime_buf[64]; | |
strftime(strftime_buf, sizeof(strftime_buf), "%R:%S %a %d-%m-%y", &timeinfo); | |
time_str = strftime_buf; // Now is this format HH:MM:SS Sat 05-07-17 | |
reading_hour = time_str.substring(0,2).toInt(); | |
return true; | |
} | |
int StartWiFi(const char* ssid, const char* password){ | |
int connAttempts = 0; | |
Serial.println("\r\nConnecting to: "+String(ssid)); | |
WiFi.begin(ssid, password); | |
while (WiFi.status() != WL_CONNECTED ) { | |
delay(500); | |
Serial.print("."); | |
if(connAttempts > 20) return false; | |
connAttempts++; | |
} | |
Serial.print("WiFi connected\r\nIP address: "); | |
Serial.println(WiFi.localIP()); | |
return true; | |
} | |
/* | |
FRAME-3 description | |
// This frame draws a graph of pressure (delata) change for the last 24-hours, see Annex* for more details | |
// Draws a 'relative value' chart using reading[23] as the baseline | |
// +8 | | |
// +7 |-- | |
// : | |
// +1 |-- -- -- -- -- -- -- | |
// 0 +-24+-18+-12+-8-+-3-+-2-+-1-+-0-+ | |
// -1 | | |
// -2 | | |
// The 'reading' array holds values for Pressure, Temperature, Humidity and Wx State for the last 24-hours | |
// [00][01][02][03][04][05][06][07][08][09][10][11][12][13][14][15][16][17][18][19][20][21][22][23] Values are shifted left <-- each hour | |
// ^-23Hr ^-18Hr ^-12Hr ^-6Hr ^-3 ^-2 ^-1 ^0Hr | |
// P ~ readings in each array position | |
// T ~ readings in each array position | |
// % ~ readings in each array position | |
// Wx ~ readings in each array position | |
// Forecast basics: | |
// Look at the pressure change over3 hours | |
// If pressure is descending, then a low pressure area is approaching | |
// If pressure is ascending , then a low is passing or a high pressure is coming | |
// When pressure is changing rapidly (>6hPa/3 hours), it will be windy (or potentially windy) | |
// More detailed: | |
// Pressure falling slowly (0.5 - 3 hPa in 3h): low is weak, dying or moving slowly. You might get some rain but typically no high winds. | |
// Pressure falling moderately (3-6 hPa/3h): rapid movement or deepening low. Moderate winds and rain and a warm front. | |
: the low is passing fast, the day after tomorrow will typically be fine. | |
// Pressure falling fast (6-12 hPa/3h) : Storm conditions highly likely. | |
// Pressure rises are connected with gradually drier weather | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment