Instrument Neutral Distributed Interface INDI  2.0.2
watchdog.cpp
Go to the documentation of this file.
1 /*******************************************************************************
2  Copyright(c) 2015 Jasem Mutlaq. All rights reserved.
3 
4  INDI Watchdog driver.
5 
6  The driver expects a heartbeat from the client every X minutes. If no heartbeat
7  is received, the driver executes the shutdown procedures.
8 
9  This program is free software; you can redistribute it and/or modify it
10  under the terms of the GNU General Public License as published by the Free
11  Software Foundation; either version 2 of the License, or (at your option)
12  any later version.
13 
14  This program is distributed in the hope that it will be useful, but WITHOUT
15  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17  more details.
18 
19  You should have received a copy of the GNU Library General Public License
20  along with this library; see the file COPYING.LIB. If not, write to
21  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22  Boston, MA 02110-1301, USA.
23 
24  The full GNU General Public License is included in this distribution in the
25  file called LICENSE.
26 *******************************************************************************/
27 
28 #include "watchdog.h"
29 #include "watchdogclient.h"
30 
31 #include <memory>
32 #include <cstring>
33 #include <unistd.h>
34 #include <sys/wait.h>
35 
36 // Naming the object after my love Juli which I lost in 2018. May she rest in peace.
37 // http://indilib.org/images/juli_tommy.jpg
38 std::unique_ptr<WatchDog> juli(new WatchDog());
39 
44 {
45  setVersion(0, 3);
47 
48  watchdogClient = new WatchDogClient();
49 
50  m_ShutdownStage = WATCHDOG_IDLE;
51 }
52 
57 {
58  delete (watchdogClient);
59 }
60 
65 {
66  return "WatchDog";
67 }
68 
73 {
74  if (ShutdownTriggerS[TRIGGER_CLIENT].s == ISS_ON && HeartBeatN[0].value > 0)
75  {
76  LOGF_INFO("Client Watchdog is enabled. Shutdown is triggered after %.f seconds of communication loss with the client.",
77  HeartBeatN[0].value);
78  if (m_WatchDogTimer > 0)
79  RemoveTimer(m_WatchDogTimer);
80  m_WatchDogTimer = SetTimer(HeartBeatN[0].value * 1000);
81  }
82 
83  if (ShutdownTriggerS[TRIGGER_WEATHER].s == ISS_ON)
84  {
85  if (WeatherThresholdN[0].value > 0)
86  LOGF_INFO("Weather Watchdog is enabled. Shutdown is triggered %.f seconds after Weather status enters DANGER zone.",
87  WeatherThresholdN[0].value);
88  else
89  LOG_INFO("Weather Watchdog is enabled. Shutdown is triggered when Weather status in DANGER zone.");
90  }
91 
92  IDSnoopDevice(ActiveDeviceT[ACTIVE_WEATHER].text, "WEATHER_STATUS");
93  IDSnoopDevice(ActiveDeviceT[ACTIVE_TELESCOPE].text, "TELESCOPE_PARK");
94  IDSnoopDevice(ActiveDeviceT[ACTIVE_DOME].text, "DOME_PARK");
95 
96  return true;
97 }
98 
103 {
104  if (m_WatchDogTimer > 0)
105  {
106  RemoveTimer(m_WatchDogTimer);
107  m_WatchDogTimer = -1;
108  }
109  if (m_WeatherAlertTimer > 0)
110  {
111  RemoveTimer(m_WeatherAlertTimer);
112  m_WeatherAlertTimer = -1;
113  }
114 
115  LOG_INFO("Watchdog is disabled.");
116  m_ShutdownStage = WATCHDOG_IDLE;
117 
118  return true;
119 }
120 
125 {
127 
128  // Heart Beat to client
129  IUFillNumber(&HeartBeatN[0], "WATCHDOG_HEARTBEAT_VALUE", "Threshold (s)", "%.f", 0, 3600, 60, 0);
130  IUFillNumberVector(&HeartBeatNP, HeartBeatN, 1, getDeviceName(), "WATCHDOG_HEARTBEAT", "Heart beat",
132 
133  // Weather Threshold
134  IUFillNumber(&WeatherThresholdN[0], "WATCHDOG_WEATHER_VALUE", "Threshold (s)", "%.f", 0, 3600, 60, 0);
135  IUFillNumberVector(&WeatherThresholdNP, WeatherThresholdN, 1, getDeviceName(), "WATCHDOG_WEATHER", "Weather",
137 
138  // INDI Server Settings
139  IUFillText(&SettingsT[INDISERVER_HOST], "INDISERVER_HOST", "indiserver host", "localhost");
140  IUFillText(&SettingsT[INDISERVER_PORT], "INDISERVER_PORT", "indiserver port", "7624");
141  IUFillText(&SettingsT[SHUTDOWN_SCRIPT], "SHUTDOWN_SCRIPT", "shutdown script", nullptr);
142  IUFillTextVector(&SettingsTP, SettingsT, 3, getDeviceName(), "WATCHDOG_SETTINGS", "Settings", MAIN_CONTROL_TAB,
143  IP_RW, 60, IPS_IDLE);
144 
145  // Shutdown procedure
146  IUFillSwitch(&ShutdownProcedureS[PARK_MOUNT], "PARK_MOUNT", "Park Mount", ISS_OFF);
147  IUFillSwitch(&ShutdownProcedureS[PARK_DOME], "PARK_DOME", "Park Dome", ISS_OFF);
148  IUFillSwitch(&ShutdownProcedureS[EXECUTE_SCRIPT], "EXECUTE_SCRIPT", "Execute Script", ISS_OFF);
149  IUFillSwitchVector(&ShutdownProcedureSP, ShutdownProcedureS, 3, getDeviceName(), "WATCHDOG_SHUTDOWN", "Shutdown",
151 
152  // Shutdown Trigger
153  IUFillSwitch(&ShutdownTriggerS[TRIGGER_CLIENT], "TRIGGER_CLIENT", "Client", ISS_OFF);
154  IUFillSwitch(&ShutdownTriggerS[TRIGGER_WEATHER], "TRIGGER_WEATHER", "Weather", ISS_OFF);
155  IUFillSwitchVector(&ShutdownTriggerSP, ShutdownTriggerS, 2, getDeviceName(), "WATCHDOG_Trigger", "Trigger",
157 
158  // Mount Policy
159  IUFillSwitch(&MountPolicyS[MOUNT_IGNORED], "MOUNT_IGNORED", "Mount ignored", ISS_ON);
160  IUFillSwitch(&MountPolicyS[MOUNT_LOCKS], "MOUNT_LOCKS", "Mount locks", ISS_OFF);
161  IUFillSwitchVector(&MountPolicySP, MountPolicyS, 2, getDeviceName(), "WATCHDOG_MOUNT_POLICY", "Mount Policy",
163 
164  // Active Devices
165  IUFillText(&ActiveDeviceT[ACTIVE_TELESCOPE], "ACTIVE_TELESCOPE", "Telescope", "Telescope Simulator");
166  IUFillText(&ActiveDeviceT[ACTIVE_DOME], "ACTIVE_DOME", "Dome", "Dome Simulator");
167  IUFillText(&ActiveDeviceT[ACTIVE_WEATHER], "ACTIVE_WEATHER", "Weather", "Weather Simulator");
168  IUFillTextVector(&ActiveDeviceTP, ActiveDeviceT, 3, getDeviceName(), "ACTIVE_DEVICES", "Active devices",
169  OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
170 
171  addDebugControl();
172 
173  return true;
174 }
175 
179 void WatchDog::ISGetProperties(const char *dev)
180 {
182 
183  defineProperty(&HeartBeatNP);
184  defineProperty(&WeatherThresholdNP);
185  defineProperty(&SettingsTP);
186  defineProperty(&ShutdownTriggerSP);
187  defineProperty(&ShutdownProcedureSP);
188  defineProperty(&MountPolicySP);
189  defineProperty(&ActiveDeviceTP);
190 }
191 
195 bool WatchDog::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
196 {
197  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
198  {
199  // Update Settings
200  if (!strcmp(SettingsTP.name, name))
201  {
202  IUUpdateText(&SettingsTP, texts, names, n);
203  SettingsTP.s = IPS_OK;
204  IDSetText(&SettingsTP, nullptr);
205 
206  try
207  {
208  m_INDIServerPort = std::stoi(SettingsT[INDISERVER_PORT].text);
209  }
210  catch(...)
211  {
212  SettingsTP.s = IPS_ALERT;
213  LOG_ERROR("Failed to parse numbers.");
214  }
215 
216  IDSetText(&SettingsTP, nullptr);
217  return true;
218  }
219 
220  // Snoop Active Devices.
221  if (!strcmp(ActiveDeviceTP.name, name))
222  {
223  if (watchdogClient->isBusy())
224  {
225  ActiveDeviceTP.s = IPS_ALERT;
226  IDSetText(&ActiveDeviceTP, nullptr);
227  LOG_ERROR("Cannot change devices names while shutdown is in progress...");
228  return true;
229  }
230 
231  IDSnoopDevice(ActiveDeviceT[ACTIVE_WEATHER].text, "WEATHER_STATUS");
232  IDSnoopDevice(ActiveDeviceT[ACTIVE_TELESCOPE].text, "TELESCOPE_PARK");
233  IDSnoopDevice(ActiveDeviceT[ACTIVE_DOME].text, "DOME_PARK");
234 
235  IUUpdateText(&ActiveDeviceTP, texts, names, n);
236  ActiveDeviceTP.s = IPS_OK;
237  IDSetText(&ActiveDeviceTP, nullptr);
238  return true;
239  }
240  }
241 
242  return INDI::DefaultDevice::ISNewText(dev, name, texts, names, n);
243 }
244 
248 bool WatchDog::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
249 {
250  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
251  {
252  // Weather threshold
253  if (!strcmp(WeatherThresholdNP.name, name))
254  {
255  IUUpdateNumber(&WeatherThresholdNP, values, names, n);
256  WeatherThresholdNP.s = IPS_OK;
257  IDSetNumber(&WeatherThresholdNP, nullptr);
258  return true;
259  }
260  // Heart Beat
261  // Client must set this property to indicate it is alive.
262  // If heat beat not received from client then shutdown procedure begins if
263  // the client trigger is selected.
264  else if (!strcmp(HeartBeatNP.name, name))
265  {
266  double prevHeartBeat = HeartBeatN[0].value;
267 
268  if (watchdogClient->isBusy())
269  {
270  HeartBeatNP.s = IPS_ALERT;
271  IDSetNumber(&HeartBeatNP, nullptr);
272  LOG_ERROR("Cannot change heart beat while shutdown is in progress...");
273  return true;
274  }
275 
276  IUUpdateNumber(&HeartBeatNP, values, names, n);
277  HeartBeatNP.s = IPS_OK;
278 
279  // If trigger is not set, don't do anything else
280  if (ShutdownTriggerS[TRIGGER_CLIENT].s == ISS_OFF)
281  {
282  if (m_WatchDogTimer > 0)
283  {
284  RemoveTimer(m_WatchDogTimer);
285  m_WatchDogTimer = -1;
286  }
287  IDSetNumber(&HeartBeatNP, nullptr);
288  return true;
289  }
290 
291  if (HeartBeatN[0].value == 0)
292  LOG_INFO("Client Watchdog is disabled.");
293  else
294  {
295  if (prevHeartBeat != HeartBeatN[0].value)
296  LOGF_INFO("Client Watchdog is enabled. Shutdown is triggered after %.f seconds of communication loss with the client.",
297  HeartBeatN[0].value);
298 
299  LOG_DEBUG("Received heart beat from client.");
300 
301  if (m_WatchDogTimer > 0)
302  {
303  RemoveTimer(m_WatchDogTimer);
304  m_WatchDogTimer = -1;
305  }
306 
307  m_WatchDogTimer = SetTimer(HeartBeatN[0].value * 1000);
308  }
309  IDSetNumber(&HeartBeatNP, nullptr);
310 
311  return true;
312  }
313  }
314 
315  return DefaultDevice::ISNewNumber(dev, name, values, names, n);
316 }
317 
321 bool WatchDog::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
322 {
323  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
324  {
325  // Shutdown procedure setting
326  if (!strcmp(ShutdownProcedureSP.name, name))
327  {
328  IUUpdateSwitch(&ShutdownProcedureSP, states, names, n);
329 
330  if (ShutdownProcedureS[EXECUTE_SCRIPT].s == ISS_ON &&
331  (SettingsT[EXECUTE_SCRIPT].text == nullptr || SettingsT[EXECUTE_SCRIPT].text[0] == '\0'))
332  {
333  LOG_ERROR("Error: shutdown script file is not set.");
334  ShutdownProcedureSP.s = IPS_ALERT;
335  ShutdownProcedureS[EXECUTE_SCRIPT].s = ISS_OFF;
336  }
337  else
338  ShutdownProcedureSP.s = IPS_OK;
339  IDSetSwitch(&ShutdownProcedureSP, nullptr);
340  return true;
341  }
342  // Mount Lock Policy
343  else if (!strcmp(MountPolicySP.name, name))
344  {
345  IUUpdateSwitch(&MountPolicySP, states, names, n);
346  MountPolicySP.s = IPS_OK;
347 
348  if (MountPolicyS[MOUNT_IGNORED].s == ISS_ON)
349  LOG_INFO("Mount is ignored. Dome can start parking without waiting for mount to complete parking.");
350  else
351  LOG_INFO("Mount locks. Dome must wait for mount to park before it can start the parking procedure.");
352  IDSetSwitch(&MountPolicySP, nullptr);
353  return true;
354  }
355  // Shutdown Trigger handling
356  else if (!strcmp(ShutdownTriggerSP.name, name))
357  {
358  std::vector<ISState> oldStates(ShutdownTriggerSP.nsp, ISS_OFF);
359  std::vector<ISState> newStates(ShutdownTriggerSP.nsp, ISS_OFF);
360  for (int i = 0; i < ShutdownTriggerSP.nsp; i++)
361  oldStates[i] = ShutdownTriggerS[i].s;
362  IUUpdateSwitch(&ShutdownTriggerSP, states, names, n);
363  for (int i = 0; i < ShutdownTriggerSP.nsp; i++)
364  newStates[i] = ShutdownTriggerS[i].s;
365 
366 
367  // Check for client changes
368  if (oldStates[TRIGGER_CLIENT] != newStates[TRIGGER_CLIENT])
369  {
370  // User disabled client trigger
371  if (newStates[TRIGGER_CLIENT] == ISS_OFF)
372  {
373  LOG_INFO("Disabling client watchdog. Lost communication with client shall not trigger the shutdown procedure.");
374  if (m_WatchDogTimer > 0)
375  {
376  RemoveTimer(m_WatchDogTimer);
377  m_WatchDogTimer = -1;
378  }
379  }
380  // User enabled client trigger
381  else
382  {
383  // Check first that we have a valid heart beat
384  if (HeartBeatN[0].value == 0)
385  {
386  LOG_ERROR("Heart beat timeout should be set first.");
387  ShutdownTriggerSP.s = IPS_ALERT;
388  for (int i = 0; i < ShutdownTriggerSP.nsp; i++)
389  ShutdownTriggerS[i].s = oldStates[i];
390  IDSetSwitch(&ShutdownTriggerSP, nullptr);
391  return true;
392  }
393 
394  LOGF_INFO("Client Watchdog is enabled. Shutdown is triggered after %.f seconds of communication loss with the client.",
395  HeartBeatN[0].value);
396  if (m_WatchDogTimer > 0)
397  RemoveTimer(m_WatchDogTimer);
398  m_WatchDogTimer = SetTimer(HeartBeatN[0].value * 1000);
399  }
400  }
401 
402  // Check for weather changes
403  if (oldStates[TRIGGER_WEATHER] != newStates[TRIGGER_WEATHER])
404  {
405  // User disabled weather trigger
406  if (newStates[TRIGGER_WEATHER] == ISS_OFF)
407  {
408  // If we have an active timer, remove it.
409  if (m_WeatherAlertTimer > 0)
410  {
411  RemoveTimer(m_WeatherAlertTimer);
412  m_WeatherAlertTimer = -1;
413  }
414 
415  LOG_INFO("Weather Watchdog is disabled.");
416  }
417  else
418  {
419  IDSnoopDevice(ActiveDeviceT[ACTIVE_WEATHER].text, "WEATHER_STATUS");
420  LOG_INFO("Weather Watchdog is enabled.");
421  }
422 
423  }
424 
425  ShutdownTriggerSP.s = IPS_OK;
426  IDSetSwitch(&ShutdownTriggerSP, nullptr);
427  return true;
428  }
429  }
430 
431  return DefaultDevice::ISNewSwitch(dev, name, states, names, n);
432 }
433 
438 {
439  const char * propName = findXMLAttValu(root, "name");
440 
441  // Weather Status
442  if (!strcmp("WEATHER_STATUS", propName))
443  {
444  IPState newWeatherState;
445  crackIPState(findXMLAttValu(root, "state"), &newWeatherState);
446 
447  // In case timer is active, and weather status is now OK
448  // Let's disable the timer
449  if (m_WeatherState == IPS_ALERT && newWeatherState != IPS_ALERT)
450  {
451  LOG_INFO("Weather status is no longer in DANGER zone.");
452  if (m_WeatherAlertTimer > 0)
453  {
454  LOG_INFO("Shutdown procedure cancelled.");
455  RemoveTimer(m_WeatherAlertTimer);
456  m_WeatherAlertTimer = -1;
457  }
458  }
459 
460  // In case weather shutdown is active and;
461  // weather timer is off and;
462  // previous weather status is not alert and;
463  // current weather status is alert, then
464  // we start the weather timer which on timeout would case the shutdown procedure to commence.
465  if (m_WeatherState != IPS_ALERT && newWeatherState == IPS_ALERT)
466  {
467  LOG_WARN("Weather is in DANGER zone.");
468  if (ShutdownTriggerS[TRIGGER_WEATHER].s == ISS_ON && m_WeatherAlertTimer == -1)
469  {
470  if (WeatherThresholdN[0].value > 0)
471  LOGF_INFO("Shutdown procedure shall commence in %.f seconds unless weather status improves.", WeatherThresholdN[0].value);
472  m_WeatherAlertTimer = SetTimer(WeatherThresholdN[0].value * 1000);
473  }
474  }
475 
476  m_WeatherState = newWeatherState;
477  }
478  // Check Telescope Park status
479  else if (!strcmp("TELESCOPE_PARK", propName))
480  {
481  if (!strcmp(findXMLAttValu(root, "state"), "Ok"))
482  {
483  XMLEle * ep = nullptr;
484  bool parked = false;
485  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
486  {
487  const char * elemName = findXMLAttValu(ep, "name");
488 
489  if ((!strcmp(elemName, "PARK") && !strcmp(pcdataXMLEle(ep), "On")))
490  parked = true;
491  else if ((!strcmp(elemName, "UNPARK") && !strcmp(pcdataXMLEle(ep), "On")))
492  parked = false;
493  }
494  if (parked != m_IsMountParked)
495  {
496  LOGF_INFO("Mount is %s", parked ? "Parked" : "Unparked");
497  m_IsMountParked = parked;
498  // In case mount was UNPARKED while weather status is still ALERT
499  // And weather shutdown trigger was active and mount parking was selected
500  // then we force the mount to park again.
501  if (parked == false &&
502  ShutdownTriggerS[TRIGGER_WEATHER].s == ISS_ON &&
503  m_WeatherState == IPS_ALERT &&
504  ShutdownProcedureS[PARK_MOUNT].s == ISS_ON)
505  {
506  LOG_WARN("Mount unparked while weather alert is active! Parking mount...");
507  watchdogClient->parkMount();
508  }
509  }
510  return true;
511  }
512  }
513  // Check Telescope Park status
514  else if (!strcmp("DOME_PARK", propName))
515  {
516  if (!strcmp(findXMLAttValu(root, "state"), "Ok") || !strcmp(findXMLAttValu(root, "state"), "Busy"))
517  {
518  XMLEle * ep = nullptr;
519  bool parked = false;
520  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
521  {
522  const char * elemName = findXMLAttValu(ep, "name");
523 
524  if ((!strcmp(elemName, "PARK") && !strcmp(pcdataXMLEle(ep), "On")))
525  parked = true;
526  else if ((!strcmp(elemName, "UNPARK") && !strcmp(pcdataXMLEle(ep), "On")))
527  parked = false;
528  }
529  if (parked != m_IsDomeParked)
530  {
531  LOGF_INFO("Dome is %s", parked ? "Parked" : "Unparked");
532  m_IsDomeParked = parked;
533  // In case mount was UNPARKED while weather status is still ALERT
534  // And weather shutdown trigger was active and mount parking was selected
535  // then we force the mount to park again.
536  if (parked == false &&
537  ShutdownTriggerS[TRIGGER_WEATHER].s == ISS_ON &&
538  m_WeatherState == IPS_ALERT &&
539  ShutdownProcedureS[PARK_DOME].s == ISS_ON)
540  {
541  LOG_WARN("Dome unparked while weather alert is active! Parking dome...");
542  watchdogClient->parkDome();
543  }
544  }
545  return true;
546  }
547  }
548 
549  return DefaultDevice::ISSnoopDevice(root);
550 }
551 
556 {
558 
559  IUSaveConfigNumber(fp, &HeartBeatNP);
560  IUSaveConfigNumber(fp, &WeatherThresholdNP);
561  IUSaveConfigText(fp, &SettingsTP);
562  IUSaveConfigText(fp, &ActiveDeviceTP);
563  IUSaveConfigSwitch(fp, &MountPolicySP);
564  IUSaveConfigSwitch(fp, &ShutdownTriggerSP);
565  IUSaveConfigSwitch(fp, &ShutdownProcedureSP);
566 
567  return true;
568 }
569 
574 {
575  // Timer is up, we need to start shutdown procedure
576 
577  // If nothing to do, then return
578  if (ShutdownProcedureS[PARK_DOME].s == ISS_OFF && ShutdownProcedureS[PARK_MOUNT].s == ISS_OFF &&
579  ShutdownProcedureS[EXECUTE_SCRIPT].s == ISS_OFF)
580  return;
581 
582  switch (m_ShutdownStage)
583  {
584  // Connect to server
585  case WATCHDOG_IDLE:
586 
587  ShutdownProcedureSP.s = IPS_BUSY;
588  IDSetSwitch(&ShutdownProcedureSP, nullptr);
589 
590  if (m_WeatherState == IPS_ALERT)
591  LOG_WARN("Warning! Weather status in DANGER zone, executing shutdown procedure...");
592  else
593  LOG_WARN("Warning! Heartbeat threshold timed out, executing shutdown procedure...");
594 
595  // No need to start client if only we need to execute the script
596  if (ShutdownProcedureS[PARK_MOUNT].s == ISS_OFF && ShutdownProcedureS[PARK_DOME].s == ISS_OFF &&
597  ShutdownProcedureS[EXECUTE_SCRIPT].s == ISS_ON)
598  {
599  executeScript();
600  break;
601  }
602 
603  // Watch mount if requied
604  if (ShutdownProcedureS[PARK_MOUNT].s == ISS_ON)
605  watchdogClient->setMount(ActiveDeviceT[ACTIVE_TELESCOPE].text);
606  // Watch dome
607  if (ShutdownProcedureS[PARK_DOME].s == ISS_ON)
608  watchdogClient->setDome(ActiveDeviceT[ACTIVE_DOME].text);
609 
610  // Set indiserver host and port
611  watchdogClient->setServer(SettingsT[INDISERVER_HOST].text, m_INDIServerPort);
612 
613  LOG_DEBUG("Connecting to INDI server...");
614 
615  watchdogClient->connectServer();
616 
617  m_ShutdownStage = WATCHDOG_CLIENT_STARTED;
618 
619  break;
620 
622  // Check if client is ready
623  if (watchdogClient->isConnected())
624  {
625  LOGF_DEBUG("Connected to INDI server %s @ %s", SettingsT[0].text,
626  SettingsT[1].text);
627 
628  if (ShutdownProcedureS[PARK_MOUNT].s == ISS_ON)
629  parkMount();
630  else if (ShutdownProcedureS[PARK_DOME].s == ISS_ON)
631  parkDome();
632  else if (ShutdownProcedureS[EXECUTE_SCRIPT].s == ISS_ON)
633  executeScript();
634  }
635  else
636  LOG_DEBUG("Waiting for INDI server connection...");
637  break;
638 
640  {
641  // check if mount is parked
642  IPState mountState = watchdogClient->getMountParkState();
643 
644  if (mountState == IPS_OK || mountState == IPS_IDLE)
645  {
646  LOG_INFO("Mount parked.");
647 
648  if (ShutdownProcedureS[PARK_DOME].s == ISS_ON)
649  parkDome();
650  else if (ShutdownProcedureS[EXECUTE_SCRIPT].s == ISS_ON)
651  executeScript();
652  else
653  m_ShutdownStage = WATCHDOG_COMPLETE;
654  }
655  }
656  break;
657 
659  {
660  // check if dome is parked
661  IPState domeState = watchdogClient->getDomeParkState();
662 
663  if (domeState == IPS_OK || domeState == IPS_IDLE)
664  {
665  LOG_INFO("Dome parked.");
666 
667  if (ShutdownProcedureS[EXECUTE_SCRIPT].s == ISS_ON)
668  executeScript();
669  else
670  m_ShutdownStage = WATCHDOG_COMPLETE;
671  }
672  }
673  break;
674 
675  case WATCHDOG_COMPLETE:
676  LOG_INFO("Shutdown procedure complete.");
677  ShutdownProcedureSP.s = IPS_OK;
678  IDSetSwitch(&ShutdownProcedureSP, nullptr);
679  // If watch dog client still connected, keep it as such
680  if (watchdogClient->isConnected())
681  m_ShutdownStage = WATCHDOG_CLIENT_STARTED;
682  // If server is shutdown, then we reset to IDLE
683  else
684  m_ShutdownStage = WATCHDOG_IDLE;
685  return;
686 
687  case WATCHDOG_ERROR:
688  ShutdownProcedureSP.s = IPS_ALERT;
689  IDSetSwitch(&ShutdownProcedureSP, nullptr);
690  return;
691  }
692 
694 }
695 
696 void WatchDog::parkDome()
697 {
698  if (watchdogClient->parkDome() == false)
699  {
700  LOG_ERROR("Error: Unable to park dome! Shutdown procedure terminated.");
701  m_ShutdownStage = WATCHDOG_ERROR;
702  return;
703  }
704 
705  LOG_INFO("Parking dome...");
706  m_ShutdownStage = WATCHDOG_DOME_PARKED;
707 }
708 
709 void WatchDog::parkMount()
710 {
711  if (watchdogClient->parkMount() == false)
712  {
713  LOG_ERROR("Error: Unable to park mount! Shutdown procedure terminated.");
714  m_ShutdownStage = WATCHDOG_ERROR;
715  return;
716  }
717 
718  LOG_INFO("Parking mount...");
719 
720  // If mount is set to ignored, and we have active dome shutdown then we start
721  // parking the dome immediately.
722  if (MountPolicyS[MOUNT_IGNORED].s == ISS_ON && ShutdownProcedureS[PARK_DOME].s == ISS_ON)
723  parkDome();
724  else
725  m_ShutdownStage = WATCHDOG_MOUNT_PARKED;
726 }
727 
728 void WatchDog::executeScript()
729 {
730  // child
731  if (fork() == 0)
732  {
733  int rc = execlp(SettingsT[EXECUTE_SCRIPT].text, SettingsT[EXECUTE_SCRIPT].text, nullptr);
734 
735  if (rc)
736  exit(rc);
737  }
738  // parent
739  else
740  {
741  int statval;
742  LOGF_INFO("Executing script %s...", SettingsT[EXECUTE_SCRIPT].text);
743  LOGF_INFO("Waiting for script with PID %d to complete...", getpid());
744  wait(&statval);
745  if (WIFEXITED(statval))
746  {
747  int exit_code = WEXITSTATUS(statval);
748  LOGF_INFO("Script complete with exit code %d", exit_code);
749 
750  if (exit_code == 0)
751  m_ShutdownStage = WATCHDOG_COMPLETE;
752  else
753  {
754  LOGF_ERROR("Error: script %s failed. Shutdown procedure terminated.",
755  SettingsT[EXECUTE_SCRIPT].text);
756  m_ShutdownStage = WATCHDOG_ERROR;
757  return;
758  }
759  }
760  else
761  {
762  LOGF_ERROR(
763  "Error: script %s did not terminate with exit. Shutdown procedure terminated.",
764  SettingsT[EXECUTE_SCRIPT].text);
765  m_ShutdownStage = WATCHDOG_ERROR;
766  return;
767  }
768  }
769 }
void setServer(const char *hostname, unsigned int port)
Set the server host name and port.
bool connectServer() override
Connect to INDI server.
Definition: baseclient.cpp:308
const char * getDeviceName() const
Definition: basedevice.cpp:821
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)
virtual bool saveConfigItems(FILE *fp)
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
uint32_t getCurrentPollingPeriod() const
getCurrentPollingPeriod Return the current polling period.
virtual bool initProperties()
Initilize properties initial state and value. The child class must implement this function.
void setDriverInterface(uint16_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
void RemoveTimer(int id)
Remove timer added with SetTimer.
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
void addDebugControl()
Add Debug control to the driver.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
Process the client newSwitch command.
IPState getDomeParkState()
IPState getMountParkState()
void setMount(const std::string &value)
void setDome(const std::string &value)
@ EXECUTE_SCRIPT
Definition: watchdog.h:46
@ PARK_DOME
Definition: watchdog.h:46
@ PARK_MOUNT
Definition: watchdog.h:46
virtual ~WatchDog()
Definition: watchdog.cpp:56
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
Definition: watchdog.cpp:573
virtual bool Disconnect() override
Disconnect from device.
Definition: watchdog.cpp:102
virtual bool ISSnoopDevice(XMLEle *root) override
Process a snoop event from INDI server. This function is called when a snooped property is updated in...
Definition: watchdog.cpp:437
virtual bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
Definition: watchdog.cpp:124
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: watchdog.cpp:179
virtual const char * getDefaultName() override
Definition: watchdog.cpp:64
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
Definition: watchdog.cpp:321
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
Definition: watchdog.cpp:248
WatchDog()
Definition: watchdog.cpp:43
virtual bool Connect() override
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
Definition: watchdog.cpp:72
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
Definition: watchdog.cpp:195
@ WATCHDOG_ERROR
Definition: watchdog.h:44
@ WATCHDOG_IDLE
Definition: watchdog.h:39
@ WATCHDOG_COMPLETE
Definition: watchdog.h:43
@ WATCHDOG_CLIENT_STARTED
Definition: watchdog.h:40
@ WATCHDOG_DOME_PARKED
Definition: watchdog.h:42
@ WATCHDOG_MOUNT_PARKED
Definition: watchdog.h:41
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
Definition: watchdog.cpp:555
@ MOUNT_IGNORED
Definition: watchdog.h:47
@ MOUNT_LOCKS
Definition: watchdog.h:47
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
void ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Update the value of an existing switch vector property.
void ISGetProperties(const char *dev)
Get Device Properties.
void ISSnoopDevice(XMLEle *root)
Function defined by Drivers that is called when another Driver it is snooping (by having previously c...
void ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
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:150
@ ISS_OFF
Definition: indiapi.h:151
@ ISS_ON
Definition: indiapi.h:152
@ 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
@ ISR_1OFMANY
Definition: indiapi.h:173
@ ISR_NOFMANY
Definition: indiapi.h:175
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 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
int crackIPState(const char *str, IPState *ip)
Extract property state (Idle, OK, Busy, Alert) from the supplied string.
Definition: indidevapi.c:576
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
void IDSnoopDevice(const char *snooped_device, const char *snooped_property)
Function a Driver calls to snoop on another Device. Snooped messages will then arrive via ISSnoopDevi...
Definition: indidriver.c:143
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 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
const char * findXMLAttValu(XMLEle *ep, const char *name)
Find an XML element's attribute value.
Definition: lilxml.cpp:644
char * pcdataXMLEle(XMLEle *ep)
Return the pcdata of an XML element.
Definition: lilxml.cpp:606
XMLEle * nextXMLEle(XMLEle *ep, int init)
Iterate an XML element for a list of nesetd XML elements.
Definition: lilxml.cpp:555
char name[MAXINDINAME]
Definition: indiapi.h:323
char name[MAXINDINAME]
Definition: indiapi.h:371
char name[MAXINDINAME]
Definition: indiapi.h:250
std::unique_ptr< WatchDog > juli(new WatchDog())