Skip to content

Commit cbcd23a

Browse files
committed
New Weather Provider: OpenWeather
1 parent 69c585f commit cbcd23a

13 files changed

+244
-3
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.idea
22
atlassian-ide-plugin.xml
33
build
4+
cmake-build-debug
45
sprinklers_pi

CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ add_executable(sprinklers_pi
3232
Aeris.h
3333
DarkSky.cpp
3434
DarkSky.h
35+
OpenWeather.cpp
36+
OpenWeather.h
3537
web.cpp
3638
web.h
3739
json.hpp)

DarkSky.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,11 @@ static void ParseResponse(json &data, Weather::ReturnVals * ret)
7272
ret->meantempi, ret->windmph/WIND_FACTOR, ret->precipi/PRECIP_FACTOR, ret->UV/UV_FACTOR);
7373
}
7474

75-
static void GetData(const Weather::Settings & settings,const char *m_darkSkyAPIHost,time_t timstamp, Weather::ReturnVals * ret)
75+
static void GetData(const Weather::Settings & settings,const char *m_darkSkyAPIHost,time_t timestamp, Weather::ReturnVals * ret)
7676
{
7777
char cmd[255];
7878

79-
snprintf(cmd, sizeof(cmd), "/usr/bin/curl -sS -o /tmp/darksky.json 'https://%s/forecast/%s/%s,%ld?exclude=currently,daily,minutely,flags'", m_darkSkyAPIHost, settings.apiSecret, settings.location, timstamp);
79+
snprintf(cmd, sizeof(cmd), "/usr/bin/curl -sS -o /tmp/darksky.json 'https://%s/forecast/%s/%s,%ld?exclude=currently,daily,minutely,flags'", m_darkSkyAPIHost, settings.apiSecret, settings.location, timestamp);
8080
// trace("cmd: %s\n",cmd);
8181

8282
FILE *fh;

Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Weather.cpp \
1414
Wunderground.cpp \
1515
Aeris.cpp \
1616
DarkSky.cpp \
17+
OpenWeather.cpp \
1718
core.cpp \
1819
port.cpp \
1920
settings.cpp \

OpenWeather.cpp

+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// OpenWeather.cpp
2+
// This file manages the retrieval of Weather related information and adjustment of durations
3+
// from OpenWeather
4+
5+
#include "config.h"
6+
#ifdef WEATHER_OPENWEATHER
7+
8+
#include "OpenWeather.h"
9+
#include "core.h"
10+
#include "port.h"
11+
#include <string.h>
12+
#include <stdlib.h>
13+
#include <fstream>
14+
#include "json.hpp"
15+
16+
using json = nlohmann::json;
17+
18+
OpenWeather::OpenWeather(void)
19+
{
20+
m_openWeatherAPIHost="api.openweathermap.org";
21+
}
22+
23+
static void ParseResponse(json &data, Weather::ReturnVals * ret)
24+
{
25+
freeMemory();
26+
ret->valid = false;
27+
ret->maxhumidity = -999;
28+
ret->minhumidity = 999;
29+
30+
float temp=0;
31+
float wind=0;
32+
float rain=0;
33+
float precip=0;
34+
short humidity;
35+
short i=0;
36+
37+
try {
38+
for (auto &hour : data["hourly"]) {
39+
rain = 0;
40+
temp += hour["temp"].get<float>();
41+
wind += hour["wind_speed"].get<float>();
42+
if (hour.count("rain") > 0 && hour["rain"].count("1h") > 0) {
43+
rain = hour["rain"]["1h"].get<float>();
44+
precip += rain;
45+
}
46+
humidity = hour["humidity"].get<short>();
47+
/*
48+
trace("collected the following values:\ntemp: %0.2f\nwind: %0.2f\nprecip: %0.2f\nhumid: %0.2f\n",
49+
hour["temp"].get<float>(), hour["wind_speed"].get<float>(), rain, humidity);
50+
51+
trace("totals so far:\ntemp: %0.2f\nwind: %0.2f\nprecip: %0.2f\n\n",
52+
temp, wind, precip);
53+
*/
54+
if (humidity > ret->maxhumidity) {
55+
ret->maxhumidity = humidity;
56+
}
57+
if (humidity < ret->minhumidity) {
58+
ret->minhumidity = humidity;
59+
}
60+
if (++i > 24) {
61+
break;
62+
}
63+
}
64+
if (i > 0) {
65+
ret->valid = true;
66+
ret->meantempi = (short) std::round(temp/i);
67+
ret->windmph = (short) std::round(wind/i * WIND_FACTOR);
68+
ret->precipi = (short) std::round(precip / MM_TO_IN * PRECIP_FACTOR); // we want total not average
69+
ret->UV = (short) std::round(data["current"]["uvi"].get<float>() * UV_FACTOR);
70+
}
71+
} catch(std::exception &err) {
72+
trace(err.what());
73+
}
74+
75+
76+
if (ret->maxhumidity == -999 || ret->maxhumidity > 100) {
77+
ret->maxhumidity = NEUTRAL_HUMIDITY;
78+
}
79+
if (ret->minhumidity == 999 || ret->minhumidity < 0) {
80+
ret->minhumidity = NEUTRAL_HUMIDITY;
81+
}
82+
83+
trace("Parsed the following values:\ntemp: %d\nwind: %0.2f\nprecip: %0.2f\nuv: %0.2f\n",
84+
ret->meantempi, ret->windmph/WIND_FACTOR, ret->precipi/PRECIP_FACTOR, ret->UV/UV_FACTOR);
85+
}
86+
87+
static void GetData(const Weather::Settings & settings,const char *m_openWeatherAPIHost,time_t timestamp, Weather::ReturnVals * ret)
88+
{
89+
char cmd[255];
90+
91+
// split location into lat, long
92+
char * pch;
93+
char * loc = strdup(settings.location);
94+
char * lat = strtok(loc, ", ");
95+
char * lon = strtok(NULL, ", ");
96+
97+
// get weather json
98+
if (timestamp != 0) {
99+
snprintf(cmd, sizeof(cmd),
100+
"/usr/bin/curl -sS -o /tmp/openWeather.json 'https://%s/data/2.5/onecall/timemachine?appid=%s&lat=%s&lon=%s&dt=%ld&units=imperial'",
101+
m_openWeatherAPIHost, settings.apiSecret, lat, lon, timestamp);
102+
} else {
103+
snprintf(cmd, sizeof(cmd),
104+
"/usr/bin/curl -sS -o /tmp/openWeather.json 'https://%s/data/2.5/onecall?appid=%s&lat=%s&lon=%s&units=imperial'",
105+
m_openWeatherAPIHost, settings.apiSecret, lat, lon);
106+
}
107+
//trace("cmd: %s\n",cmd);
108+
109+
FILE *fh;
110+
char buf[255];
111+
112+
buf[0]=0;
113+
114+
if ((fh = popen(cmd, "r")) != NULL) {
115+
size_t byte_count = fread(buf, 1, sizeof(buf) - 1, fh);
116+
buf[byte_count] = 0;
117+
}
118+
119+
(void) pclose(fh);
120+
trace("curl error output: %s\n",buf);
121+
122+
json j;
123+
std::ifstream ifs("/tmp/openWeather.json");
124+
ifs >> j;
125+
126+
ParseResponse(j, ret);
127+
128+
ifs.close();
129+
130+
if (!ret->valid)
131+
{
132+
if (ret->keynotfound)
133+
trace("Invalid OpenWeather Key\n");
134+
else
135+
trace("Bad OpenWeather Response\n");
136+
}
137+
}
138+
139+
Weather::ReturnVals OpenWeather::InternalGetVals(const Weather::Settings & settings) const
140+
{
141+
ReturnVals vals = {0};
142+
const time_t now = nntpTimeServer.utcNow();
143+
144+
// today
145+
trace("Get Today's Weather\n");
146+
GetData(settings, m_openWeatherAPIHost, 0, &vals);
147+
if (vals.valid) {
148+
// save today's values
149+
short precip_today = vals.precipi;
150+
short uv_today = vals.UV;
151+
152+
//trace("local hour: %d\n", nntpTimeServer.LocalHour());
153+
if (nntpTimeServer.LocalHour() >= 8) {
154+
trace("Get Today's Weather for the hours between midnight and now\n");
155+
GetData(settings, m_openWeatherAPIHost, now - 8 * 3600, &vals);
156+
if (vals.valid) {
157+
// add precip to today's values
158+
precip_today += vals.precipi;
159+
}
160+
}
161+
162+
// yesterday
163+
trace("Get Yesterday's Weather\n");
164+
GetData(settings, m_openWeatherAPIHost, now - 24 * 3600, &vals);
165+
if (vals.valid) {
166+
// restore today's values
167+
vals.precip_today = precip_today;
168+
vals.UV = uv_today;
169+
}
170+
}
171+
172+
return vals;
173+
}
174+
175+
#endif

OpenWeather.h

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// OpenWeather.h
2+
// This file manages the retrieval of Weather related information and adjustment of durations
3+
// from OpenWeather
4+
//
5+
6+
#ifndef _OW_h
7+
#define _OW_h
8+
9+
#include "port.h"
10+
#include "Weather.h"
11+
12+
class OpenWeather : public Weather
13+
{
14+
public:
15+
OpenWeather(void);
16+
private:
17+
const char* m_openWeatherAPIHost;
18+
Weather::ReturnVals InternalGetVals(const Weather::Settings & settings) const;
19+
};
20+
21+
#endif

README.md

+13
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,20 @@ By default, we now ship with no weather provider enabled, and therefore no adjus
2424
Follow the directions below to enable a weather provider. If you change weather providers be sure to run "make clean"
2525
before rebuilding.
2626

27+
### OpenWeather / OpenWeatherMap
28+
This is the current recommended free provider, however, it is currently unsupported on Arduino/AVR Platforms due to a dependence on curl and a JSON library that is currently untested on Arduino. It works on Linux devices including raspberry pi.
29+
30+
1. Uncomment `#define WEATHER_OPENWEATHER` in config.h before building.
31+
1. Build and start the server.
32+
1. Navigate to the Settings Page.
33+
1. Fill in API Secret with your api key from this page: https://home.openweathermap.org/api_keys
34+
1. Fill in Location in the format: lattidue,longitude (eg. "40.749748,-73.991618")
35+
1. Click "OK" at the top to save.
36+
1. Navigate to the Advanced -> Weather Provider Diagnostics page to test everything is working.
37+
2738
### DarkSky Weather
39+
WARNING: Darksky has been aquired by Apple and is shutting down their free API at the end of 2021.
40+
2841
Uncomment `#define WEATHER_DARKSKY` in config.h before building.
2942

3043
DarkSky is currently unsupported on Arduino/AVR Platforms due to a dependence on curl and a JSON library that is currently untested on Arduino.

Weather.h

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
#define WIND_FACTOR 10.0
1818
#define UV_FACTOR 10.0
1919

20+
#define MM_TO_IN 25.4
21+
2022
class Weather
2123
{
2224
public:

config.h

+4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
* Only uncomment one weather provider below.
2727
*************************************************/
2828

29+
// Open Weather https://openweathermap.org/darksky-openweather
30+
//#define WEATHER_OPENWEATHER
31+
2932
// Weather Underground
3033
// WARNING: this API may stop working at any moment.
3134
//#define WEATHER_WUNDERGROUND
@@ -34,6 +37,7 @@
3437
//#define WEATHER_AERIS
3538

3639
// DarkSky Weather https://darksky.net/dev
40+
// WARNING: this API will only work through the end of 2021
3741
//#define WEATHER_DARKSKY
3842

3943
// END WEATHER PROVIDER SECTION

core.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#include "Aeris.h"
1414
#elif defined(WEATHER_DARKSKY)
1515
#include "DarkSky.h"
16+
#elif defined(WEATHER_OPENWEATHER)
17+
#include "OpenWeather.h"
1618
#else
1719
#include "Weather.h"
1820
#endif
@@ -259,6 +261,8 @@ static runStateClass::DurationAdjustments AdjustDurations(Schedule * sched)
259261
Aeris w;
260262
#elif defined(WEATHER_DARKSKY)
261263
DarkSky w;
264+
#elif defined(WEATHER_OPENWEATHER)
265+
OpenWeather w;
262266
#else
263267
// this is a dummy provider which will just result in 100
264268
Weather w;

port.h

+10
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,16 @@ class nntp
7878
struct tm * ti = localtime(&t);
7979
return t + ti->tm_gmtoff;
8080
}
81+
int LocalHour()
82+
{
83+
time_t t = time(0);
84+
struct tm * ti = localtime(&t);
85+
return ti->tm_hour;
86+
}
87+
time_t utcNow()
88+
{
89+
return time(0);
90+
}
8191
void checkTime()
8292
{
8393
}

version.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.5.3
1+
1.6.0

web.cpp

+8
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
#include "Aeris.h"
1717
#elif defined(WEATHER_DARKSKY)
1818
#include "DarkSky.h"
19+
#elif defined(WEATHER_OPENWEATHER)
20+
#include "OpenWeather.h"
1921
#else
2022
#include "Weather.h"
2123
#endif
@@ -255,6 +257,10 @@ static void JSONSettings(const KVPairs & key_value_pairs, FILE * stream_file)
255257
#if defined(WEATHER_DARKSKY)
256258
fprintf_P(stream_file, PSTR("\t\"apisecret\" : \"%s\",\n"), settings.apiSecret);
257259
fprintf_P(stream_file, PSTR("\t\"loc\" : \"%s\",\n"), settings.location);
260+
#endif
261+
#if defined(WEATHER_OPENWEATHER)
262+
fprintf_P(stream_file, PSTR("\t\"apisecret\" : \"%s\",\n"), settings.apiSecret);
263+
fprintf_P(stream_file, PSTR("\t\"loc\" : \"%s\",\n"), settings.location);
258264
#endif
259265
// leave this value last, it has no comma after the value
260266
fprintf_P(stream_file, PSTR("\t\"sadj\" : \"%ld\"\n"), (long) GetSeasonalAdjust());
@@ -272,6 +278,8 @@ static void JSONwCheck(const KVPairs & key_value_pairs, FILE * stream_file)
272278
Aeris w;
273279
#elif defined(WEATHER_DARKSKY)
274280
DarkSky w;
281+
#elif defined(WEATHER_OPENWEATHER)
282+
OpenWeather w;
275283
#else
276284
Weather w;
277285
noprovider = true;

0 commit comments

Comments
 (0)