Instrument Neutral Distributed Interface INDI  1.9.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 #include "gason.h"
30 #include "locale_compat.h"
31 
32 #include <curl/curl.h>
33 
34 #include <memory>
35 #include <cstring>
36 
37 // We declare an auto pointer to OpenWeatherMap.
38 std::unique_ptr<OpenWeatherMap> openWeatherMap(new OpenWeatherMap());
39 
40 static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
41 {
42  ((std::string *)userp)->append((char *)contents, size * nmemb);
43  return size * nmemb;
44 }
45 
47 {
48  setVersion(1, 1);
49 
50  owmLat = -1000;
51  owmLong = -1000;
52 
54  curl_global_init(CURL_GLOBAL_DEFAULT);
55 }
56 
58 {
59  curl_global_cleanup();
60 }
61 
63 {
64  return (const char *)"OpenWeatherMap";
65 }
66 
68 {
69  if (strlen(owmAPIKeyTP[0].getText()) == 0)
70  {
71  LOG_ERROR("OpenWeatherMap API Key is not available. Please register your API key at "
72  "www.openweathermap.org and save it under Options.");
73  return false;
74  }
75 
76  return true;
77 }
78 
80 {
81  return true;
82 }
83 
85 {
87 
88  owmAPIKeyTP[0].fill("API_KEY", "API Key", nullptr);
89  owmAPIKeyTP.fill(getDeviceName(), "OWM_API_KEY", "OpenWeatherMap", OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
90 
91  addParameter("WEATHER_FORECAST", "Weather", 0, 0, 15);
92  addParameter("WEATHER_TEMPERATURE", "Temperature (C)", -10, 30, 15);
93  addParameter("WEATHER_PRESSURE", "Pressure (hPa)", 900, 1100, 15);
94  addParameter("WEATHER_HUMIDITY", "Humidity (%)", 0, 100, 15);
95  addParameter("WEATHER_WIND_SPEED", "Wind (m/s)", 0, 20, 15);
96  addParameter("WEATHER_RAIN_HOUR", "Rain precip (mm)", 0, 0, 15);
97  addParameter("WEATHER_SNOW_HOUR", "Snow precip (mm)", 0, 0, 15);
98  addParameter("WEATHER_CLOUD_COVER", "Clouds (%)", 0, 100, 15);
99  addParameter("WEATHER_CODE", "Status code", 200, 810, 15);
100 
101  setCriticalParameter("WEATHER_FORECAST");
102  setCriticalParameter("WEATHER_TEMPERATURE");
103  setCriticalParameter("WEATHER_WIND_SPEED");
104  setCriticalParameter("WEATHER_RAIN_HOUR");
105  setCriticalParameter("WEATHER_SNOW_HOUR");
106 
107  addDebugControl();
108 
109  return true;
110 }
111 
112 void OpenWeatherMap::ISGetProperties(const char *dev)
113 {
115 
116  static bool once = true;
117 
118  if (once)
119  {
120  once = false;
121  defineProperty(&owmAPIKeyTP);
122 
123  loadConfig(true, "OWM_API_KEY");
124  }
125 }
126 
127 bool OpenWeatherMap::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
128 {
129  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
130  {
131  if (owmAPIKeyTP.isNameMatch(name))
132  {
133  owmAPIKeyTP.update(texts, names, n);
134  owmAPIKeyTP.setState(IPS_OK);
135  owmAPIKeyTP.apply();
136  return true;
137  }
138  }
139 
140  return INDI::Weather::ISNewText(dev, name, texts, names, n);
141 }
142 
143 bool OpenWeatherMap::updateLocation(double latitude, double longitude, double elevation)
144 {
145  INDI_UNUSED(elevation);
146 
147  owmLat = latitude;
148  owmLong = (longitude > 180) ? (longitude - 360) : longitude;
149 
150  return true;
151 }
152 
154 {
155  CURL *curl;
156  CURLcode res;
157  std::string readBuffer;
158  char errorBuffer[CURL_ERROR_SIZE];
159  char requestURL[MAXRBUF];
160 
161  // If location is not updated yet, return busy
162  if (owmLat == -1000 || owmLong == -1000)
163  return IPS_BUSY;
164 
165  AutoCNumeric locale;
166 
167  snprintf(requestURL, MAXRBUF, "http://api.openweathermap.org/data/2.5/weather?lat=%g&lon=%g&appid=%s&units=metric",
168  owmLat, owmLong, owmAPIKeyTP[0].getText());
169 
170  curl = curl_easy_init();
171  if (curl)
172  {
173  curl_easy_setopt(curl, CURLOPT_URL, requestURL);
174  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
175  curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
176  curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errorBuffer);
177  errorBuffer[0] = 0;
178  res = curl_easy_perform(curl);
179  curl_easy_cleanup(curl);
180  if(res != CURLE_OK)
181  {
182  if(strlen(errorBuffer))
183  {
184  LOGF_ERROR("Error %d reading data: %s", res, errorBuffer);
185  }
186  else
187  {
188  LOGF_ERROR("Error %d reading data: %s", res, curl_easy_strerror(res));
189  }
190  return IPS_ALERT;
191  }
192  }
193 
194  char srcBuffer[readBuffer.size()];
195  strncpy(srcBuffer, readBuffer.c_str(), readBuffer.size());
196  char *source = srcBuffer;
197  // do not forget terminate source string with 0
198  char *endptr;
199  JsonValue value;
200  JsonAllocator allocator;
201  int status = jsonParse(source, &endptr, &value, allocator);
202  if (status != JSON_OK)
203  {
204  LOGF_ERROR("%s at %zd", jsonStrError(status), endptr - source);
205  LOGF_DEBUG("%s", requestURL);
206  LOGF_DEBUG("%s", readBuffer.c_str());
207  return IPS_ALERT;
208  }
209 
210  JsonIterator it;
211  JsonIterator observationIterator;
212 
213  double forecast = 0;
214  double temperature = 0;
215  double pressure = 0;
216  double humidity = 0;
217  double wind = 0;
218  double rain = 0;
219  double snow = 0;
220  double clouds = 0;
221  int code = 0;
222 
223  auto checkKey = [&observationIterator](const char* key, double & value) -> bool
224  {
225  if(!strcmp(observationIterator->key, key))
226  {
227  if (observationIterator->value.isDouble())
228  value = observationIterator->value.toNumber();
229  else
230  value = atof(observationIterator->value.toString());
231  return true;
232  }
233  return false;
234  };
235 
236  auto checkWildCardKey = [&observationIterator](const char* key, double & value) -> bool
237  {
238  if(strstr(observationIterator->key, key))
239  {
240  if (observationIterator->value.isDouble())
241  value = observationIterator->value.toNumber();
242  else
243  value = atof(observationIterator->value.toString());
244  return true;
245  }
246  return false;
247  };
248 
249  for (it = begin(value); it != end(value); ++it)
250  {
251  if (!strcmp(it->key, "weather"))
252  {
253  // This is an array of weather conditions
254  for (auto i : it->value)
255  {
256  for (observationIterator = begin(i->value); observationIterator != end(i->value); ++observationIterator)
257  {
258  if (!strcmp(observationIterator->key, "id"))
259  {
260  int id;
261  if (observationIterator->value.isDouble())
262  id = observationIterator->value.toNumber();
263  else
264  id = atoi(observationIterator->value.toString());
265 
266  code = id;
267 
268  if (id >= 200 && id < 300)
269  {
270  // Thunderstorm
271  forecast = 2;
272  }
273  else if (id >= 300 && id < 400)
274  {
275  // Drizzle
276  forecast = 2;
277  }
278  else if (id >= 500 && id < 700)
279  {
280  // Rain and snow
281  forecast = 2;
282  }
283  else if (id >= 700 && id < 800)
284  {
285  // Mist and so on
286  forecast = 1;
287  }
288  else if (id == 800)
289  {
290  // Clear!
291  forecast = 0;
292  }
293  else if (id >= 801 && id <= 803)
294  {
295  // Some clouds
296  forecast = 1;
297  }
298  else if (id >= 804 && id < 900)
299  {
300  // Overcast
301  forecast = 2;
302  }
303  }
304  }
305  }
306  }
307  else if (!strcmp(it->key, "main"))
308  {
309  // Temperature, pressure, humidity
310  for (observationIterator = begin(it->value); observationIterator != end(it->value); ++observationIterator)
311  {
312  if(checkKey("temp", temperature)) continue;
313  if(checkKey("pressure", pressure)) continue;
314  checkKey("humidity", humidity);
315  }
316  }
317  else if (!strcmp(it->key, "wind"))
318  {
319  for (observationIterator = begin(it->value); observationIterator != end(it->value); ++observationIterator)
320  {
321  checkKey("speed", wind);
322  }
323  }
324  else if (!strcmp(it->key, "clouds"))
325  {
326  for (observationIterator = begin(it->value); observationIterator != end(it->value); ++observationIterator)
327  {
328  checkKey("all", clouds);
329  }
330  }
331  else if (!strcmp(it->key, "rain"))
332  {
333  for (observationIterator = begin(it->value); observationIterator != end(it->value); ++observationIterator)
334  {
335  checkWildCardKey("h", rain);
336  }
337  }
338  else if (!strcmp(it->key, "snow"))
339  {
340  for (observationIterator = begin(it->value); observationIterator != end(it->value); ++observationIterator)
341  {
342  checkWildCardKey("h", snow);
343  }
344  }
345  }
346  setParameterValue("WEATHER_FORECAST", forecast);
347  setParameterValue("WEATHER_TEMPERATURE", temperature);
348  setParameterValue("WEATHER_PRESSURE", pressure);
349  setParameterValue("WEATHER_HUMIDITY", humidity);
350  setParameterValue("WEATHER_WIND_SPEED", wind);
351  setParameterValue("WEATHER_RAIN_HOUR", rain);
352  setParameterValue("WEATHER_SNOW_HOUR", snow);
353  setParameterValue("WEATHER_CLOUD_COVER", clouds);
354  setParameterValue("WEATHER_CODE", code);
355  return IPS_OK;
356 }
357 
359 {
361 
362  IUSaveConfigText(fp, &owmAPIKeyTP);
363 
364  return true;
365 }
openWeatherMap
std::unique_ptr< OpenWeatherMap > openWeatherMap(new OpenWeatherMap())
INDI::BaseDevice::getText
INDI::PropertyView< IText > * getText(const char *name) const
Definition: basedevice.cpp:104
OpenWeatherMap::OpenWeatherMap
OpenWeatherMap()
Definition: openweathermap.cpp:46
jsonParse
int jsonParse(char *s, char **endptr, JsonValue *value, JsonAllocator &allocator)
Definition: gason.cpp:182
INDI::WeatherInterface::setCriticalParameter
bool setCriticalParameter(std::string param)
setCriticalParameter Set parameter that is considered critical to the operation of the observatory....
Definition: indiweatherinterface.cpp:167
IPState
IPState
Property state.
Definition: indiapi.h:158
INDI::PropertyBasic::isNameMatch
bool isNameMatch(const char *otherName) const
Definition: indipropertybasic.cpp:194
LOGF_ERROR
#define LOGF_ERROR(fmt,...)
Definition: indilogger.h:80
IPS_OK
@ IPS_OK
Definition: indiapi.h:161
OpenWeatherMap::updateWeather
virtual IPState updateWeather() override
updateWeather Update weather conditions from device or service. The function should not change the st...
Definition: openweathermap.cpp:153
JsonValue::isDouble
bool isDouble() const
Definition: gason.h:60
INDI::Weather::initProperties
virtual bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
Definition: indiweather.cpp:41
openweathermap.h
locale_compat.h
JsonValue
Definition: gason.h:48
IPS_ALERT
@ IPS_ALERT
Definition: indiapi.h:163
INDI::DefaultDevice::defineProperty
void defineProperty(INumberVectorProperty *property)
Definition: defaultdevice.cpp:997
OPTIONS_TAB
const char * OPTIONS_TAB
OPTIONS_TAB Where all the driver's options are located. Those may include auxiliary controls,...
Definition: defaultdevice.cpp:39
INDI_UNUSED
#define INDI_UNUSED(x)
Definition: indidevapi.h:799
INDI::DefaultDevice::setVersion
void setVersion(uint16_t vMajor, uint16_t vMinor)
Set driver version information to be defined in DRIVER_INFO property as vMajor.vMinor.
Definition: defaultdevice.cpp:1219
OpenWeatherMap::initProperties
virtual bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
Definition: openweathermap.cpp:84
INDI::BaseDevice::getDeviceName
const char * getDeviceName() const
Definition: basedevice.cpp:799
INDI::PropertyText::fill
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
Definition: indipropertytext.cpp:45
MAXRBUF
#define MAXRBUF
Definition: indidriver.c:52
JsonAllocator
Definition: gason.h:135
jsonStrError
const char * jsonStrError(int err)
Definition: gason.cpp:31
OpenWeatherMap::~OpenWeatherMap
virtual ~OpenWeatherMap()
Definition: openweathermap.cpp:57
LOGF_DEBUG
#define LOGF_DEBUG(fmt,...)
Definition: indilogger.h:83
INDI::Weather::setWeatherConnection
void setWeatherConnection(const uint8_t &value)
setWeatherConnection Set Weather connection mode. Child class should call this in the constructor bef...
Definition: indiweather.cpp:402
begin
JsonIterator begin(JsonValue o)
Definition: gason.h:104
OpenWeatherMap::saveConfigItems
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
Definition: openweathermap.cpp:358
OpenWeatherMap::Connect
bool Connect() override
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
Definition: openweathermap.cpp:67
INDI::Weather::saveConfigItems
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
Definition: indiweather.cpp:369
IUSaveConfigText
void IUSaveConfigText(FILE *fp, const ITextVectorProperty *tvp)
Add a text vector property value to the configuration file.
Definition: indicom.c:1460
JsonNode::value
JsonValue value
Definition: gason.h:89
IPS_BUSY
@ IPS_BUSY
Definition: indiapi.h:162
IPS_IDLE
@ IPS_IDLE
Definition: indiapi.h:160
OpenWeatherMap::ISGetProperties
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...
Definition: openweathermap.cpp:112
INDI::DefaultDevice::loadConfig
virtual bool loadConfig(bool silent=false, const char *property=nullptr)
Load the last saved configuration file.
Definition: defaultdevice.cpp:147
end
JsonIterator end(JsonValue)
Definition: gason.h:108
gason.h
OpenWeatherMap::ISNewText
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
Definition: openweathermap.cpp:127
OpenWeatherMap::getDefaultName
const char * getDefaultName() override
Definition: openweathermap.cpp:62
INDI::PropertyText::update
bool update(const char *const texts[], const char *const names[], int n)
Definition: indipropertytext.cpp:39
INDI::Weather::ISNewText
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
Definition: indiweather.cpp:228
LOG_ERROR
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition: indilogger.h:72
INDI::PropertyBasic::apply
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
Definition: indipropertybasic.cpp:243
INDI::WeatherInterface::addParameter
void addParameter(std::string name, std::string label, double numMinOk, double numMaxOk, double percWarning)
addParameter Add a physical weather measurable parameter to the weather driver. The weather value has...
Definition: indiweatherinterface.cpp:131
INDI::DefaultDevice::ISGetProperties
virtual void ISGetProperties(const char *dev)
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
Definition: defaultdevice.cpp:750
name
const char * name
Definition: indiserver.c:116
OpenWeatherMap
Definition: openweathermap.h:33
INDI::Weather::CONNECTION_NONE
@ CONNECTION_NONE
Definition: indiweather.h:83
IP_RW
@ IP_RW
Definition: indiapi.h:185
INDI::PropertyBasic::setState
void setState(IPState state)
Definition: indipropertybasic.cpp:103
OpenWeatherMap::updateLocation
virtual bool updateLocation(double latitude, double longitude, double elevation) override
Update weather station location.
Definition: openweathermap.cpp:143
JsonIterator
Definition: gason.h:94
JsonValue::toString
char * toString() const
Definition: gason.h:75
OpenWeatherMap::Disconnect
bool Disconnect() override
Disconnect from device.
Definition: openweathermap.cpp:79
JsonValue::toNumber
double toNumber() const
Definition: gason.h:70
INDI::DefaultDevice::addDebugControl
void addDebugControl()
Add Debug control to the driver.
Definition: defaultdevice.cpp:639
JsonNode::key
char * key
Definition: gason.h:91
INDI::WeatherInterface::setParameterValue
void setParameterValue(std::string name, double value)
setParameterValue Update weather parameter value
Definition: indiweatherinterface.cpp:155