Instrument Neutral Distributed Interface INDI 2.2.5
openweathermap.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2015 Jasem Mutlaq. All rights reserved.
3
4 INDI Weather Underground (TM) Weather Driver
5
6 Modified for OpenWeatherMap API by Jarno Paananen
7
8 This program is free software; you can redistribute it and/or modify it
9 under the terms of the GNU General Public License as published by the Free
10 Software Foundation; either version 2 of the License, or (at your option)
11 any later version.
12
13 This program is distributed in the hope that it will be useful, but WITHOUT
14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
16 more details.
17
18 You should have received a copy of the GNU Library General Public License
19 along with this library; see the file COPYING.LIB. If not, write to
20 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 Boston, MA 02110-1301, USA.
22
23 The full GNU General Public License is included in this distribution in the
24 file called LICENSE.
25*******************************************************************************/
26
27#include "openweathermap.h"
28
29#ifdef _USE_SYSTEM_JSONLIB
30#include <nlohmann/json.hpp>
31#else
32#include <indijson.hpp>
33#endif
34
35#include "locale_compat.h"
36
37#include <curl/curl.h>
38
39#include <memory>
40#include <cstring>
41#include <limits> // Needed for std::numeric_limits
42
44
45// We declare an auto pointer to OpenWeatherMap.
46std::unique_ptr<OpenWeatherMap> openWeatherMap(new OpenWeatherMap());
47
48static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
49{
50 ((std::string *)userp)->append((char *)contents, size * nmemb);
51 return size * nmemb;
52}
53
55{
56 setVersion(1, 2);
57
58 owmLat = std::numeric_limits<double>::quiet_NaN();
59 owmLong = std::numeric_limits<double>::quiet_NaN();
60 previousForecast = std::numeric_limits<double>::quiet_NaN(); // Initialize previous forecast
61
63 curl_global_init(CURL_GLOBAL_DEFAULT);
64}
65
67{
68 curl_global_cleanup();
69}
70
72{
73 return "OpenWeatherMap";
74}
75
77{
79 defineProperty(owmAPIKeyTP);
80}
81
83{
84 if (owmAPIKeyTP[0].isEmpty())
85 {
86 LOG_ERROR("OpenWeatherMap API Key is not available. Please register your API key at "
87 "www.openweathermap.org and save it under Options.");
88 return false;
89 }
90
91 return true;
92}
93
95{
96 return true;
97}
98
100{
102
103 char api_key[256] = {0};
104 IUGetConfigText(getDeviceName(), "OWM_API_KEY", "API_KEY", api_key, 256);
105 owmAPIKeyTP[0].fill("API_KEY", "API Key", api_key);
106 owmAPIKeyTP.fill(getDeviceName(), "OWM_API_KEY", "OpenWeatherMap", OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
107
108 addParameter("WEATHER_FORECAST", "Weather", 0, 0, 15);
109 addParameter("WEATHER_TEMPERATURE", "Temperature (C)", -10, 30, 15);
110 addParameter("WEATHER_PRESSURE", "Pressure (hPa)", 900, 1100, 15);
111 addParameter("WEATHER_HUMIDITY", "Humidity (%)", 0, 100, 15);
112 addParameter("WEATHER_WIND_SPEED", "Wind (m/s)", 0, 20, 15);
113 addParameter("WEATHER_RAIN_HOUR", "Rain precip (mm)", 0, 0, 15);
114 addParameter("WEATHER_SNOW_HOUR", "Snow precip (mm)", 0, 0, 15);
115 addParameter("WEATHER_CLOUD_COVER", "Clouds (%)", 0, 100, 15);
116 addParameter("WEATHER_CODE", "Status code", 200, 810, 15);
117
118 setCriticalParameter("WEATHER_FORECAST");
119 setCriticalParameter("WEATHER_TEMPERATURE");
120 setCriticalParameter("WEATHER_WIND_SPEED");
121 setCriticalParameter("WEATHER_RAIN_HOUR");
122 setCriticalParameter("WEATHER_SNOW_HOUR");
123
126 return true;
127}
128
129bool OpenWeatherMap::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
130{
131 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
132 {
133 if (owmAPIKeyTP.isNameMatch(name))
134 {
135 owmAPIKeyTP.update(texts, names, n);
136 owmAPIKeyTP.setState(IPS_OK);
137 owmAPIKeyTP.apply();
138 saveConfig(true, owmAPIKeyTP.getName());
139 return true;
140 }
141 }
142
143 return INDI::Weather::ISNewText(dev, name, texts, names, n);
144}
145
146bool OpenWeatherMap::updateLocation(double latitude, double longitude, double elevation)
147{
148 INDI_UNUSED(elevation);
149
150 owmLat = latitude;
151 owmLong = (longitude > 180) ? (longitude - 360) : longitude;
152
153 return true;
154}
155
157{
158 CURL *curl {nullptr};
159 CURLcode res;
160 std::string readBuffer;
161 char errorBuffer[CURL_ERROR_SIZE] = {0};
162 char requestURL[MAXRBUF] = {0};
163
164 // If location is not updated yet, return busy
165 if (std::isnan(owmLat) || std::isnan(owmLong))
166 return IPS_BUSY;
167
168 AutoCNumeric locale;
169
170 snprintf(requestURL, MAXRBUF, "http://api.openweathermap.org/data/2.5/weather?lat=%g&lon=%g&appid=%s&units=metric",
171 owmLat, owmLong, owmAPIKeyTP[0].getText());
172
173 curl = curl_easy_init();
174 if (curl)
175 {
176 curl_easy_setopt(curl, CURLOPT_URL, requestURL);
177 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
178 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
179 curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errorBuffer);
180 errorBuffer[0] = 0;
181 res = curl_easy_perform(curl);
182 curl_easy_cleanup(curl);
183 if(res != CURLE_OK)
184 {
185 if(strlen(errorBuffer))
186 {
187 LOGF_ERROR("Error %d reading data: %s", res, errorBuffer);
188 }
189 else
190 {
191 LOGF_ERROR("Error %d reading data: %s", res, curl_easy_strerror(res));
192 }
193 return IPS_ALERT;
194 }
195 }
196
197 double forecast = 0;
198 double temperature = 0;
199 double pressure = 0;
200 double humidity = 0;
201 double wind = 0;
202 double rain = 0;
203 double snow = 0;
204 double clouds = 0;
205 int code = 0;
206 std::string description; // To store weather description
207
208 try
209 {
210 json weatherReport = json::parse(readBuffer);
211
212 weatherReport["weather"][0]["id"].get_to(code);
213 weatherReport["weather"][0]["description"].get_to(description); // Get description
214
215 if (code >= 200 && code < 300)
216 {
217 // Thunderstorm
218 forecast = 2;
219 }
220 else if (code >= 300 && code < 400)
221 {
222 // Drizzle
223 forecast = 2;
224 }
225 else if (code >= 500 && code < 700)
226 {
227 // Rain and snow
228 forecast = 2;
229 }
230 else if (code >= 700 && code < 800)
231 {
232 // Mist and so on
233 forecast = 1;
234 }
235 else if (code == 800)
236 {
237 // Clear!
238 forecast = 0;
239 }
240 else if (code >= 801 && code <= 803)
241 {
242 // Some clouds
243 forecast = 1;
244 }
245 else if (code >= 804 && code < 900)
246 {
247 // Overcast
248 forecast = 2;
249 }
250
251 // Temperature
252 weatherReport["main"]["temp"].get_to(temperature);
253 // Pressure
254 weatherReport["main"]["pressure"].get_to(pressure);
255 // Humidity
256 weatherReport["main"]["humidity"].get_to(humidity);
257 // Wind
258 weatherReport["wind"]["speed"].get_to(wind);
259 // Cloud
260 weatherReport["clouds"]["all"].get_to(clouds);
261 // Rain (does not exist in all reports)
262 if (weatherReport.contains("rain"))
263 {
264 if (weatherReport["rain"].contains("h"))
265 {
266 weatherReport["rain"]["h"].get_to(rain);
267 }
268 else if (weatherReport["rain"].contains("1h"))
269 {
270 weatherReport["rain"]["1h"].get_to(rain);
271 }
272 }
273 // Snow (does not exist in all reports)
274 if (weatherReport.contains("snow"))
275 {
276 if (weatherReport["snow"].contains("h"))
277 {
278 weatherReport["snow"]["h"].get_to(snow);
279 }
280 else if (weatherReport["snow"].contains("1h"))
281 {
282 weatherReport["snow"]["1h"].get_to(snow);
283 }
284 }
285
286 }
287 catch (json::exception &e)
288 {
289 // output exception information
290 LOGF_ERROR("Error parsing weather report %s id: %d", e.what(), e.id);
291 return IPS_ALERT;
292 }
293
294 // Log forecast change if it differs from the previous value
295 if (std::isnan(previousForecast) || forecast != previousForecast)
296 {
297 LOGF_INFO("Forecast changed: %s (Code: %d)", description.c_str(), code);
298 previousForecast = forecast; // Update previous forecast
299 }
300
301 setParameterValue("WEATHER_FORECAST", forecast);
302 setParameterValue("WEATHER_TEMPERATURE", temperature);
303 setParameterValue("WEATHER_PRESSURE", pressure);
304 setParameterValue("WEATHER_HUMIDITY", humidity);
305 setParameterValue("WEATHER_WIND_SPEED", wind);
306 setParameterValue("WEATHER_RAIN_HOUR", rain);
307 setParameterValue("WEATHER_SNOW_HOUR", snow);
308 setParameterValue("WEATHER_CLOUD_COVER", clouds);
309 setParameterValue("WEATHER_CODE", code);
310 return IPS_OK;
311}
312
314{
316
317 owmAPIKeyTP.save(fp);
318
319 return true;
320}
double humidity
double temperature
INDI::PropertyText getText(const char *name) const
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
void setVersion(uint16_t vMajor, uint16_t vMinor)
Set driver version information to be defined in DRIVER_INFO property as vMajor.vMinor.
void defineProperty(INumberVectorProperty *property)
void addDebugControl()
Add Debug control to the driver.
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
void save(FILE *f) const
save Save property to configuration file.
const char * getName() const
bool isNameMatch(const char *otherName) const
bool update(const char *const texts[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
void addParameter(std::string name, std::string label, double numMinOk, double numMaxOk, double percWarning, bool flipWarning=false)
Add a physical weather measurable parameter to the weather driver.
bool setCriticalParameter(std::string name)
setCriticalParameter Set parameter that is considered critical to the operation of the observatory....
void setParameterValue(std::string name, double value)
setParameterValue Update weather parameter value
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
INDI::PropertyNumber LocationNP
void setWeatherConnection(const uint8_t &value)
setWeatherConnection Set Weather connection mode. Child class should call this in the constructor bef...
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
virtual void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
virtual bool updateLocation(double latitude, double longitude, double elevation) override
Update weather station location.
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
bool Connect() override
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
const char * getDefaultName() override
virtual IPState updateWeather() override
updateWeather Update weather conditions from device or service. The function should not change the st...
bool Disconnect() override
Disconnect from device.
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual ~OpenWeatherMap()
a class to store JSON values
ValueType & get_to(ValueType &v) const noexcept(noexcept(JSONSerializer< ValueType >::from_json(std::declval< const basic_json_t & >(), v)))
get a value (explicit)
bool contains(const typename object_t::key_type &key) const
check the existence of an element in a JSON object
general exception of the basic_json class
const int id
the id of the exception
const char * what() const noexcept override
returns the explanatory string
const char * OPTIONS_TAB
OPTIONS_TAB Where all the driver's options are located. Those may include auxiliary controls,...
@ IP_RW
Definition indiapi.h:184
IPState
Property state.
Definition indiapi.h:158
@ IPS_BUSY
Definition indiapi.h:161
@ IPS_ALERT
Definition indiapi.h:162
@ IPS_IDLE
Definition indiapi.h:159
@ IPS_OK
Definition indiapi.h:160
#define INDI_UNUSED(x)
Definition indidevapi.h:132
#define MAXRBUF
Definition indidriver.c:52
int IUGetConfigText(const char *dev, const char *property, const char *member, char *value, int len)
IUGetConfigText Opens configuration file and reads single text property.
Definition indidriver.c:908
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition indilogger.h:72
#define LOGF_ERROR(fmt,...)
Definition indilogger.h:80
#define getDeviceName
basic_json<> json
default specialization
std::unique_ptr< OpenWeatherMap > openWeatherMap(new OpenWeatherMap())