Instrument Neutral Distributed Interface INDI 2.2.5
aagsolo.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2025 Peter Englmaier. All rights reserved.
3
4 INDI AAG Solo CloudWatcher 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 "aagsolo.h"
26#include "locale_compat.h"
27
28#include <curl/curl.h>
29
30#include <memory>
31#include <cstring>
32
33
34// We declare an auto pointer to AAGSolo.
35static std::unique_ptr<AAGSolo> aag_solo(new AAGSolo());
36
37static size_t write_data(void *contents, size_t size, size_t nmemb, void *userp)
38{
39 ((std::string *)userp)->append((char *)contents, size * nmemb);
40 return size * nmemb;
41}
42
48
50{
51 return "AAG Solo Cloudwatcher";
52}
53
55{
56 if (soloHostTP[0].getText() == nullptr
57 || soloHostTP[0].getText()[0] == '\0')
58 {
59 LOG_ERROR("AAG Solo Cloudwatcher host name or IP must be specified in options tab. Example: aagsolo.local.net");
60 return false;
61 }
62 else
63 return true;
64}
65
67{
68 return true;
69}
70
71bool AAGSolo::createProperties()
72{
73 if (readWatchFile() == false)
74 return false;
75 double minOK = 0, maxOK = 0, percWarn = 15;
76 for (auto const &x : weatherMap)
77 {
78 if (x.first == keys[RAIN].key)
79 {
80 // Caution: If both OK=0 the parameter never (re)appears in UI! (-> WeatherInterface::addParameter())
81 minOK = 3150;
82 maxOK = 5000;
83 percWarn = 0;
84 IUGetConfigNumber(getDeviceName(), "WEATHER_RAIN", "MIN_OK", &minOK);
85 IUGetConfigNumber(getDeviceName(), "WEATHER_RAIN", "MAX_OK", &maxOK);
86 IUGetConfigNumber(getDeviceName(), "WEATHER_RAIN", "PERC_WARN", &percWarn);
87
88 addParameter("WEATHER_RAIN", "Rain", minOK, maxOK, percWarn);
89 if (criticalSP[0].getState()) // TODO: do not specify index here
90 setCriticalParameter("WEATHER_RAIN");
91 }
92 if (x.first == keys[LIGHTMPSAS].key)
93 {
94 minOK = 15;
95 maxOK = 30;
96 percWarn = 30;
97 IUGetConfigNumber(getDeviceName(), "WEATHER_LIGHT", "MIN_OK", &minOK);
98 IUGetConfigNumber(getDeviceName(), "WEATHER_LIGHT", "MAX_OK", &maxOK);
99 IUGetConfigNumber(getDeviceName(), "WEATHER_LIGHT", "PERC_WARN", &percWarn);
100
101 addParameter("WEATHER_LIGHT", "Light", minOK, maxOK, percWarn);
102 if (criticalSP[7].getState()) // TODO: do not specify index here
103 setCriticalParameter("WEATHER_LIGHT");
104 }
105 else if (x.first == keys[TEMP].key)
106 {
107 minOK = -10;
108 maxOK = 30;
109 percWarn = 15;
110 IUGetConfigNumber(getDeviceName(), "WEATHER_TEMPERATURE", "MIN_OK", &minOK);
111 IUGetConfigNumber(getDeviceName(), "WEATHER_TEMPERATURE", "MAX_OK", &maxOK);
112 IUGetConfigNumber(getDeviceName(), "WEATHER_TEMPERATURE", "PERC_WARN", &percWarn);
113
114 addParameter("WEATHER_TEMPERATURE", "Temperature", minOK, maxOK, percWarn);
115 if (criticalSP[1].getState())
116 setCriticalParameter("WEATHER_TEMPERATURE");
117 }
118 else if (x.first == keys[WIND].key)
119 {
120 minOK = 0;
121 maxOK = 20;
122 percWarn = 15;
123 IUGetConfigNumber(getDeviceName(), "WEATHER_WIND_SPEED", "MIN_OK", &minOK);
124 IUGetConfigNumber(getDeviceName(), "WEATHER_WIND_SPEED", "MAX_OK", &maxOK);
125 IUGetConfigNumber(getDeviceName(), "WEATHER_WIND_SPEED", "PERC_WARN", &percWarn);
126
127 addParameter("WEATHER_WIND_SPEED", "Wind speed", minOK, maxOK, percWarn);
128 if (criticalSP[2].getState())
129 setCriticalParameter("WEATHER_WIND_SPEED");
130 }
131 else if (x.first == keys[GUST].key)
132 {
133 minOK = 0;
134 maxOK = 20;
135 percWarn = 15;
136 IUGetConfigNumber(getDeviceName(), "WEATHER_WIND_GUST", "MIN_OK", &minOK);
137 IUGetConfigNumber(getDeviceName(), "WEATHER_WIND_GUST", "MAX_OK", &maxOK);
138 IUGetConfigNumber(getDeviceName(), "WEATHER_WIND_GUST", "PERC_WARN", &percWarn);
139
140 addParameter("WEATHER_WIND_GUST", "Gust", minOK, maxOK, percWarn);
141 if (criticalSP[3].getState())
142 setCriticalParameter("WEATHER_WIND_GUST");
143 }
144 else if (x.first == keys[CLOUDS].key)
145 {
146 minOK = -30;
147 maxOK = -10;
148 percWarn = 15;
149 IUGetConfigNumber(getDeviceName(), "WEATHER_CLOUDS", "MIN_OK", &minOK);
150 IUGetConfigNumber(getDeviceName(), "WEATHER_CLOUDS", "MAX_OK", &maxOK);
151 IUGetConfigNumber(getDeviceName(), "WEATHER_CLOUDS", "PERC_WARN", &percWarn);
152
153 addParameter("WEATHER_CLOUDS", "Clouds", minOK, maxOK, percWarn);
154 if (criticalSP[4].getState())
155 setCriticalParameter("WEATHER_CLOUDS");
156 }
157 else if (x.first == keys[HUM].key)
158 {
159 minOK = 20;
160 maxOK = 95;
161 percWarn = 15;
162 IUGetConfigNumber(getDeviceName(), "WEATHER_HUMIDITY", "MIN_OK", &minOK);
163 IUGetConfigNumber(getDeviceName(), "WEATHER_HUMIDITY", "MAX_OK", &maxOK);
164 IUGetConfigNumber(getDeviceName(), "WEATHER_HUMIDITY", "PERC_WARN", &percWarn);
165
166 addParameter("WEATHER_HUMIDITY", "Humidity", minOK, maxOK, percWarn);
167 if (criticalSP[5].getState())
168 setCriticalParameter("WEATHER_HUMIDITY");
169 }
170 else if (x.first == keys[RELPRESS].key)
171 {
172 minOK = 983;
173 maxOK = 1043;
174 percWarn = 15;
175 IUGetConfigNumber(getDeviceName(), "WEATHER_PRESSURE", "MIN_OK", &minOK);
176 IUGetConfigNumber(getDeviceName(), "WEATHER_PRESSURE", "MAX_OK", &maxOK);
177 IUGetConfigNumber(getDeviceName(), "WEATHER_PRESSURE", "PERC_WARN", &percWarn);
178
179 addParameter("WEATHER_PRESSURE", "rel. pressure", minOK, maxOK, percWarn);
180 if (criticalSP[6].getState())
181 setCriticalParameter("WEATHER_PRESSURE");
182 }
183 else if (x.first == keys[SAFE].key)
184 {
185 minOK = 0.5;
186 maxOK = 1.5;
187 percWarn = 0;
188 IUGetConfigNumber(getDeviceName(), "WEATHER_ISSAFE", "MIN_OK", &minOK);
189 IUGetConfigNumber(getDeviceName(), "WEATHER_ISSAFE", "MAX_OK", &maxOK);
190 IUGetConfigNumber(getDeviceName(), "WEATHER_ISSAFE", "PERC_WARN", &percWarn);
191
192 addParameter("WEATHER_ISSAFE", "Safe", minOK, maxOK, percWarn);
193 if (criticalSP[8].getState())
194 setCriticalParameter("WEATHER_ISSAFE");
195 }
196 }
197 return true;
198}
199
200// initialize properties on launching indiserver
202{
203 INDI::Weather::initProperties(); // FIXME: parameters shoudl be statusGroup and paramsGroup
205
206 // Critical Parameters --------------------------------------------------------------------
207 criticalSP[0].fill("CRITICAL_1", "Rain", ISS_ON);
208 criticalSP[1].fill("CRITICAL_2", "Temperature", ISS_OFF);
209 criticalSP[2].fill("CRITICAL_3", "Wind", ISS_ON);
210 criticalSP[3].fill("CRITICAL_4", "Gust", ISS_ON);
211 criticalSP[4].fill("CRITICAL_5", "Clouds", ISS_ON);
212 criticalSP[5].fill("CRITICAL_6", "Humidity", ISS_OFF);
213 criticalSP[6].fill("CRITICAL_7", "Pressure", ISS_OFF);
214 criticalSP[7].fill("CRITICAL_8", "Light", ISS_OFF);
215 criticalSP[8].fill("CRITICAL_9", "Safe", ISS_OFF);
216 criticalSP.fill(getDeviceName(), "CRITICALS", "Criticals", OPTIONS_TAB, IP_RW, ISR_NOFMANY, 0, IPS_IDLE);
218
219 soloInfoTP[0].fill("CWINFO", "cwinfo", nullptr);
220 soloInfoTP[1].fill("DATATIME", "GMT Time", nullptr);
221 soloInfoTP.fill(getDeviceName(), "DEVICEINFO", "Device Info", INFO_TAB, IP_RO, 60, IPS_IDLE);
222 return true;
223}
224
225// initialize properties after connection/disconnection
227{
228 if (isConnected())
229 {
230 createProperties();
231 INDI::Weather::updateProperties(); // define inherited properties
232 }
233 else
234 {
235 // call deliberatly here to prevent reorder of fields in indicontrol interface
236 INDI::Weather::updateProperties(); // delete inherited properties
237 // deleteProperty() does not reset widgets array to 0!! So we do it manually:
239 for (auto &oneProperty : INDI::Weather::ParametersRangeNP )
240 oneProperty.resize(0);
242 // clear array of "ParametersRangeNP"
244 }
246 defineProperty(soloInfoTP);
247
248 return true;
249}
250
251// get properties from configuation
252void AAGSolo::ISGetProperties(const char *dev)
253{
255
256 soloHostTP[0].fill("HOSTNAME", "HOSTNAME", nullptr);
257 soloHostTP.fill(getDeviceName(), "SOLO_HOST", "Solo", OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
258 defineProperty(soloHostTP);
259 loadConfig(true, "SOLO_HOST");
260}
261
262bool AAGSolo::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
263{
264 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
265 {
266 if (soloHostTP.isNameMatch(name))
267 {
268 soloHostTP.update(texts, names, n);
269 soloHostTP.setState(IPS_OK);
270 soloHostTP.apply();
271 return true;
272 }
273 }
274 return INDI::Weather::ISNewText(dev, name, texts, names, n);
275}
276
277bool AAGSolo::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
278{
279 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
280 {
281 if (criticalSP.isNameMatch(name))
282 {
283 // pthread_mutex_lock(&lock);
284 criticalSP.update(states, names, n);
287 // pthread_mutex_unlock(&lock);
288 if (isConnected())
289 {
290 LOG_WARN("Changing criticals requires driver reconnect");
291 }
292 return true;
293 }
294 }
295 return INDI::Weather::ISNewSwitch(dev, name, states, names, n);
296}
297
299{
300 if (readWatchFile() == false)
301 return IPS_BUSY;
302
303 for (auto const &x : weatherMap)
304 {
305 for (int i = 0; i < numKeys; i++)
306 {
307 if (x.first == keys[i].key)
308 {
309 setParameterValue(keys[i].parameter, std::strtod(x.second.c_str(), nullptr));
310 break;
311 }
312 }
313 if (x.first == "cwinfo")
314 {
315 soloInfoTP[0].fill("CWINFO", "cwinfo", x.second.c_str());
316 soloInfoTP.setState(IPS_OK);
317 soloInfoTP.apply();
318 }
319 else if (x.first == "dataGMTTime")
320 {
321 soloInfoTP[1].fill("DATATIME", "GMT Time", x.second.c_str());
322 soloInfoTP.setState(IPS_OK);
323 soloInfoTP.apply();
324 }
325 }
326 return IPS_OK;
327}
328
329bool AAGSolo::readWatchFile()
330{
331 CURL *curl;
332 CURLcode res;
333 bool rc = false;
334 char requestURL[MAXRBUF];
335
336 AutoCNumeric locale;
337
338 snprintf(requestURL, MAXRBUF, "http://%s/cgi-bin/cgiLastData", soloHostTP[0].getText());
339
340 curl = curl_easy_init();
341
342 if (curl)
343 {
344 std::string readBuffer;
345 curl_easy_setopt(curl, CURLOPT_URL, requestURL);
346 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
347 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
348 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
349 res = curl_easy_perform(curl);
350
351 if (res == CURLE_OK)
352 {
353 weatherMap = createMap(readBuffer);
354 rc = true;
355 }
356 curl_easy_cleanup(curl);
357 }
358 return rc;
359}
360
362{
364
365 criticalSP.save(fp);
366 soloHostTP.save(fp);
367
368 return true;
369}
370
371std::map<std::string, std::string> AAGSolo::createMap(std::string const &s)
372{
373 std::map<std::string, std::string> m;
374
375 std::string key, val;
376 std::istringstream iss(s);
377
378 while(std::getline(std::getline(iss, key, '=') >> std::ws, val))
379 m[key] = val;
380
381 return m;
382}
@ RELPRESS
Definition aagsolo.h:54
@ LIGHTMPSAS
Definition aagsolo.h:54
@ SAFE
Definition aagsolo.h:54
@ WIND
Definition aagsolo.h:53
@ GUST
Definition aagsolo.h:53
@ HUM
Definition aagsolo.h:54
@ CLOUDS
Definition aagsolo.h:53
@ RAIN
Definition aagsolo.h:53
@ TEMP
Definition aagsolo.h:53
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
Definition aagsolo.cpp:277
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
Definition aagsolo.cpp:226
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
Definition aagsolo.cpp:262
static const int numKeys
Definition aagsolo.h:59
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
Definition aagsolo.cpp:201
const char * getDefaultName() override
Definition aagsolo.cpp:49
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
Definition aagsolo.cpp:361
AAGSolo()
Definition aagsolo.cpp:43
key keys[numKeys]
Definition aagsolo.h:60
bool Connect() override
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
Definition aagsolo.cpp:54
virtual IPState updateWeather() override
updateWeather Update weather conditions from device or service. The function should not change the st...
Definition aagsolo.cpp:298
bool Disconnect() override
Disconnect from device.
Definition aagsolo.cpp:66
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 aagsolo.cpp:252
INDI::PropertySwitch criticalSP
Definition aagsolo.h:51
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.
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
bool load()
load Attempt to load property values from configuration file.
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)
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.
const char * INFO_TAB
INFO_TAB Where all the properties for general information are located.
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
@ ISS_ON
Definition indiapi.h:150
@ IP_RW
Definition indiapi.h:184
@ IP_RO
Definition indiapi.h:182
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_WARN(txt)
Definition indilogger.h:73
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition indilogger.h:72
#define getDeviceName
std::string key
Definition aagsolo.h:56