Instrument Neutral Distributed Interface INDI 2.2.5
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
33const char *LABELS_TAB = "Labels";
34const char *PARAMETERS_TAB = "Parameters";
35
36// We declare an auto pointer to WeatherWatcher.
37static std::unique_ptr<WeatherWatcher> weatherWatcher(new WeatherWatcher());
38
39static size_t write_data(void *contents, size_t size, size_t nmemb, void *userp)
40{
41 ((std::string *)userp)->append((char *)contents, size * nmemb);
42 return size * nmemb;
43}
44
50
52{
53 return "Weather Watcher";
54}
55
57{
58 if (watchFileTP[0].isEmpty() || watchFileTP[0].getText()[0] == '\0')
59 {
60 LOG_ERROR("Watch file must be specified first in options.");
61 return false;
62 }
63 else
64 return true;
65}
66
68{
69 return true;
70}
71
72bool WeatherWatcher::createProperties()
73{
74 if (readWatchFile() == false)
75 return false;
76 double minOK = 0, maxOK = 0, percWarn = 15;
77 for (auto const &x : weatherMap)
78 {
79 if (x.first == keywordTP[WEATHER_RAIN].getText())
80 {
81 // Caution: If both OK=0 the parameter never (re)appears in UI! (-> WeatherInterface::addParameter())
82 minOK = -1;
83 maxOK = 0;
84 percWarn = 15;
85 IUGetConfigNumber(getDeviceName(), "WEATHER_RAIN_HOUR", "MIN_OK", &minOK);
86 IUGetConfigNumber(getDeviceName(), "WEATHER_RAIN_HOUR", "MAX_OK", &maxOK);
87 IUGetConfigNumber(getDeviceName(), "WEATHER_RAIN_HOUR", "PERC_WARN", &percWarn);
88
89 addParameter("WEATHER_RAIN_HOUR", labelTP[WEATHER_RAIN].text, minOK, maxOK, percWarn);
90 if (criticalSP[WEATHER_RAIN].getState())
91 setCriticalParameter("WEATHER_RAIN_HOUR");
92 }
93 else if (x.first == keywordTP[WEATHER_TEMP].getText())
94 {
95 minOK = -10;
96 maxOK = 30;
97 percWarn = 15;
98 IUGetConfigNumber(getDeviceName(), "WEATHER_TEMPERATURE", "MIN_OK", &minOK);
99 IUGetConfigNumber(getDeviceName(), "WEATHER_TEMPERATURE", "MAX_OK", &maxOK);
100 IUGetConfigNumber(getDeviceName(), "WEATHER_TEMPERATURE", "PERC_WARN", &percWarn);
101
102 addParameter("WEATHER_TEMPERATURE", labelTP[WEATHER_TEMP].text, minOK, maxOK, percWarn);
103 if (criticalSP[WEATHER_TEMP].getState())
104 setCriticalParameter("WEATHER_TEMPERATURE");
105 }
106 else if (x.first == keywordTP[WEATHER_WIND].getText())
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", labelTP[WEATHER_WIND].text, minOK, maxOK, percWarn);
116 if (criticalSP[WEATHER_WIND].getState())
117 setCriticalParameter("WEATHER_WIND_SPEED");
118 }
119 else if (x.first == keywordTP[WEATHER_GUST].getText())
120 {
121 minOK = 0;
122 maxOK = 20;
123 percWarn = 15;
124 IUGetConfigNumber(getDeviceName(), "WEATHER_WIND_GUST", "MIN_OK", &minOK);
125 IUGetConfigNumber(getDeviceName(), "WEATHER_WIND_GUST", "MAX_OK", &maxOK);
126 IUGetConfigNumber(getDeviceName(), "WEATHER_WIND_GUST", "PERC_WARN", &percWarn);
127
128 addParameter("WEATHER_WIND_GUST", labelTP[WEATHER_GUST].text, minOK, maxOK, percWarn);
129 if (criticalSP[WEATHER_GUST].getState())
130 setCriticalParameter("WEATHER_WIND_GUST");
131 }
132 else if (x.first == keywordTP[WEATHER_CLOUD].getText())
133 {
134 minOK = 0;
135 maxOK = 20;
136 percWarn = 15;
137 IUGetConfigNumber(getDeviceName(), "WEATHER_CLOUDS", "MIN_OK", &minOK);
138 IUGetConfigNumber(getDeviceName(), "WEATHER_CLOUDS", "MAX_OK", &maxOK);
139 IUGetConfigNumber(getDeviceName(), "WEATHER_CLOUDS", "PERC_WARN", &percWarn);
140
141 addParameter("WEATHER_CLOUDS", labelTP[WEATHER_CLOUD].text, minOK, maxOK, percWarn, true);
142 if (criticalSP[WEATHER_CLOUD].getState())
143 setCriticalParameter("WEATHER_CLOUDS");
144 }
145 else if (x.first == keywordTP[WEATHER_HUM].getText())
146 {
147 minOK = 0;
148 maxOK = 100;
149 percWarn = 15;
150 IUGetConfigNumber(getDeviceName(), "WEATHER_HUMIDITY", "MIN_OK", &minOK);
151 IUGetConfigNumber(getDeviceName(), "WEATHER_HUMIDITY", "MAX_OK", &maxOK);
152 IUGetConfigNumber(getDeviceName(), "WEATHER_HUMIDITY", "PERC_WARN", &percWarn);
153
154 addParameter("WEATHER_HUMIDITY", labelTP[WEATHER_HUM].text, minOK, maxOK, percWarn);
155 if (criticalSP[WEATHER_HUM].getState())
156 setCriticalParameter("WEATHER_HUMIDITY");
157 }
158 else if (x.first == keywordTP[WEATHER_PRESS].getText())
159 {
160 minOK = 983;
161 maxOK = 1043;
162 percWarn = 15;
163 IUGetConfigNumber(getDeviceName(), "WEATHER_PRESSURE", "MIN_OK", &minOK);
164 IUGetConfigNumber(getDeviceName(), "WEATHER_PRESSURE", "MAX_OK", &maxOK);
165 IUGetConfigNumber(getDeviceName(), "WEATHER_PRESSURE", "PERC_WARN", &percWarn);
166
167 addParameter("WEATHER_PRESSURE", labelTP[WEATHER_PRESS].text, minOK, maxOK, percWarn);
168 if (criticalSP[WEATHER_PRESS].getState())
169 setCriticalParameter("WEATHER_PRESSURE");
170 }
171 else if (x.first == keywordTP[WEATHER_FORECAST].getText())
172 {
173 minOK = -1;
174 maxOK = 0;
175 percWarn = 15;
176 IUGetConfigNumber(getDeviceName(), "WEATHER_FORECAST", "MIN_OK", &minOK);
177 IUGetConfigNumber(getDeviceName(), "WEATHER_FORECAST", "MAX_OK", &maxOK);
178 IUGetConfigNumber(getDeviceName(), "WEATHER_FORECAST", "PERC_WARN", &percWarn);
179
180 addParameter("WEATHER_FORECAST", labelTP[WEATHER_FORECAST].text, minOK, maxOK, percWarn);
181 if (criticalSP[WEATHER_FORECAST].getState())
182 setCriticalParameter("WEATHER_FORECAST");
183 }
184 }
185 return true;
186}
187
188// initialize properties on launching indiserver
195
196// initialize properties after connection/disconnection
198{
199 if (isConnected())
200 {
201 getProperties();
202 createProperties();
203 INDI::Weather::updateProperties(); // define inherited properties
204 }
205 else
206 {
209 // call deliberatly here to prevent reorder of fields in indicontrol interface
210 INDI::Weather::updateProperties(); // delete inherited properties
211 // deleteProperty() does not reset widgets array to 0!! So we do it manually:
213 for (auto &oneProperty : INDI::Weather::ParametersRangeNP )
214 oneProperty.resize(0);
216 // clear array of "ParametersRangeNP"
218 }
219
220 return true;
221}
222
223// get dynamic properties
224bool WeatherWatcher::getProperties()
225{
226 // Labels for Parameters-------------------------------------------------------------------------------------
227 labelTP[WEATHER_RAIN].fill("LABEL_1", "WEATHER_RAIN_HOUR", "Rain");
228 labelTP[WEATHER_TEMP].fill("LABEL_2", "WEATHER_TEMPERATURE", "Temperature");
229 labelTP[WEATHER_WIND].fill("LABEL_3", "WEATHER_WIND_SPEED", "Wind");
230 labelTP[WEATHER_GUST].fill("LABEL_4", "WEATHER_WIND_GUST", "Gust");
231 labelTP[WEATHER_CLOUD].fill("LABEL_5", "WEATHER_CLOUDS", "Clouds");
232 labelTP[WEATHER_HUM].fill("LABEL_6", "WEATHER_HUMIDITY", "Humidity");
233 labelTP[WEATHER_PRESS].fill("lABEL_7", "WEATHER_PRESSURE", "Pressure");
234 labelTP[WEATHER_FORECAST].fill("LABEL_8", "WEATHER_FORECAST", "Forecast");
235 labelTP.fill(getDeviceName(), "LABELS", "Property Label", LABELS_TAB, IP_RW, 60, IPS_IDLE);
237 loadConfig(true, "LABELS");
238
239 // Keywords for Parameters with dynamic labels ----------------------------------------------------------------
240 keywordTP[WEATHER_RAIN].fill("KEY_1", labelTP[WEATHER_RAIN].text, "precip");
241 keywordTP[WEATHER_TEMP].fill("KEY_2", labelTP[WEATHER_TEMP].text, "temperature");
242 keywordTP[WEATHER_WIND].fill("KEY_3", labelTP[WEATHER_WIND].text, "wind");
243 keywordTP[WEATHER_GUST].fill("KEY_4", labelTP[WEATHER_GUST].text, "gust");
244 keywordTP[WEATHER_CLOUD].fill("KEY_5", labelTP[WEATHER_CLOUD].text, "clouds");
245 keywordTP[WEATHER_HUM].fill("KEY_6", labelTP[WEATHER_HUM].text, "humidity");
246 keywordTP[WEATHER_PRESS].fill("KEY_7", labelTP[WEATHER_PRESS].text, "pressure");
247 keywordTP[WEATHER_FORECAST].fill("KEY_8", labelTP[WEATHER_FORECAST].text, "forecast");
248 keywordTP.fill(getDeviceName(), "KEYWORDS", "Keywords", OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
250 loadConfig(true, "KEYWORDS");
251
252 // Critical Parameters with dynamic labels --------------------------------------------------------------------
258 criticalSP[WEATHER_HUM].fill("CRITICAL_6", labelTP[WEATHER_HUM].text, ISS_OFF);
261 criticalSP.fill(getDeviceName(), "CRITICALS", "Criticals", PARAMETERS_TAB, IP_RW, ISR_NOFMANY, 0, IPS_IDLE);
263 loadConfig(true, "CRITICALS");
264
265 return true;
266}
267// get properties from configuation
269{
271
272 watchFileTP[0].fill("URL", "URL", nullptr);
273 watchFileTP.fill(getDeviceName(), "WATCH_SOURCE", "Source", OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
274 defineProperty(watchFileTP);
275 loadConfig(true, "WATCH_SOURCE");
276
277 separatorTP[0].fill("SEPARATOR", "Separator", "=");
278 separatorTP.fill(getDeviceName(), "SEPARATOR_KEYWORD", "Separator", OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
280 loadConfig(true, "SEPARATOR_KEYWORD");
281}
282
283bool WeatherWatcher::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
284{
285 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
286 {
287 if (labelTP.isNameMatch(name))
288 {
289 labelTP.update(texts, names, n);
291 labelTP.apply();
292 return true;
293 }
294 if (watchFileTP.isNameMatch(name))
295 {
296 watchFileTP.update(texts, names, n);
297 watchFileTP.setState(IPS_OK);
298 watchFileTP.apply();
299 return true;
300 }
301 if (keywordTP.isNameMatch(name))
302 {
303 keywordTP.update(texts, names, n);
306 return true;
307 }
308
309 if (separatorTP.isNameMatch(name))
310 {
311 separatorTP.update(texts, names, n);
314 return true;
315 }
316 }
317 return INDI::Weather::ISNewText(dev, name, texts, names, n);
318}
319
320bool WeatherWatcher::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
321{
322 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
323 {
324 if (criticalSP.isNameMatch(name))
325 {
326 // pthread_mutex_lock(&lock);
327 criticalSP.update(states, names, n);
330 // pthread_mutex_unlock(&lock);
331 return true;
332 }
333 }
334 return INDI::Weather::ISNewSwitch(dev, name, states, names, n);
335}
336
338{
339
340 if (readWatchFile() == false)
341 return IPS_BUSY;
342
343 for (auto const &x : weatherMap)
344 {
345 if (x.first == keywordTP[WEATHER_RAIN].getText())
346 {
347 setParameterValue("WEATHER_RAIN_HOUR", std::strtod(x.second.c_str(), nullptr));
348 }
349 else if (x.first == keywordTP[WEATHER_TEMP].getText())
350 {
351 setParameterValue("WEATHER_TEMPERATURE", std::strtod(x.second.c_str(), nullptr));
352 }
353 else if (x.first == keywordTP[WEATHER_WIND].getText())
354 {
355 setParameterValue("WEATHER_WIND_SPEED", std::strtod(x.second.c_str(), nullptr));
356 }
357 else if (x.first == keywordTP[WEATHER_GUST].getText())
358 {
359 setParameterValue("WEATHER_WIND_GUST", std::strtod(x.second.c_str(), nullptr));
360 }
361 else if (x.first == keywordTP[WEATHER_CLOUD].getText())
362 {
363 setParameterValue("WEATHER_CLOUDS", std::strtod(x.second.c_str(), nullptr));
364 }
365 else if (x.first == keywordTP[WEATHER_HUM].getText())
366 {
367 setParameterValue("WEATHER_HUMIDITY", std::strtod(x.second.c_str(), nullptr));
368 }
369 else if (x.first == keywordTP[WEATHER_PRESS].getText())
370 {
371 setParameterValue("WEATHER_PRESSURE", std::strtod(x.second.c_str(), nullptr));
372 }
373 else if (x.first == keywordTP[WEATHER_FORECAST].getText())
374 {
375 setParameterValue("WEATHER_FORECAST", std::strtod(x.second.c_str(), nullptr));
376 }
377 }
378 return IPS_OK;
379}
380
381bool WeatherWatcher::readWatchFile()
382{
383 CURL *curl;
384 CURLcode res;
385 bool rc = false;
386 char requestURL[MAXRBUF];
387
388 AutoCNumeric locale;
389
390 if (std::string(watchFileTP[0].getText()).find("http") == 0)
391 snprintf(requestURL, MAXRBUF, "%s", watchFileTP[0].getText());
392 else
393 snprintf(requestURL, MAXRBUF, "file://%s", watchFileTP[0].getText());
394
395 curl = curl_easy_init();
396
397 if (curl)
398 {
399 std::string readBuffer;
400 curl_easy_setopt(curl, CURLOPT_URL, requestURL);
401 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
402 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
403 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
404 res = curl_easy_perform(curl);
405
406 if (res == CURLE_OK)
407 {
408 weatherMap = createMap(readBuffer);
409 rc = true;
410 }
411 curl_easy_cleanup(curl);
412 }
413 return rc;
414}
415
417{
419
420 labelTP.save(fp);
421 criticalSP.save(fp);
422 watchFileTP.save(fp);
423 keywordTP.save(fp);
425
426 return true;
427}
428
429std::map<std::string, std::string> WeatherWatcher::createMap(std::string const &s)
430{
431 std::map<std::string, std::string> m;
432
433 std::string key, val;
434 std::istringstream iss(s);
435
436 while(std::getline(std::getline(iss, key, separatorTP[0].getText()[0]) >> std::ws, val))
437 m[key] = val;
438
439 return m;
440}
bool isConnected() const
INDI::PropertyText getText(const char *name) const
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.
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
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.
void resize(size_t size)
bool isNameMatch(const char *otherName) const
bool update(const ISState states[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, ISRule rule, double timeout, IPState state)
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)
INDI::PropertyViewText * getText() const
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
INDI::PropertyNumber ParametersNP
std::vector< INDI::PropertyNumber > ParametersRangeNP
INDI::PropertyLight critialParametersLP
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
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...
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
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 IPState updateWeather() override
updateWeather Update weather conditions from device or service. The function should not change the st...
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
INDI::PropertyText keywordTP
INDI::PropertyText separatorTP
bool Disconnect() override
Disconnect from device.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
const char * getDefaultName() override
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
INDI::PropertyText labelTP
INDI::PropertySwitch criticalSP
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,...
ISState
Switch state.
Definition indiapi.h:148
@ ISS_OFF
Definition indiapi.h:149
@ IP_RW
Definition indiapi.h:184
IPState
Property state.
Definition indiapi.h:158
@ IPS_BUSY
Definition indiapi.h:161
@ IPS_IDLE
Definition indiapi.h:159
@ IPS_OK
Definition indiapi.h:160
@ ISR_NOFMANY
Definition indiapi.h:173
#define MAXRBUF
Definition indidriver.c:52
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:846
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition indilogger.h:72
#define PARAMETERS_TAB
#define getDeviceName
const char * LABELS_TAB
const char * PARAMETERS_TAB