Instrument Neutral Distributed Interface INDI  2.0.2
weather_safety_proxy.cpp
Go to the documentation of this file.
1 /*******************************************************************************
2  Copyright(c) 2019 Hans Lambermont. All rights reserved.
3 
4  INDI Weather Safety Proxy
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 <memory>
26 #include <unistd.h>
27 #include <sys/wait.h>
28 #include <cstring>
29 #include <curl/curl.h>
30 
31 #include "json.h"
32 #include "weather_safety_proxy.h"
33 
35 
36 std::unique_ptr<WeatherSafetyProxy> weatherSafetyProxy(new WeatherSafetyProxy());
37 
38 static size_t WSP_WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
39 {
40  ((std::string *)userp)->append((char *)contents, size * nmemb);
41  return size * nmemb;
42 }
43 
45 {
46  setVersion(1, 0);
47 
49 }
50 
52 
54 {
55  return "Weather Safety Proxy";
56 }
57 
59 {
60  return true;
61 }
62 
64 {
65  return true;
66 }
67 
69 {
71 
72  IUFillText(&keywordT[0], "WEATHER_CONDITION", "Weather Condition", "condition");
73  IUFillTextVector(&keywordTP, keywordT, 1, getDeviceName(), "KEYWORD", "Keywords", OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
74 
75  IUFillText(&ScriptsT[WSP_SCRIPT], "WEATHER_SAFETY_SCRIPT", "Weather safety script",
76  "/usr/local/share/indi/scripts/weather_status.py");
77  IUFillTextVector(&ScriptsTP, ScriptsT, WSP_SCRIPT_COUNT, getDefaultName(), "WEATHER_SAFETY_SCRIPTS", "Script", OPTIONS_TAB,
78  IP_RW, 100, IPS_IDLE);
79 
80  IUFillText(&UrlT[WSP_URL], "WEATHER_SAFETY_URL", "Weather safety URL", "http://0.0.0.0:5000/weather/safety");
81  IUFillTextVector(&UrlTP, UrlT, WSP_URL_COUNT, getDefaultName(), "WEATHER_SAFETY_URLS", "Url", OPTIONS_TAB, IP_RW, 100,
82  IPS_IDLE);
83 
84  IUFillSwitch(&ScriptOrCurlS[WSP_USE_SCRIPT], "Use script", "", ISS_ON);
85  IUFillSwitch(&ScriptOrCurlS[WSP_USE_CURL], "Use url", "", ISS_OFF);
86  IUFillSwitchVector(&ScriptOrCurlSP, ScriptOrCurlS, WSP_USE_COUNT, getDeviceName(), "SCRIPT_OR_CURL", "Script or url",
88 
89  IUFillNumber(&softErrorHysteresisN[WSP_SOFT_ERROR_MAX], "SOFT_ERROR_MAX", "Max soft errors", "%g", 0.0, 1000.0, 1.0, 30.0);
90  IUFillNumber(&softErrorHysteresisN[WSP_SOFT_ERROR_RECOVERY], "SOFT_ERROR_RECOVERY", "Minimum soft error for recovery", "%g",
91  0.0, 1000.0, 1.0, 7.0);
92  IUFillNumberVector(&softErrorHysteresisNP, softErrorHysteresisN, 2, getDeviceName(), "SOFT_ERROR_HYSTERESIS",
93  "Soft error hysterese", OPTIONS_TAB, IP_RW, 0, IPS_IDLE);
94 
95  addParameter("WEATHER_SAFETY", "Weather Safety", 0.9, 1.1, 0); // 0 is unsafe, 1 is safe
96  setCriticalParameter("WEATHER_SAFETY");
97 
98  IUFillText(&reasonsT[0], "Reasons", "", nullptr);
99  IUFillTextVector(&reasonsTP, reasonsT, 1, getDeviceName(), "WEATHER_SAFETY_REASONS", "Weather Safety Reasons",
101 
102  addDebugControl();
103 
104  return true;
105 }
106 
108 {
110 
111  if (isConnected())
112  {
113  defineProperty(&reasonsTP);
114  }
115  else
116  {
117  deleteProperty(reasonsTP.name);
118  }
119 
120  return true;
121 }
122 
124 {
126  IUSaveConfigText(fp, &ScriptsTP);
127  IUSaveConfigText(fp, &UrlTP);
128  IUSaveConfigSwitch(fp, &ScriptOrCurlSP);
129  IUSaveConfigNumber(fp, &softErrorHysteresisNP);
130  return true;
131 }
132 
134 {
136  static bool once = true;
137  if (once)
138  {
139  once = false;
140  defineProperty(&ScriptsTP);
141  defineProperty(&UrlTP);
142  defineProperty(&ScriptOrCurlSP);
143  defineProperty(&softErrorHysteresisNP);
144  loadConfig(false, "WEATHER_SAFETY_SCRIPTS");
145  loadConfig(false, "WEATHER_SAFETY_URLS");
146  loadConfig(false, "SCRIPT_OR_CURL");
147  loadConfig(false, "SOFT_ERROR_HYSTERESIS");
148  }
149 }
150 
151 bool WeatherSafetyProxy::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
152 {
153  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
154  {
155  if (strcmp(name, softErrorHysteresisNP.name) == 0)
156  {
157  IUUpdateNumber(&softErrorHysteresisNP, values, names, n);
158  softErrorHysteresisNP.s = IPS_OK;
159  IDSetNumber(&softErrorHysteresisNP, nullptr);
160  return true;
161  }
162  }
163  return INDI::Weather::ISNewNumber(dev, name, values, names, n);
164 }
165 
166 bool WeatherSafetyProxy::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
167 {
168  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
169  {
170  if (strcmp(name, keywordTP.name) == 0)
171  {
172  keywordTP.s = IPS_OK;
173  IUUpdateText(&keywordTP, texts, names, n);
174  // update client display
175  IDSetText(&keywordTP, nullptr);
176  return true;
177  }
178  if (strcmp(name, ScriptsTP.name) == 0)
179  {
180  ScriptsTP.s = IPS_OK;
181  IUUpdateText(&ScriptsTP, texts, names, n);
182  // update client display
183  IDSetText(&ScriptsTP, nullptr);
184  return true;
185  }
186  if (strcmp(name, UrlTP.name) == 0)
187  {
188  UrlTP.s = IPS_OK;
189  IUUpdateText(&UrlTP, texts, names, n);
190  // update client display
191  IDSetText(&UrlTP, nullptr);
192  return true;
193  }
194  }
195 
196  return INDI::Weather::ISNewText(dev, name, texts, names, n);
197 }
198 
199 bool WeatherSafetyProxy::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
200 {
201  if (!strcmp(getDeviceName(), dev))
202  {
203  if (!strcmp(name, ScriptOrCurlSP.name))
204  {
205  LOG_DEBUG("WeatherSafetyProxy::ISNewSwitch");
206  IUUpdateSwitch(&ScriptOrCurlSP, states, names, n);
207  ScriptOrCurlSP.s = IPS_OK;
208  IDSetSwitch(&ScriptOrCurlSP, nullptr);
209  return true;
210  }
211  }
212 
213  return INDI::Weather::ISNewSwitch(dev, name, states, names, n);
214 }
215 
216 // Called by Weather::TimerHit every UpdatePeriodN[0].value seconds if we return IPS_OK or every 5 seconds otherwise
218 {
219  IPState ret = IPS_ALERT;
220  if (ScriptOrCurlS[WSP_USE_SCRIPT].s == ISS_ON)
221  {
222  ret = executeScript();
223  }
224  else
225  {
226  ret = executeCurl();
227  }
228  if (ret != IPS_OK)
229  {
230  if (Safety == WSP_SAFE)
231  {
232  SofterrorCount++;
233  LOGF_WARN("Soft error %d occurred during SAFE conditions, counting", SofterrorCount);
234  if (SofterrorCount > softErrorHysteresisN[WSP_SOFT_ERROR_MAX].value)
235  {
236  char Warning[] = "Max softerrors reached while Weather was SAFE";
237  LOG_WARN(Warning);
238  Safety = WSP_UNSAFE;
239  setParameterValue("WEATHER_SAFETY", WSP_UNSAFE);
240  IUSaveText(&reasonsT[0], Warning);
241  reasonsTP.s = IPS_OK;
242  IDSetText(&reasonsTP, nullptr);
243  SofterrorRecoveryMode = true;
244  ret = IPS_OK; // So that indiweather actually syncs the CriticalParameters we just set
245  }
246  }
247  else
248  {
249  LOG_WARN("Soft error occurred during UNSAFE conditions, ignore");
250  SofterrorCount = 0;
251  SofterrorRecoveryCount = 0;
252  }
253  }
254  else
255  {
256  SofterrorCount = 0;
257  }
258  return ret;
259 }
260 
261 IPState WeatherSafetyProxy::executeScript()
262 {
263  const char *cmd = ScriptsT[WSP_SCRIPT].text;
264 
265  if (access(cmd, F_OK | X_OK) == -1)
266  {
267  LOGF_ERROR("Cannot use script [%s], check its existence and permissions", cmd);
268  LastParseSuccess = false;
269  return IPS_ALERT;
270  }
271 
272  LOGF_DEBUG("Run script: %s", cmd);
273  FILE *handle = popen(cmd, "r");
274  if (handle == nullptr)
275  {
276  LOGF_ERROR("Failed to run script [%s]", strerror(errno));
277  LastParseSuccess = false;
278  return IPS_ALERT;
279  }
280  char buf[BUFSIZ];
281  size_t byte_count = fread(buf, 1, BUFSIZ - 1, handle);
282  pclose(handle);
283  buf[byte_count] = 0;
284  if (byte_count == 0)
285  {
286  LOGF_ERROR("Got no output from script [%s]", cmd);
287  LastParseSuccess = false;
288  return IPS_ALERT;
289  }
290  LOGF_DEBUG("Read %d bytes output [%s]", byte_count, buf);
291 
292  return parseSafetyJSON(buf);
293 }
294 
295 IPState WeatherSafetyProxy::executeCurl()
296 {
297  CURL *curl_handle;
298  CURLcode res;
299  std::string readBuffer;
300 
301  curl_handle = curl_easy_init();
302  if (curl_handle)
303  {
304  curl_easy_setopt(curl_handle, CURLOPT_URL, UrlT[WSP_URL].text);
305  curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WSP_WriteCallback);
306  curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &readBuffer);
307  curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0");
308  LOGF_DEBUG("Call curl %s", UrlT[WSP_URL].text);
309  res = curl_easy_perform(curl_handle);
310  if (res != CURLE_OK)
311  {
312  LOGF_ERROR("curl_easy_perform failed with [%s]", curl_easy_strerror(res));
313  return IPS_ALERT;
314  }
315  curl_easy_cleanup(curl_handle);
316  LOGF_DEBUG("Read %d bytes output [%s]", readBuffer.size(), readBuffer.c_str());
317  return parseSafetyJSON(readBuffer);
318  }
319  else
320  {
321  LOG_ERROR("curl_easy_init failed");
322  return IPS_ALERT;
323  }
324 }
325 
326 IPState WeatherSafetyProxy::parseSafetyJSON(const std::string &buffer)
327 {
328  json report = json::parse(buffer);
329  try
330  {
331  int NewSafety = 0;
332  report["roof_status"]["open_ok"].get_to(NewSafety);
333 
334  if (NewSafety != Safety)
335  {
336  if (NewSafety == WSP_UNSAFE)
337  {
338  LOG_WARN("Weather is UNSAFE");
339  }
340  else if (NewSafety == WSP_SAFE)
341  {
342  if (SofterrorRecoveryMode == true)
343  {
344  SofterrorRecoveryCount++;
345  if (SofterrorRecoveryCount > softErrorHysteresisN[WSP_SOFT_ERROR_RECOVERY].value)
346  {
347  LOG_INFO("Minimum soft recovery errors reached while Weather was SAFE");
348  SofterrorRecoveryCount = 0;
349  SofterrorRecoveryMode = false;
350  }
351  else
352  {
353  LOGF_INFO("Weather is SAFE but soft error recovery %d is still counting", SofterrorRecoveryCount);
354  NewSafety = WSP_UNSAFE;
355  }
356  }
357  else
358  {
359  LOG_INFO("Weather is SAFE");
360  }
361  }
362  Safety = NewSafety;
363  }
364  setParameterValue("WEATHER_SAFETY", NewSafety);
365 
366  // Optional reasons?
367  try
368  {
369  std::string reasons;
370  report["roof_status"]["reasons"].get_to(reasons);
371  if (SofterrorRecoveryMode == true)
372  {
373  char newReasons[MAXRBUF];
374  snprintf(newReasons, MAXRBUF, "SofterrorRecoveryMode, %s", reasons.c_str());
375  IUSaveText(&reasonsT[0], newReasons);
376  }
377  else
378  {
379  IUSaveText(&reasonsT[0], reasons.c_str());
380  }
381  reasonsTP.s = IPS_OK;
382  IDSetText(&reasonsTP, nullptr);
383  }
384  catch (json::exception &e) {}
385  }
386  catch (json::exception &e)
387  {
388  // output exception information
389  LOGF_ERROR("Error parsing weather report %s id: %d", e.what(), e.id);
390  return IPS_ALERT;
391  }
392 
393  return IPS_OK;
394 }
bool isConnected() const
Definition: basedevice.cpp:520
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.
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 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 updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
Definition: indiweather.cpp:85
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...
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
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
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 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 initProperties() override
Initilize properties initial state and value. The child class must implement this function.
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 IPState updateWeather() override
updateWeather Update weather conditions from device or service. The function should not change the st...
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
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.
bool Disconnect() override
Disconnect from device.
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 * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * OPTIONS_TAB
OPTIONS_TAB Where all the driver's options are located. Those may include auxiliary controls,...
int errno
ISState
Switch state.
Definition: indiapi.h:150
@ ISS_OFF
Definition: indiapi.h:151
@ ISS_ON
Definition: indiapi.h:152
@ IP_RW
Definition: indiapi.h:186
@ IP_RO
Definition: indiapi.h:184
IPState
Property state.
Definition: indiapi.h:160
@ IPS_ALERT
Definition: indiapi.h:164
@ IPS_IDLE
Definition: indiapi.h:161
@ IPS_OK
Definition: indiapi.h:162
@ ISR_1OFMANY
Definition: indiapi.h:173
void IUSaveConfigSwitch(FILE *fp, const ISwitchVectorProperty *svp)
Add a switch vector property value to the configuration file.
Definition: indidevapi.c:25
void IUFillNumberVector(INumberVectorProperty *nvp, INumber *np, int nnp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a number vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:272
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 IUSaveText(IText *tp, const char *newtext)
Function to reliably save new text in a IText.
Definition: indidevapi.c:36
void IUSaveConfigNumber(FILE *fp, const INumberVectorProperty *nvp)
Add a number vector property value to the configuration file.
Definition: indidevapi.c:15
void IUFillSwitch(ISwitch *sp, const char *name, const char *label, ISState s)
Assign attributes for a switch property. The switch's auxiliary elements will be set to NULL.
Definition: indidevapi.c:158
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 IUFillNumber(INumber *np, const char *name, const char *label, const char *format, double min, double max, double step, double value)
Assign attributes for a number property. The number's auxiliary elements will be set to NULL.
Definition: indidevapi.c:180
void IUSaveConfigText(FILE *fp, const ITextVectorProperty *tvp)
Add a text vector property value to the configuration file.
Definition: indidevapi.c:20
void IUFillSwitchVector(ISwitchVectorProperty *svp, ISwitch *sp, int nsp, const char *dev, const char *name, const char *label, const char *group, IPerm p, ISRule r, double timeout, IPState s)
Assign attributes for a switch vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:235
int IUUpdateSwitch(ISwitchVectorProperty *svp, ISState *states, char *names[], int n)
Update all switches in a switch vector property.
Definition: indidriver.c:1308
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
Definition: indidriver.c:1211
void IDSetSwitch(const ISwitchVectorProperty *svp, const char *fmt,...)
Definition: indidriver.c:1231
int IUUpdateText(ITextVectorProperty *tvp, char *texts[], char *names[], int n)
Update all text members in a text vector property.
Definition: indidriver.c:1396
int IUUpdateNumber(INumberVectorProperty *nvp, double values[], char *names[], int n)
Update all numbers in a number vector property.
Definition: indidriver.c:1362
void IDSetText(const ITextVectorProperty *tvp, const char *fmt,...)
Definition: indidriver.c:1191
#define LOGF_INFO(fmt,...)
Definition: indilogger.h:82
#define LOG_DEBUG(txt)
Definition: indilogger.h:75
#define LOGF_WARN(fmt,...)
Definition: indilogger.h:81
#define LOG_WARN(txt)
Definition: indilogger.h:73
#define LOGF_DEBUG(fmt,...)
Definition: indilogger.h:83
#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 LOG_INFO(txt)
Definition: indilogger.h:74
#define MAXRBUF
Definition: indiserver.cpp:102
std::vector< uint8_t > buffer
basic_json<> json
default specialization
Definition: json.h:3196
__u8 cmd[4]
Definition: pwc-ioctl.h:2
char name[MAXINDINAME]
Definition: indiapi.h:323
char name[MAXINDINAME]
Definition: indiapi.h:371
char name[MAXINDINAME]
Definition: indiapi.h:250
std::unique_ptr< WeatherSafetyProxy > weatherSafetyProxy(new WeatherSafetyProxy())
@ WSP_USE_SCRIPT
@ WSP_USE_COUNT
@ WSP_USE_CURL
@ WSP_UNSAFE
@ WSP_SAFE
@ WSP_URL_COUNT
@ WSP_SOFT_ERROR_MAX
@ WSP_SOFT_ERROR_RECOVERY
@ WSP_SCRIPT_COUNT
@ WSP_SCRIPT