Skip to content

Commit

Permalink
New Weather Provider: OpenWeather
Browse files Browse the repository at this point in the history
  • Loading branch information
nhorvath committed Apr 18, 2020
1 parent 69c585f commit cbcd23a
Show file tree
Hide file tree
Showing 13 changed files with 244 additions and 3 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.idea
atlassian-ide-plugin.xml
build
cmake-build-debug
sprinklers_pi
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ add_executable(sprinklers_pi
Aeris.h
DarkSky.cpp
DarkSky.h
OpenWeather.cpp
OpenWeather.h
web.cpp
web.h
json.hpp)
Expand Down
4 changes: 2 additions & 2 deletions DarkSky.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ static void ParseResponse(json &data, Weather::ReturnVals * ret)
ret->meantempi, ret->windmph/WIND_FACTOR, ret->precipi/PRECIP_FACTOR, ret->UV/UV_FACTOR);
}

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

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);
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);
// trace("cmd: %s\n",cmd);

FILE *fh;
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Weather.cpp \
Wunderground.cpp \
Aeris.cpp \
DarkSky.cpp \
OpenWeather.cpp \
core.cpp \
port.cpp \
settings.cpp \
Expand Down
175 changes: 175 additions & 0 deletions OpenWeather.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// OpenWeather.cpp
// This file manages the retrieval of Weather related information and adjustment of durations
// from OpenWeather

#include "config.h"
#ifdef WEATHER_OPENWEATHER

#include "OpenWeather.h"
#include "core.h"
#include "port.h"
#include <string.h>
#include <stdlib.h>
#include <fstream>
#include "json.hpp"

using json = nlohmann::json;

OpenWeather::OpenWeather(void)
{
m_openWeatherAPIHost="api.openweathermap.org";
}

static void ParseResponse(json &data, Weather::ReturnVals * ret)
{
freeMemory();
ret->valid = false;
ret->maxhumidity = -999;
ret->minhumidity = 999;

float temp=0;
float wind=0;
float rain=0;
float precip=0;
short humidity;
short i=0;

try {
for (auto &hour : data["hourly"]) {
rain = 0;
temp += hour["temp"].get<float>();
wind += hour["wind_speed"].get<float>();
if (hour.count("rain") > 0 && hour["rain"].count("1h") > 0) {
rain = hour["rain"]["1h"].get<float>();
precip += rain;
}
humidity = hour["humidity"].get<short>();
/*
trace("collected the following values:\ntemp: %0.2f\nwind: %0.2f\nprecip: %0.2f\nhumid: %0.2f\n",
hour["temp"].get<float>(), hour["wind_speed"].get<float>(), rain, humidity);
trace("totals so far:\ntemp: %0.2f\nwind: %0.2f\nprecip: %0.2f\n\n",
temp, wind, precip);
*/
if (humidity > ret->maxhumidity) {
ret->maxhumidity = humidity;
}
if (humidity < ret->minhumidity) {
ret->minhumidity = humidity;
}
if (++i > 24) {
break;
}
}
if (i > 0) {
ret->valid = true;
ret->meantempi = (short) std::round(temp/i);
ret->windmph = (short) std::round(wind/i * WIND_FACTOR);
ret->precipi = (short) std::round(precip / MM_TO_IN * PRECIP_FACTOR); // we want total not average
ret->UV = (short) std::round(data["current"]["uvi"].get<float>() * UV_FACTOR);
}
} catch(std::exception &err) {
trace(err.what());
}


if (ret->maxhumidity == -999 || ret->maxhumidity > 100) {
ret->maxhumidity = NEUTRAL_HUMIDITY;
}
if (ret->minhumidity == 999 || ret->minhumidity < 0) {
ret->minhumidity = NEUTRAL_HUMIDITY;
}

trace("Parsed the following values:\ntemp: %d\nwind: %0.2f\nprecip: %0.2f\nuv: %0.2f\n",
ret->meantempi, ret->windmph/WIND_FACTOR, ret->precipi/PRECIP_FACTOR, ret->UV/UV_FACTOR);
}

static void GetData(const Weather::Settings & settings,const char *m_openWeatherAPIHost,time_t timestamp, Weather::ReturnVals * ret)
{
char cmd[255];

// split location into lat, long
char * pch;
char * loc = strdup(settings.location);
char * lat = strtok(loc, ", ");
char * lon = strtok(NULL, ", ");

// get weather json
if (timestamp != 0) {
snprintf(cmd, sizeof(cmd),
"/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'",
m_openWeatherAPIHost, settings.apiSecret, lat, lon, timestamp);
} else {
snprintf(cmd, sizeof(cmd),
"/usr/bin/curl -sS -o /tmp/openWeather.json 'https://%s/data/2.5/onecall?appid=%s&lat=%s&lon=%s&units=imperial'",
m_openWeatherAPIHost, settings.apiSecret, lat, lon);
}
//trace("cmd: %s\n",cmd);

FILE *fh;
char buf[255];

buf[0]=0;

if ((fh = popen(cmd, "r")) != NULL) {
size_t byte_count = fread(buf, 1, sizeof(buf) - 1, fh);
buf[byte_count] = 0;
}

(void) pclose(fh);
trace("curl error output: %s\n",buf);

json j;
std::ifstream ifs("/tmp/openWeather.json");
ifs >> j;

ParseResponse(j, ret);

ifs.close();

if (!ret->valid)
{
if (ret->keynotfound)
trace("Invalid OpenWeather Key\n");
else
trace("Bad OpenWeather Response\n");
}
}

Weather::ReturnVals OpenWeather::InternalGetVals(const Weather::Settings & settings) const
{
ReturnVals vals = {0};
const time_t now = nntpTimeServer.utcNow();

// today
trace("Get Today's Weather\n");
GetData(settings, m_openWeatherAPIHost, 0, &vals);
if (vals.valid) {
// save today's values
short precip_today = vals.precipi;
short uv_today = vals.UV;

//trace("local hour: %d\n", nntpTimeServer.LocalHour());
if (nntpTimeServer.LocalHour() >= 8) {
trace("Get Today's Weather for the hours between midnight and now\n");
GetData(settings, m_openWeatherAPIHost, now - 8 * 3600, &vals);
if (vals.valid) {
// add precip to today's values
precip_today += vals.precipi;
}
}

// yesterday
trace("Get Yesterday's Weather\n");
GetData(settings, m_openWeatherAPIHost, now - 24 * 3600, &vals);
if (vals.valid) {
// restore today's values
vals.precip_today = precip_today;
vals.UV = uv_today;
}
}

return vals;
}

#endif
21 changes: 21 additions & 0 deletions OpenWeather.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// OpenWeather.h
// This file manages the retrieval of Weather related information and adjustment of durations
// from OpenWeather
//

#ifndef _OW_h
#define _OW_h

#include "port.h"
#include "Weather.h"

class OpenWeather : public Weather
{
public:
OpenWeather(void);
private:
const char* m_openWeatherAPIHost;
Weather::ReturnVals InternalGetVals(const Weather::Settings & settings) const;
};

#endif
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,20 @@ By default, we now ship with no weather provider enabled, and therefore no adjus
Follow the directions below to enable a weather provider. If you change weather providers be sure to run "make clean"
before rebuilding.

### OpenWeather / OpenWeatherMap
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.

1. Uncomment `#define WEATHER_OPENWEATHER` in config.h before building.
1. Build and start the server.
1. Navigate to the Settings Page.
1. Fill in API Secret with your api key from this page: https://home.openweathermap.org/api_keys
1. Fill in Location in the format: lattidue,longitude (eg. "40.749748,-73.991618")
1. Click "OK" at the top to save.
1. Navigate to the Advanced -> Weather Provider Diagnostics page to test everything is working.

### DarkSky Weather
WARNING: Darksky has been aquired by Apple and is shutting down their free API at the end of 2021.

Uncomment `#define WEATHER_DARKSKY` in config.h before building.

DarkSky is currently unsupported on Arduino/AVR Platforms due to a dependence on curl and a JSON library that is currently untested on Arduino.
Expand Down
2 changes: 2 additions & 0 deletions Weather.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
#define WIND_FACTOR 10.0
#define UV_FACTOR 10.0

#define MM_TO_IN 25.4

class Weather
{
public:
Expand Down
4 changes: 4 additions & 0 deletions config.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
* Only uncomment one weather provider below.
*************************************************/

// Open Weather https://openweathermap.org/darksky-openweather
//#define WEATHER_OPENWEATHER

// Weather Underground
// WARNING: this API may stop working at any moment.
//#define WEATHER_WUNDERGROUND
Expand All @@ -34,6 +37,7 @@
//#define WEATHER_AERIS

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

// END WEATHER PROVIDER SECTION
Expand Down
4 changes: 4 additions & 0 deletions core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#include "Aeris.h"
#elif defined(WEATHER_DARKSKY)
#include "DarkSky.h"
#elif defined(WEATHER_OPENWEATHER)
#include "OpenWeather.h"
#else
#include "Weather.h"
#endif
Expand Down Expand Up @@ -259,6 +261,8 @@ static runStateClass::DurationAdjustments AdjustDurations(Schedule * sched)
Aeris w;
#elif defined(WEATHER_DARKSKY)
DarkSky w;
#elif defined(WEATHER_OPENWEATHER)
OpenWeather w;
#else
// this is a dummy provider which will just result in 100
Weather w;
Expand Down
10 changes: 10 additions & 0 deletions port.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,16 @@ class nntp
struct tm * ti = localtime(&t);
return t + ti->tm_gmtoff;
}
int LocalHour()
{
time_t t = time(0);
struct tm * ti = localtime(&t);
return ti->tm_hour;
}
time_t utcNow()
{
return time(0);
}
void checkTime()
{
}
Expand Down
2 changes: 1 addition & 1 deletion version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.5.3
1.6.0
8 changes: 8 additions & 0 deletions web.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
#include "Aeris.h"
#elif defined(WEATHER_DARKSKY)
#include "DarkSky.h"
#elif defined(WEATHER_OPENWEATHER)
#include "OpenWeather.h"
#else
#include "Weather.h"
#endif
Expand Down Expand Up @@ -255,6 +257,10 @@ static void JSONSettings(const KVPairs & key_value_pairs, FILE * stream_file)
#if defined(WEATHER_DARKSKY)
fprintf_P(stream_file, PSTR("\t\"apisecret\" : \"%s\",\n"), settings.apiSecret);
fprintf_P(stream_file, PSTR("\t\"loc\" : \"%s\",\n"), settings.location);
#endif
#if defined(WEATHER_OPENWEATHER)
fprintf_P(stream_file, PSTR("\t\"apisecret\" : \"%s\",\n"), settings.apiSecret);
fprintf_P(stream_file, PSTR("\t\"loc\" : \"%s\",\n"), settings.location);
#endif
// leave this value last, it has no comma after the value
fprintf_P(stream_file, PSTR("\t\"sadj\" : \"%ld\"\n"), (long) GetSeasonalAdjust());
Expand All @@ -272,6 +278,8 @@ static void JSONwCheck(const KVPairs & key_value_pairs, FILE * stream_file)
Aeris w;
#elif defined(WEATHER_DARKSKY)
DarkSky w;
#elif defined(WEATHER_OPENWEATHER)
OpenWeather w;
#else
Weather w;
noprovider = true;
Expand Down

3 comments on commit cbcd23a

@ThomasDtz
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hallo Nick, thanks for implementing OpenWeatherMap provider - it is very helpful to me. But i have one question: why you checked for LocalHour >= 8 in OpenWeather::InternalGetVals() ?
....
if (nntpTimeServer.LocalHour() >= 8) {
trace("Get Today's Weather for the hours between midnight and now\n");
GetData(settings, m_openWeatherAPIHost, now - 8 * 3600, &vals);
....
For my understanding, it would be possible to lose rain information for the time between 0 and max. 8 a clock of the current day if i check the weather data between that time.
Isn't it so?

Best regards!

Thomas
Germany

@nhorvath
Copy link
Collaborator Author

@nhorvath nhorvath commented on cbcd23a Nov 16, 2020 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ThomasDtz
Copy link

@ThomasDtz ThomasDtz commented on cbcd23a Nov 19, 2020 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.