Instrument Neutral Distributed Interface INDI  2.0.2
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 "json.h"
30 #include "locale_compat.h"
31 
32 #include <curl/curl.h>
33 
34 #include <memory>
35 #include <cstring>
36 
38 
39 // We declare an auto pointer to OpenWeatherMap.
40 std::unique_ptr<OpenWeatherMap> openWeatherMap(new OpenWeatherMap());
41 
42 static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
43 {
44  ((std::string *)userp)->append((char *)contents, size * nmemb);
45  return size * nmemb;
46 }
47 
49 {
50  setVersion(1, 2);
51 
52  owmLat = std::numeric_limits<double>::quiet_NaN();
53  owmLong = std::numeric_limits<double>::quiet_NaN();
54 
56  curl_global_init(CURL_GLOBAL_DEFAULT);
57 }
58 
60 {
61  curl_global_cleanup();
62 }
63 
65 {
66  return "OpenWeatherMap";
67 }
68 
69 void OpenWeatherMap::ISGetProperties(const char *dev)
70 {
72  defineProperty(owmAPIKeyTP);
73 }
74 
76 {
77  if (owmAPIKeyTP[0].getText() == nullptr)
78  {
79  LOG_ERROR("OpenWeatherMap API Key is not available. Please register your API key at "
80  "www.openweathermap.org and save it under Options.");
81  return false;
82  }
83 
84  return true;
85 }
86 
88 {
89  return true;
90 }
91 
93 {
95 
96  char api_key[256] = {0};
97  IUGetConfigText(getDeviceName(), "OWM_API_KEY", "API_KEY", api_key, 256);
98  owmAPIKeyTP[0].fill("API_KEY", "API Key", api_key);
99  owmAPIKeyTP.fill(getDeviceName(), "OWM_API_KEY", "OpenWeatherMap", OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
100 
101  addParameter("WEATHER_FORECAST", "Weather", 0, 0, 15);
102  addParameter("WEATHER_TEMPERATURE", "Temperature (C)", -10, 30, 15);
103  addParameter("WEATHER_PRESSURE", "Pressure (hPa)", 900, 1100, 15);
104  addParameter("WEATHER_HUMIDITY", "Humidity (%)", 0, 100, 15);
105  addParameter("WEATHER_WIND_SPEED", "Wind (m/s)", 0, 20, 15);
106  addParameter("WEATHER_RAIN_HOUR", "Rain precip (mm)", 0, 0, 15);
107  addParameter("WEATHER_SNOW_HOUR", "Snow precip (mm)", 0, 0, 15);
108  addParameter("WEATHER_CLOUD_COVER", "Clouds (%)", 0, 100, 15);
109  addParameter("WEATHER_CODE", "Status code", 200, 810, 15);
110 
111  setCriticalParameter("WEATHER_FORECAST");
112  setCriticalParameter("WEATHER_TEMPERATURE");
113  setCriticalParameter("WEATHER_WIND_SPEED");
114  setCriticalParameter("WEATHER_RAIN_HOUR");
115  setCriticalParameter("WEATHER_SNOW_HOUR");
116 
117  addDebugControl();
118 
119  return true;
120 }
121 
122 bool OpenWeatherMap::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
123 {
124  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
125  {
126  if (owmAPIKeyTP.isNameMatch(name))
127  {
128  owmAPIKeyTP.update(texts, names, n);
129  owmAPIKeyTP.setState(IPS_OK);
130  owmAPIKeyTP.apply();
131  saveConfig(true, owmAPIKeyTP.getName());
132  return true;
133  }
134  }
135 
136  return INDI::Weather::ISNewText(dev, name, texts, names, n);
137 }
138 
139 bool OpenWeatherMap::updateLocation(double latitude, double longitude, double elevation)
140 {
141  INDI_UNUSED(elevation);
142 
143  owmLat = latitude;
144  owmLong = (longitude > 180) ? (longitude - 360) : longitude;
145 
146  return true;
147 }
148 
150 {
151  CURL *curl {nullptr};
152  CURLcode res;
153  std::string readBuffer;
154  char errorBuffer[CURL_ERROR_SIZE] = {0};
155  char requestURL[MAXRBUF] = {0};
156 
157  // If location is not updated yet, return busy
158  if (std::isnan(owmLat) || std::isnan(owmLong))
159  return IPS_BUSY;
160 
161  AutoCNumeric locale;
162 
163  snprintf(requestURL, MAXRBUF, "http://api.openweathermap.org/data/2.5/weather?lat=%g&lon=%g&appid=%s&units=metric",
164  owmLat, owmLong, owmAPIKeyTP[0].getText());
165 
166  curl = curl_easy_init();
167  if (curl)
168  {
169  curl_easy_setopt(curl, CURLOPT_URL, requestURL);
170  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
171  curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
172  curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errorBuffer);
173  errorBuffer[0] = 0;
174  res = curl_easy_perform(curl);
175  curl_easy_cleanup(curl);
176  if(res != CURLE_OK)
177  {
178  if(strlen(errorBuffer))
179  {
180  LOGF_ERROR("Error %d reading data: %s", res, errorBuffer);
181  }
182  else
183  {
184  LOGF_ERROR("Error %d reading data: %s", res, curl_easy_strerror(res));
185  }
186  return IPS_ALERT;
187  }
188  }
189 
190  double forecast = 0;
191  double temperature = 0;
192  double pressure = 0;
193  double humidity = 0;
194  double wind = 0;
195  double rain = 0;
196  double snow = 0;
197  double clouds = 0;
198  int code = 0;
199 
200  try
201  {
202  json weatherReport = json::parse(readBuffer);
203 
204  weatherReport["weather"][0]["id"].get_to(code);
205  if (code >= 200 && code < 300)
206  {
207  // Thunderstorm
208  forecast = 2;
209  }
210  else if (code >= 300 && code < 400)
211  {
212  // Drizzle
213  forecast = 2;
214  }
215  else if (code >= 500 && code < 700)
216  {
217  // Rain and snow
218  forecast = 2;
219  }
220  else if (code >= 700 && code < 800)
221  {
222  // Mist and so on
223  forecast = 1;
224  }
225  else if (code == 800)
226  {
227  // Clear!
228  forecast = 0;
229  }
230  else if (code >= 801 && code <= 803)
231  {
232  // Some clouds
233  forecast = 1;
234  }
235  else if (code >= 804 && code < 900)
236  {
237  // Overcast
238  forecast = 2;
239  }
240 
241  // Temperature
242  weatherReport["main"]["temp"].get_to(temperature);
243  // Pressure
244  weatherReport["main"]["pressure"].get_to(pressure);
245  // Humidity
246  weatherReport["main"]["humidity"].get_to(humidity);
247  // Wind
248  weatherReport["wind"]["speed"].get_to(wind);
249  // Cloud
250  weatherReport["clouds"]["all"].get_to(clouds);
251  try
252  {
253  // Rain
254  weatherReport["rain"]["h"].get_to(rain);
255  // Snow
256  weatherReport["snow"]["h"].get_to(snow);
257  }
258  // Ignore error since these values do not exist in all reports.
259  catch (json::exception &e) {}
260 
261  }
262  catch (json::exception &e)
263  {
264  // output exception information
265  LOGF_ERROR("Error parsing weather report %s id: %d", e.what(), e.id);
266  return IPS_ALERT;
267  }
268 
269  setParameterValue("WEATHER_FORECAST", forecast);
270  setParameterValue("WEATHER_TEMPERATURE", temperature);
271  setParameterValue("WEATHER_PRESSURE", pressure);
272  setParameterValue("WEATHER_HUMIDITY", humidity);
273  setParameterValue("WEATHER_WIND_SPEED", wind);
274  setParameterValue("WEATHER_RAIN_HOUR", rain);
275  setParameterValue("WEATHER_SNOW_HOUR", snow);
276  setParameterValue("WEATHER_CLOUD_COVER", clouds);
277  setParameterValue("WEATHER_CODE", code);
278  return IPS_OK;
279 }
280 
282 {
284 
285  owmAPIKeyTP.save(fp);
286 
287  return true;
288 }
const char * getDeviceName() const
Definition: basedevice.cpp:821
INDI::PropertyText getText(const char *name) const
Definition: basedevice.cpp:94
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
virtual void ISGetProperties(const char *dev)
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
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
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 setParameterValue(std::string name, double value)
setParameterValue Update weather parameter value
bool setCriticalParameter(std::string param)
setCriticalParameter Set parameter that is considered critical to the operation of the observatory....
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...
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
void setWeatherConnection(const uint8_t &value)
setWeatherConnection Set Weather connection mode. Child class should call this in the constructor bef...
virtual bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
Definition: indiweather.cpp:41
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
Initilize 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
Definition: json.h:18647
ValueType & get_to(ValueType &v) const noexcept(noexcept(JSONSerializer< ValueType >::from_json(std::declval< const basic_json_t & >(), v)))
get a value (explicit)
Definition: json.h:20336
general exception of the basic_json class
Definition: json.h:4032
const char * what() const noexcept override
returns the explanatory string
Definition: json.h:4035
const int id
the id of the exception
Definition: json.h:4041
const char * OPTIONS_TAB
OPTIONS_TAB Where all the driver's options are located. Those may include auxiliary controls,...
@ IP_RW
Definition: indiapi.h:186
IPState
Property state.
Definition: indiapi.h:160
@ IPS_BUSY
Definition: indiapi.h:163
@ IPS_ALERT
Definition: indiapi.h:164
@ IPS_IDLE
Definition: indiapi.h:161
@ IPS_OK
Definition: indiapi.h:162
#define INDI_UNUSED(x)
Definition: indidevapi.h:131
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:889
#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 MAXRBUF
Definition: indiserver.cpp:102
basic_json<> json
default specialization
Definition: json.h:3196
std::unique_ptr< OpenWeatherMap > openWeatherMap(new OpenWeatherMap())