Instrument Neutral Distributed Interface INDI  2.0.2
weatherwatcher.cpp
Go to the documentation of this file.
1 /*******************************************************************************
2  Copyright(c) 2018 Jasem Mutlaq. All rights reserved.
3 
4  INDI Weather Watcher Driver
5 
6  This program is free software; you can redistribute it and/or modify it
7  under the terms of the GNU General Public License as published by the Free
8  Software Foundation; either version 2 of the License, or (at your option)
9  any later version.
10 
11  This program is distributed in the hope that it will be useful, but WITHOUT
12  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14  more details.
15 
16  You should have received a copy of the GNU Library General Public License
17  along with this library; see the file COPYING.LIB. If not, write to
18  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  Boston, MA 02110-1301, USA.
20 
21  The full GNU General Public License is included in this distribution in the
22  file called LICENSE.
23 *******************************************************************************/
24 
25 #include "weatherwatcher.h"
26 #include "locale_compat.h"
27 
28 #include <curl/curl.h>
29 
30 #include <memory>
31 #include <cstring>
32 
33 // We declare an auto pointer to WeatherWatcher.
34 static std::unique_ptr<WeatherWatcher> weatherWatcher(new WeatherWatcher());
35 
36 static size_t write_data(void *contents, size_t size, size_t nmemb, void *userp)
37 {
38  ((std::string *)userp)->append((char *)contents, size * nmemb);
39  return size * nmemb;
40 }
41 
43 {
44  setVersion(1, 2);
45 
47 }
48 
50 {
51  return "Weather Watcher";
52 }
53 
55 {
56  if (watchFileT[0].text == nullptr || watchFileT[0].text[0] == '\0')
57  {
58  LOG_ERROR("Watch file must be specified first in options.");
59  return false;
60  }
61 
62  return createPropertiesFromMap();
63 }
64 
66 {
67  return true;
68 }
69 
70 bool WeatherWatcher::createPropertiesFromMap()
71 {
72  // already parsed
73  if (initialParse)
74  return true;
75 
76  if (readWatchFile() == false)
77  return false;
78 
79  double minOK = 0, maxOK = 0, percWarn = 15;
80  for (auto const &x : weatherMap)
81  {
82  if (x.first == keywordT[0].text)
83  {
84  minOK = 0;
85  maxOK = 0;
86  percWarn = 15;
87  IUGetConfigNumber(getDeviceName(), "WEATHER_RAIN_HOUR", "MIN_OK", &minOK);
88  IUGetConfigNumber(getDeviceName(), "WEATHER_RAIN_HOUR", "MAX_OK", &maxOK);
89  IUGetConfigNumber(getDeviceName(), "WEATHER_RAIN_HOUR", "PERC_WARN", &percWarn);
90 
91  addParameter("WEATHER_RAIN_HOUR", "Rain (mm)", minOK, maxOK, percWarn);
92  setCriticalParameter("WEATHER_RAIN_HOUR");
93  }
94  else if (x.first == keywordT[1].text)
95  {
96  minOK = -10;
97  maxOK = 30;
98  percWarn = 15;
99  IUGetConfigNumber(getDeviceName(), "WEATHER_TEMPERATURE", "MIN_OK", &minOK);
100  IUGetConfigNumber(getDeviceName(), "WEATHER_TEMPERATURE", "MAX_OK", &maxOK);
101  IUGetConfigNumber(getDeviceName(), "WEATHER_TEMPERATURE", "PERC_WARN", &percWarn);
102 
103  addParameter("WEATHER_TEMPERATURE", "Temperature (C)", minOK, maxOK, percWarn);
104  setCriticalParameter("WEATHER_TEMPERATURE");
105  }
106  else if (x.first == keywordT[2].text)
107  {
108  minOK = 0;
109  maxOK = 20;
110  percWarn = 15;
111  IUGetConfigNumber(getDeviceName(), "WEATHER_WIND_SPEED", "MIN_OK", &minOK);
112  IUGetConfigNumber(getDeviceName(), "WEATHER_WIND_SPEED", "MAX_OK", &maxOK);
113  IUGetConfigNumber(getDeviceName(), "WEATHER_WIND_SPEED", "PERC_WARN", &percWarn);
114 
115  addParameter("WEATHER_WIND_SPEED", "Wind (kph)", minOK, maxOK, percWarn);
116  setCriticalParameter("WEATHER_WIND_SPEED");
117  }
118  else if (x.first == keywordT[3].text)
119  {
120  minOK = 0;
121  maxOK = 20;
122  percWarn = 15;
123  IUGetConfigNumber(getDeviceName(), "WEATHER_WIND_GUST", "MIN_OK", &minOK);
124  IUGetConfigNumber(getDeviceName(), "WEATHER_WIND_GUST", "MAX_OK", &maxOK);
125  IUGetConfigNumber(getDeviceName(), "WEATHER_WIND_GUST", "PERC_WARN", &percWarn);
126 
127  addParameter("WEATHER_WIND_GUST", "Gust (kph)", minOK, maxOK, percWarn);
128  }
129  else if (x.first == keywordT[4].text)
130  {
131  minOK = 0;
132  maxOK = 20;
133  percWarn = 15;
134  IUGetConfigNumber(getDeviceName(), "WEATHER_CLOUDS", "MIN_OK", &minOK);
135  IUGetConfigNumber(getDeviceName(), "WEATHER_CLOUDS", "MAX_OK", &maxOK);
136  IUGetConfigNumber(getDeviceName(), "WEATHER_CLOUDS", "PERC_WARN", &percWarn);
137 
138  addParameter("WEATHER_CLOUDS", "Clouds (%)", minOK, maxOK, percWarn);
139  setCriticalParameter("WEATHER_CLOUDS");
140  }
141  else if (x.first == keywordT[5].text)
142  {
143  minOK = 0;
144  maxOK = 100;
145  percWarn = 15;
146  IUGetConfigNumber(getDeviceName(), "WEATHER_HUMIDITY", "MIN_OK", &minOK);
147  IUGetConfigNumber(getDeviceName(), "WEATHER_HUMIDITY", "MAX_OK", &maxOK);
148  IUGetConfigNumber(getDeviceName(), "WEATHER_HUMIDITY", "PERC_WARN", &percWarn);
149 
150  addParameter("WEATHER_HUMIDITY", "Humidity (%)", minOK, maxOK, percWarn);
151  setCriticalParameter("WEATHER_HUMIDITY");
152  }
153  else if (x.first == keywordT[6].text)
154  {
155  minOK = 983;
156  maxOK = 1043;
157  percWarn = 15;
158  IUGetConfigNumber(getDeviceName(), "WEATHER_PRESSURE", "MIN_OK", &minOK);
159  IUGetConfigNumber(getDeviceName(), "WEATHER_PRESSURE", "MAX_OK", &maxOK);
160  IUGetConfigNumber(getDeviceName(), "WEATHER_PRESSURE", "PERC_WARN", &percWarn);
161 
162  addParameter("WEATHER_PRESSURE", "Pressure (hPa)", minOK, maxOK, percWarn);
163  setCriticalParameter("WEATHER_PRESSURE");
164  }
165  else if (x.first == keywordT[7].text)
166  {
167  minOK = 0;
168  maxOK = 0;
169  percWarn = 15;
170  IUGetConfigNumber(getDeviceName(), "WEATHER_FORECAST", "MIN_OK", &minOK);
171  IUGetConfigNumber(getDeviceName(), "WEATHER_FORECAST", "MAX_OK", &maxOK);
172  IUGetConfigNumber(getDeviceName(), "WEATHER_FORECAST", "PERC_WARN", &percWarn);
173 
174  addParameter("WEATHER_FORECAST", "Weather", minOK, maxOK, percWarn);
175  setCriticalParameter("WEATHER_FORECAST");
176  }
177  }
178 
179  initialParse = true;
180 
181  return true;
182 }
183 
185 {
187 
188  IUFillText(&keywordT[0], "RAIN", "Rain", "precip");
189  IUFillText(&keywordT[1], "TEMP", "Temperature", "temperature");
190  IUFillText(&keywordT[2], "WIND", "Wind", "wind");
191  IUFillText(&keywordT[3], "GUST", "Gust", "gust");
192  IUFillText(&keywordT[4], "CLOUDS", "Clouds", "clouds");
193  IUFillText(&keywordT[5], "HUMIDITY", "Humidity", "humidity");
194  IUFillText(&keywordT[6], "PRESSURE", "Pressure", "pressure");
195  IUFillText(&keywordT[7], "FORECAST", "Forecast", "forecast");
196  IUFillTextVector(&keywordTP, keywordT, 8, getDeviceName(), "KEYWORD", "Keywords", OPTIONS_TAB, IP_RW,
197  60, IPS_IDLE);
198 
199  IUFillText(&watchFileT[0], "URL", "File", nullptr);
200  IUFillTextVector(&watchFileTP, watchFileT, 1, getDeviceName(), "WATCH_SOURCE", "Source", OPTIONS_TAB, IP_RW,
201  60, IPS_IDLE);
202 
203  IUFillText(&separatorT[0], "SEPARATOR", "Separator", "=");
204  IUFillTextVector(&separatorTP, separatorT, 1, getDeviceName(), "SEPARATOR_KEYWORD", "Separator", OPTIONS_TAB, IP_RW,
205  60, IPS_IDLE);
206 
207  addDebugControl();
208 
209  return true;
210 }
211 
212 void WeatherWatcher::ISGetProperties(const char *dev)
213 {
215 
216  defineProperty(&watchFileTP);
217  loadConfig(true, "WATCH_SOURCE");
218 
220  loadConfig(true, "KEYWORD");
221 
223  loadConfig(true, "SEPARATOR_KEYWORD");
224 
225 }
226 
227 bool WeatherWatcher::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
228 {
229  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
230  {
231  if (!strcmp(watchFileTP.name, name))
232  {
233  IUUpdateText(&watchFileTP, texts, names, n);
234  watchFileTP.s = IPS_OK;
235  IDSetText(&watchFileTP, nullptr);
236  return true;
237  }
238  if (!strcmp(keywordTP.name, name))
239  {
240  IUUpdateText(&keywordTP, texts, names, n);
241  keywordTP.s = IPS_OK;
242  IDSetText(&keywordTP, nullptr);
243  return true;
244  }
245 
246  if (!strcmp(separatorTP.name, name))
247  {
248  IUUpdateText(&separatorTP, texts, names, n);
249  separatorTP.s = IPS_OK;
250  IDSetText(&separatorTP, nullptr);
251  return true;
252  }
253  }
254 
255  return INDI::Weather::ISNewText(dev, name, texts, names, n);
256 }
257 
259 {
260 
261  if (readWatchFile() == false)
262  return IPS_BUSY;
263 
264  for (auto const &x : weatherMap)
265  {
266  if (x.first == keywordT[0].text)
267  {
268  setParameterValue("WEATHER_RAIN_HOUR", std::strtod(x.second.c_str(), nullptr));
269  }
270  else if (x.first == keywordT[1].text)
271  {
272  setParameterValue("WEATHER_TEMPERATURE", std::strtod(x.second.c_str(), nullptr));
273  }
274  else if (x.first == keywordT[2].text)
275  {
276  setParameterValue("WEATHER_WIND_SPEED", std::strtod(x.second.c_str(), nullptr));
277  }
278  else if (x.first == keywordT[3].text)
279  {
280  setParameterValue("WEATHER_WIND_GUST", std::strtod(x.second.c_str(), nullptr));
281  }
282  else if (x.first == keywordT[4].text)
283  {
284  setParameterValue("WEATHER_CLOUDS", std::strtod(x.second.c_str(), nullptr));
285  }
286  else if (x.first == keywordT[5].text)
287  {
288  setParameterValue("WEATHER_HUMIDITY", std::strtod(x.second.c_str(), nullptr));
289  }
290  else if (x.first == keywordT[6].text)
291  {
292  setParameterValue("WEATHER_PRESSURE", std::strtod(x.second.c_str(), nullptr));
293  }
294  else if (x.first == keywordT[7].text)
295  {
296  setParameterValue("WEATHER_FORECAST", std::strtod(x.second.c_str(), nullptr));
297  }
298  }
299 
300  return IPS_OK;
301 }
302 
303 bool WeatherWatcher::readWatchFile()
304 {
305  CURL *curl;
306  CURLcode res;
307  bool rc = false;
308  char requestURL[MAXRBUF];
309 
310  AutoCNumeric locale;
311 
312  if (std::string(watchFileT[0].text).find("http") == 0)
313  snprintf(requestURL, MAXRBUF, "%s", watchFileT[0].text);
314  else
315  snprintf(requestURL, MAXRBUF, "file://%s", watchFileT[0].text);
316 
317  curl = curl_easy_init();
318 
319  if (curl)
320  {
321  curl_easy_setopt(curl, CURLOPT_URL, requestURL);
322  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
323  curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
324  res = curl_easy_perform(curl);
325 
326  if (res == CURLE_OK)
327  {
328  weatherMap = createMap(readBuffer);
329  rc = true;
330  }
331  curl_easy_cleanup(curl);
332  }
333 
334  return rc;
335 }
336 
338 {
340 
341  IUSaveConfigText(fp, &watchFileTP);
344 
345  return true;
346 }
347 
348 std::map<std::string, std::string> WeatherWatcher::createMap(std::string const &s)
349 {
350  std::map<std::string, std::string> m;
351 
352  std::string key, val;
353  std::istringstream iss(s);
354 
355  while(std::getline(std::getline(iss, key, separatorT[0].text[0]) >> std::ws, val))
356  m[key] = val;
357 
358  return m;
359 }
const char * getDeviceName() const
Definition: basedevice.cpp:821
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.
virtual bool loadConfig(bool silent=false, const char *property=nullptr)
Load the last saved configuration file.
void defineProperty(INumberVectorProperty *property)
void addDebugControl()
Add Debug control to the driver.
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...
IText keywordT[8]
virtual IPState updateWeather() override
updateWeather Update weather conditions from device or service. The function should not change the st...
IText separatorT[1]
ITextVectorProperty separatorTP
ITextVectorProperty keywordTP
bool Disconnect() override
Disconnect from device.
const char * getDefaultName() override
virtual bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
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...
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
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_IDLE
Definition: indiapi.h:161
@ IPS_OK
Definition: indiapi.h:162
void IUFillTextVector(ITextVectorProperty *tvp, IText *tp, int ntp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a text vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:291
void IUFillText(IText *tp, const char *name, const char *label, const char *initialText)
Assign attributes for a text property. The text's auxiliary elements will be set to NULL.
Definition: indidevapi.c:198
void IUSaveConfigText(FILE *fp, const ITextVectorProperty *tvp)
Add a text vector property value to the configuration file.
Definition: indidevapi.c:20
int IUUpdateText(ITextVectorProperty *tvp, char *texts[], char *names[], int n)
Update all text members in a text vector property.
Definition: indidriver.c:1396
int IUGetConfigNumber(const char *dev, const char *property, const char *member, double *value)
IUGetConfigNumber Opens configuration file and reads single number property.
Definition: indidriver.c:831
void IDSetText(const ITextVectorProperty *tvp, const char *fmt,...)
Definition: indidriver.c:1191
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition: indilogger.h:72
#define MAXRBUF
Definition: indiserver.cpp:102
char name[MAXINDINAME]
Definition: indiapi.h:250