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
40 ////////////////////////////////////////////////////////////////////////////////////
41 ///
42 ////////////////////////////////////////////////////////////////////////////////////
WatchDog()43 WatchDog::WatchDog()
44 {
45 setVersion(0, 3);
46 setDriverInterface(AUX_INTERFACE);
47
48 watchdogClient = new WatchDogClient();
49
50 m_ShutdownStage = WATCHDOG_IDLE;
51 }
52
53 ////////////////////////////////////////////////////////////////////////////////////
54 ///
55 ////////////////////////////////////////////////////////////////////////////////////
~WatchDog()56 WatchDog::~WatchDog()
57 {
58 delete (watchdogClient);
59 }
60
61 ////////////////////////////////////////////////////////////////////////////////////
62 ///
63 ////////////////////////////////////////////////////////////////////////////////////
getDefaultName()64 const char *WatchDog::getDefaultName()
65 {
66 return "WatchDog";
67 }
68
69 ////////////////////////////////////////////////////////////////////////////////////
70 ///
71 ////////////////////////////////////////////////////////////////////////////////////
Connect()72 bool WatchDog::Connect()
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 // Trigger Snoop
91 IDSnoopDevice(ActiveDeviceT[ACTIVE_WEATHER].text, "WEATHER_STATUS");
92 }
93
94 IDSnoopDevice(ActiveDeviceT[ACTIVE_TELESCOPE].text, "TELESCOPE_PARK");
95 IDSnoopDevice(ActiveDeviceT[ACTIVE_DOME].text, "DOME_PARK");
96
97 return true;
98 }
99
100 ////////////////////////////////////////////////////////////////////////////////////
101 ///
102 ////////////////////////////////////////////////////////////////////////////////////
Disconnect()103 bool WatchDog::Disconnect()
104 {
105 if (m_WatchDogTimer > 0)
106 {
107 RemoveTimer(m_WatchDogTimer);
108 m_WatchDogTimer = -1;
109 }
110 if (m_WeatherAlertTimer > 0)
111 {
112 RemoveTimer(m_WeatherAlertTimer);
113 m_WeatherAlertTimer = -1;
114 }
115
116 LOG_INFO("Watchdog is disabled.");
117 m_ShutdownStage = WATCHDOG_IDLE;
118
119 return true;
120 }
121
122 ////////////////////////////////////////////////////////////////////////////////////
123 ///
124 ////////////////////////////////////////////////////////////////////////////////////
initProperties()125 bool WatchDog::initProperties()
126 {
127 INDI::DefaultDevice::initProperties();
128
129 // Heart Beat to client
130 IUFillNumber(&HeartBeatN[0], "WATCHDOG_HEARTBEAT_VALUE", "Threshold (s)", "%.f", 0, 3600, 60, 0);
131 IUFillNumberVector(&HeartBeatNP, HeartBeatN, 1, getDeviceName(), "WATCHDOG_HEARTBEAT", "Heart beat",
132 MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE);
133
134 // Weather Threshold
135 IUFillNumber(&WeatherThresholdN[0], "WATCHDOG_WEATHER_VALUE", "Threshold (s)", "%.f", 0, 3600, 60, 0);
136 IUFillNumberVector(&WeatherThresholdNP, WeatherThresholdN, 1, getDeviceName(), "WATCHDOG_WEATHER", "Weather",
137 MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE);
138
139 // INDI Server Settings
140 IUFillText(&SettingsT[INDISERVER_HOST], "INDISERVER_HOST", "indiserver host", "localhost");
141 IUFillText(&SettingsT[INDISERVER_PORT], "INDISERVER_PORT", "indiserver port", "7624");
142 IUFillText(&SettingsT[SHUTDOWN_SCRIPT], "SHUTDOWN_SCRIPT", "shutdown script", nullptr);
143 IUFillTextVector(&SettingsTP, SettingsT, 3, getDeviceName(), "WATCHDOG_SETTINGS", "Settings", MAIN_CONTROL_TAB,
144 IP_RW, 60, IPS_IDLE);
145
146 // Shutdown procedure
147 IUFillSwitch(&ShutdownProcedureS[PARK_MOUNT], "PARK_MOUNT", "Park Mount", ISS_OFF);
148 IUFillSwitch(&ShutdownProcedureS[PARK_DOME], "PARK_DOME", "Park Dome", ISS_OFF);
149 IUFillSwitch(&ShutdownProcedureS[EXECUTE_SCRIPT], "EXECUTE_SCRIPT", "Execute Script", ISS_OFF);
150 IUFillSwitchVector(&ShutdownProcedureSP, ShutdownProcedureS, 3, getDeviceName(), "WATCHDOG_SHUTDOWN", "Shutdown",
151 MAIN_CONTROL_TAB, IP_RW, ISR_NOFMANY, 60, IPS_IDLE);
152
153 // Shutdown Trigger
154 IUFillSwitch(&ShutdownTriggerS[TRIGGER_CLIENT], "TRIGGER_CLIENT", "Client", ISS_OFF);
155 IUFillSwitch(&ShutdownTriggerS[TRIGGER_WEATHER], "TRIGGER_WEATHER", "Weather", ISS_OFF);
156 IUFillSwitchVector(&ShutdownTriggerSP, ShutdownTriggerS, 2, getDeviceName(), "WATCHDOG_Trigger", "Trigger",
157 MAIN_CONTROL_TAB, IP_RW, ISR_NOFMANY, 60, IPS_IDLE);
158
159 // Mount Policy
160 IUFillSwitch(&MountPolicyS[MOUNT_IGNORED], "MOUNT_IGNORED", "Mount ignored", ISS_ON);
161 IUFillSwitch(&MountPolicyS[MOUNT_LOCKS], "MOUNT_LOCKS", "Mount locks", ISS_OFF);
162 IUFillSwitchVector(&MountPolicySP, MountPolicyS, 2, getDeviceName(), "WATCHDOG_MOUNT_POLICY", "Mount Policy",
163 OPTIONS_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
164
165 // Active Devices
166 IUFillText(&ActiveDeviceT[ACTIVE_TELESCOPE], "ACTIVE_TELESCOPE", "Telescope", "Telescope Simulator");
167 IUFillText(&ActiveDeviceT[ACTIVE_DOME], "ACTIVE_DOME", "Dome", "Dome Simulator");
168 IUFillText(&ActiveDeviceT[ACTIVE_WEATHER], "ACTIVE_WEATHER", "Weather", "Weather Simulator");
169 IUFillTextVector(&ActiveDeviceTP, ActiveDeviceT, 3, getDeviceName(), "ACTIVE_DEVICES", "Active devices",
170 OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
171
172 addDebugControl();
173
174 return true;
175 }
176
177 ////////////////////////////////////////////////////////////////////////////////////
178 ///
179 ////////////////////////////////////////////////////////////////////////////////////
ISGetProperties(const char * dev)180 void WatchDog::ISGetProperties(const char *dev)
181 {
182 DefaultDevice::ISGetProperties(dev);
183
184 defineProperty(&HeartBeatNP);
185 defineProperty(&WeatherThresholdNP);
186 defineProperty(&SettingsTP);
187 defineProperty(&ShutdownTriggerSP);
188 defineProperty(&ShutdownProcedureSP);
189 defineProperty(&MountPolicySP);
190 defineProperty(&ActiveDeviceTP);
191 }
192
193 ////////////////////////////////////////////////////////////////////////////////////
194 ///
195 ////////////////////////////////////////////////////////////////////////////////////
ISNewText(const char * dev,const char * name,char * texts[],char * names[],int n)196 bool WatchDog::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
197 {
198 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
199 {
200 // Update Settings
201 if (!strcmp(SettingsTP.name, name))
202 {
203 IUUpdateText(&SettingsTP, texts, names, n);
204 SettingsTP.s = IPS_OK;
205 IDSetText(&SettingsTP, nullptr);
206
207 try
208 {
209 m_INDIServerPort = std::stoi(SettingsT[INDISERVER_PORT].text);
210 }
211 catch(...)
212 {
213 SettingsTP.s = IPS_ALERT;
214 LOG_ERROR("Failed to parse numbers.");
215 }
216
217 IDSetText(&SettingsTP, nullptr);
218 return true;
219 }
220
221 // Snoop Active Devices.
222 if (!strcmp(ActiveDeviceTP.name, name))
223 {
224 if (watchdogClient->isBusy())
225 {
226 ActiveDeviceTP.s = IPS_ALERT;
227 IDSetText(&ActiveDeviceTP, nullptr);
228 LOG_ERROR("Cannot change devices names while shutdown is in progress...");
229 return true;
230 }
231
232 IDSnoopDevice(ActiveDeviceT[ACTIVE_WEATHER].text, "WEATHER_STATUS");
233 IDSnoopDevice(ActiveDeviceT[ACTIVE_TELESCOPE].text, "TELESCOPE_PARK");
234 IDSnoopDevice(ActiveDeviceT[ACTIVE_DOME].text, "DOME_PARK");
235
236 IUUpdateText(&ActiveDeviceTP, texts, names, n);
237 ActiveDeviceTP.s = IPS_OK;
238 IDSetText(&ActiveDeviceTP, nullptr);
239 return true;
240 }
241 }
242
243 return INDI::DefaultDevice::ISNewText(dev, name, texts, names, n);
244 }
245
246 ////////////////////////////////////////////////////////////////////////////////////
247 ///
248 ////////////////////////////////////////////////////////////////////////////////////
ISNewNumber(const char * dev,const char * name,double values[],char * names[],int n)249 bool WatchDog::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
250 {
251 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
252 {
253 // Weather threshold
254 if (!strcmp(WeatherThresholdNP.name, name))
255 {
256 IUUpdateNumber(&WeatherThresholdNP, values, names, n);
257 WeatherThresholdNP.s = IPS_OK;
258 IDSetNumber(&WeatherThresholdNP, nullptr);
259 return true;
260 }
261 // Heart Beat
262 // Client must set this property to indicate it is alive.
263 // If heat beat not received from client then shutdown procedure begins if
264 // the client trigger is selected.
265 else if (!strcmp(HeartBeatNP.name, name))
266 {
267 double prevHeartBeat = HeartBeatN[0].value;
268
269 if (watchdogClient->isBusy())
270 {
271 HeartBeatNP.s = IPS_ALERT;
272 IDSetNumber(&HeartBeatNP, nullptr);
273 LOG_ERROR("Cannot change heart beat while shutdown is in progress...");
274 return true;
275 }
276
277 IUUpdateNumber(&HeartBeatNP, values, names, n);
278 HeartBeatNP.s = IPS_OK;
279
280 // If trigger is not set, don't do anything else
281 if (ShutdownTriggerS[TRIGGER_CLIENT].s == ISS_OFF)
282 {
283 if (m_WatchDogTimer > 0)
284 {
285 RemoveTimer(m_WatchDogTimer);
286 m_WatchDogTimer = -1;
287 }
288 IDSetNumber(&HeartBeatNP, nullptr);
289 return true;
290 }
291
292 if (HeartBeatN[0].value == 0)
293 LOG_INFO("Client Watchdog is disabled.");
294 else
295 {
296 if (prevHeartBeat != HeartBeatN[0].value)
297 LOGF_INFO("Client Watchdog is enabled. Shutdown is triggered after %.f seconds of communication loss with the client.",
298 HeartBeatN[0].value);
299
300 LOG_DEBUG("Received heart beat from client.");
301
302 if (m_WatchDogTimer > 0)
303 {
304 RemoveTimer(m_WatchDogTimer);
305 m_WatchDogTimer = -1;
306 }
307
308 m_WatchDogTimer = SetTimer(HeartBeatN[0].value * 1000);
309 }
310 IDSetNumber(&HeartBeatNP, nullptr);
311
312 return true;
313 }
314 }
315
316 return DefaultDevice::ISNewNumber(dev, name, values, names, n);
317 }
318
319 ////////////////////////////////////////////////////////////////////////////////////
320 ///
321 ////////////////////////////////////////////////////////////////////////////////////
ISNewSwitch(const char * dev,const char * name,ISState * states,char * names[],int n)322 bool WatchDog::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
323 {
324 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
325 {
326 // Shutdown procedure setting
327 if (!strcmp(ShutdownProcedureSP.name, name))
328 {
329 IUUpdateSwitch(&ShutdownProcedureSP, states, names, n);
330
331 if (ShutdownProcedureS[EXECUTE_SCRIPT].s == ISS_ON &&
332 (SettingsT[EXECUTE_SCRIPT].text == nullptr || SettingsT[EXECUTE_SCRIPT].text[0] == '\0'))
333 {
334 LOG_ERROR("Error: shutdown script file is not set.");
335 ShutdownProcedureSP.s = IPS_ALERT;
336 ShutdownProcedureS[EXECUTE_SCRIPT].s = ISS_OFF;
337 }
338 else
339 ShutdownProcedureSP.s = IPS_OK;
340 IDSetSwitch(&ShutdownProcedureSP, nullptr);
341 return true;
342 }
343 // Mount Lock Policy
344 else if (!strcmp(MountPolicySP.name, name))
345 {
346 IUUpdateSwitch(&MountPolicySP, states, names, n);
347 MountPolicySP.s = IPS_OK;
348
349 if (MountPolicyS[MOUNT_IGNORED].s == ISS_ON)
350 LOG_INFO("Mount is ignored. Dome can start parking without waiting for mount to complete parking.");
351 else
352 LOG_INFO("Mount locks. Dome must wait for mount to park before it can start the parking procedure.");
353 IDSetSwitch(&MountPolicySP, nullptr);
354 return true;
355 }
356 // Shutdown Trigger handling
357 else if (!strcmp(ShutdownTriggerSP.name, name))
358 {
359 std::vector<ISState> oldStates(ShutdownTriggerSP.nsp, ISS_OFF);
360 std::vector<ISState> newStates(ShutdownTriggerSP.nsp, ISS_OFF);
361 for (int i = 0; i < ShutdownTriggerSP.nsp; i++)
362 oldStates[i] = ShutdownTriggerS[i].s;
363 IUUpdateSwitch(&ShutdownTriggerSP, states, names, n);
364 for (int i = 0; i < ShutdownTriggerSP.nsp; i++)
365 newStates[i] = ShutdownTriggerS[i].s;
366
367
368 // Check for client changes
369 if (oldStates[TRIGGER_CLIENT] != newStates[TRIGGER_CLIENT])
370 {
371 // User disabled client trigger
372 if (newStates[TRIGGER_CLIENT] == ISS_OFF)
373 {
374 LOG_INFO("Disabling client watchdog. Lost communication with client shall not trigger the shutdown procedure.");
375 if (m_WatchDogTimer > 0)
376 {
377 RemoveTimer(m_WatchDogTimer);
378 m_WatchDogTimer = -1;
379 }
380 }
381 // User enabled client trigger
382 else
383 {
384 // Check first that we have a valid heart beat
385 if (HeartBeatN[0].value == 0)
386 {
387 LOG_ERROR("Heart beat timeout should be set first.");
388 ShutdownTriggerSP.s = IPS_ALERT;
389 for (int i = 0; i < ShutdownTriggerSP.nsp; i++)
390 ShutdownTriggerS[i].s = oldStates[i];
391 IDSetSwitch(&ShutdownTriggerSP, nullptr);
392 return true;
393 }
394
395 LOGF_INFO("Client Watchdog is enabled. Shutdown is triggered after %.f seconds of communication loss with the client.",
396 HeartBeatN[0].value);
397 if (m_WatchDogTimer > 0)
398 RemoveTimer(m_WatchDogTimer);
399 m_WatchDogTimer = SetTimer(HeartBeatN[0].value * 1000);
400 }
401 }
402
403 // Check for weather changes
404 if (oldStates[TRIGGER_WEATHER] != newStates[TRIGGER_WEATHER])
405 {
406 // User disabled weather trigger
407 if (newStates[TRIGGER_WEATHER] == ISS_OFF)
408 {
409 // If we have an active timer, remove it.
410 if (m_WeatherAlertTimer > 0)
411 {
412 RemoveTimer(m_WeatherAlertTimer);
413 m_WeatherAlertTimer = -1;
414 }
415
416 LOG_INFO("Weather Watchdog is disabled.");
417 }
418 else
419 LOG_INFO("Weather Watchdog is enabled.");
420
421 }
422
423 ShutdownTriggerSP.s = IPS_OK;
424 IDSetSwitch(&ShutdownTriggerSP, nullptr);
425 return true;
426 }
427 }
428
429 return DefaultDevice::ISNewSwitch(dev, name, states, names, n);
430 }
431
432 ////////////////////////////////////////////////////////////////////////////////////
433 ///
434 ////////////////////////////////////////////////////////////////////////////////////
ISSnoopDevice(XMLEle * root)435 bool WatchDog::ISSnoopDevice(XMLEle * root)
436 {
437 const char * propName = findXMLAttValu(root, "name");
438
439 // Weather Status
440 if (!strcmp("WEATHER_STATUS", propName))
441 {
442 IPState newWeatherState;
443 crackIPState(findXMLAttValu(root, "state"), &newWeatherState);
444
445 // In case timer is active, and weather status is now OK
446 // Let's disable the timer
447 if (m_WeatherState == IPS_ALERT && newWeatherState != IPS_ALERT)
448 {
449 LOG_INFO("Weather status is no longer in DANGER zone.");
450 if (m_WeatherAlertTimer > 0)
451 {
452 LOG_INFO("Shutdown procedure cancelled.");
453 RemoveTimer(m_WeatherAlertTimer);
454 m_WeatherAlertTimer = -1;
455 }
456 }
457
458 // In case weather shutdown is active and;
459 // weather timer is off and;
460 // previous weather status is not alert and;
461 // current weather status is alert, then
462 // we start the weather timer which on timeout would case the shutdown procedure to commence.
463 if (m_WeatherState != IPS_ALERT && newWeatherState == IPS_ALERT)
464 {
465 LOG_WARN("Weather is in DANGER zone.");
466 if (ShutdownTriggerS[TRIGGER_WEATHER].s == ISS_ON && m_WeatherAlertTimer == -1)
467 {
468 if (WeatherThresholdN[0].value > 0)
469 LOGF_INFO("Shutdown procedure shall commence in %.f seconds unless weather status improves.", WeatherThresholdN[0].value);
470 m_WeatherAlertTimer = SetTimer(WeatherThresholdN[0].value * 1000);
471 }
472 }
473
474 m_WeatherState = newWeatherState;
475 }
476 // Check Telescope Park status
477 else if (!strcmp("TELESCOPE_PARK", propName))
478 {
479 if (!strcmp(findXMLAttValu(root, "state"), "Ok"))
480 {
481 XMLEle * ep = nullptr;
482 bool parked = false;
483 for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
484 {
485 const char * elemName = findXMLAttValu(ep, "name");
486
487 if ((!strcmp(elemName, "PARK") && !strcmp(pcdataXMLEle(ep), "On")))
488 parked = true;
489 else if ((!strcmp(elemName, "UNPARK") && !strcmp(pcdataXMLEle(ep), "On")))
490 parked = false;
491 }
492 if (parked != m_IsMountParked)
493 {
494 LOGF_INFO("Mount is %s", parked ? "Parked" : "Unparked");
495 m_IsMountParked = parked;
496 // In case mount was UNPARKED while weather status is still ALERT
497 // And weather shutdown trigger was active and mount parking was selected
498 // then we force the mount to park again.
499 if (parked == false &&
500 ShutdownTriggerS[TRIGGER_WEATHER].s == ISS_ON &&
501 m_WeatherState == IPS_ALERT &&
502 ShutdownProcedureS[PARK_MOUNT].s == ISS_ON)
503 {
504 LOG_WARN("Mount unparked while weather alert is active! Parking mount...");
505 watchdogClient->parkMount();
506 }
507 }
508 return true;
509 }
510 }
511 // Check Telescope Park status
512 else if (!strcmp("DOME_PARK", propName))
513 {
514 if (!strcmp(findXMLAttValu(root, "state"), "Ok") || !strcmp(findXMLAttValu(root, "state"), "Busy"))
515 {
516 XMLEle * ep = nullptr;
517 bool parked = false;
518 for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
519 {
520 const char * elemName = findXMLAttValu(ep, "name");
521
522 if ((!strcmp(elemName, "PARK") && !strcmp(pcdataXMLEle(ep), "On")))
523 parked = true;
524 else if ((!strcmp(elemName, "UNPARK") && !strcmp(pcdataXMLEle(ep), "On")))
525 parked = false;
526 }
527 if (parked != m_IsDomeParked)
528 {
529 LOGF_INFO("Dome is %s", parked ? "Parked" : "Unparked");
530 m_IsDomeParked = parked;
531 // In case mount was UNPARKED while weather status is still ALERT
532 // And weather shutdown trigger was active and mount parking was selected
533 // then we force the mount to park again.
534 if (parked == false &&
535 ShutdownTriggerS[TRIGGER_WEATHER].s == ISS_ON &&
536 m_WeatherState == IPS_ALERT &&
537 ShutdownProcedureS[PARK_DOME].s == ISS_ON)
538 {
539 LOG_WARN("Dome unparked while weather alert is active! Parking dome...");
540 watchdogClient->parkDome();
541 }
542 }
543 return true;
544 }
545 }
546
547 return DefaultDevice::ISSnoopDevice(root);
548 }
549
550 ////////////////////////////////////////////////////////////////////////////////////
551 ///
552 ////////////////////////////////////////////////////////////////////////////////////
saveConfigItems(FILE * fp)553 bool WatchDog::saveConfigItems(FILE * fp)
554 {
555 INDI::DefaultDevice::saveConfigItems(fp);
556
557 IUSaveConfigNumber(fp, &HeartBeatNP);
558 IUSaveConfigNumber(fp, &WeatherThresholdNP);
559 IUSaveConfigText(fp, &SettingsTP);
560 IUSaveConfigText(fp, &ActiveDeviceTP);
561 IUSaveConfigSwitch(fp, &MountPolicySP);
562 IUSaveConfigSwitch(fp, &ShutdownTriggerSP);
563 IUSaveConfigSwitch(fp, &ShutdownProcedureSP);
564
565 return true;
566 }
567
568 ////////////////////////////////////////////////////////////////////////////////////
569 ///
570 ////////////////////////////////////////////////////////////////////////////////////
TimerHit()571 void WatchDog::TimerHit()
572 {
573 // Timer is up, we need to start shutdown procedure
574
575 // If nothing to do, then return
576 if (ShutdownProcedureS[PARK_DOME].s == ISS_OFF && ShutdownProcedureS[PARK_MOUNT].s == ISS_OFF &&
577 ShutdownProcedureS[EXECUTE_SCRIPT].s == ISS_OFF)
578 return;
579
580 switch (m_ShutdownStage)
581 {
582 // Connect to server
583 case WATCHDOG_IDLE:
584
585 ShutdownProcedureSP.s = IPS_BUSY;
586 IDSetSwitch(&ShutdownProcedureSP, nullptr);
587
588 if (m_WeatherState == IPS_ALERT)
589 LOG_WARN("Warning! Weather status in DANGER zone, executing shutdown procedure...");
590 else
591 LOG_WARN("Warning! Heartbeat threshold timed out, executing shutdown procedure...");
592
593 // No need to start client if only we need to execute the script
594 if (ShutdownProcedureS[PARK_MOUNT].s == ISS_OFF && ShutdownProcedureS[PARK_DOME].s == ISS_OFF &&
595 ShutdownProcedureS[EXECUTE_SCRIPT].s == ISS_ON)
596 {
597 executeScript();
598 break;
599 }
600
601 // Watch mount if requied
602 if (ShutdownProcedureS[PARK_MOUNT].s == ISS_ON)
603 watchdogClient->setMount(ActiveDeviceT[ACTIVE_TELESCOPE].text);
604 // Watch dome
605 if (ShutdownProcedureS[PARK_DOME].s == ISS_ON)
606 watchdogClient->setDome(ActiveDeviceT[ACTIVE_DOME].text);
607
608 // Set indiserver host and port
609 watchdogClient->setServer(SettingsT[INDISERVER_HOST].text, m_INDIServerPort);
610
611 LOG_DEBUG("Connecting to INDI server...");
612
613 watchdogClient->connectServer();
614
615 m_ShutdownStage = WATCHDOG_CLIENT_STARTED;
616
617 break;
618
619 case WATCHDOG_CLIENT_STARTED:
620 // Check if client is ready
621 if (watchdogClient->isConnected())
622 {
623 LOGF_DEBUG("Connected to INDI server %s @ %s", SettingsT[0].text,
624 SettingsT[1].text);
625
626 if (ShutdownProcedureS[PARK_MOUNT].s == ISS_ON)
627 parkMount();
628 else if (ShutdownProcedureS[PARK_DOME].s == ISS_ON)
629 parkDome();
630 else if (ShutdownProcedureS[EXECUTE_SCRIPT].s == ISS_ON)
631 executeScript();
632 }
633 else
634 LOG_DEBUG("Waiting for INDI server connection...");
635 break;
636
637 case WATCHDOG_MOUNT_PARKED:
638 {
639 // check if mount is parked
640 IPState mountState = watchdogClient->getMountParkState();
641
642 if (mountState == IPS_OK || mountState == IPS_IDLE)
643 {
644 LOG_INFO("Mount parked.");
645
646 if (ShutdownProcedureS[PARK_DOME].s == ISS_ON)
647 parkDome();
648 else if (ShutdownProcedureS[EXECUTE_SCRIPT].s == ISS_ON)
649 executeScript();
650 else
651 m_ShutdownStage = WATCHDOG_COMPLETE;
652 }
653 }
654 break;
655
656 case WATCHDOG_DOME_PARKED:
657 {
658 // check if dome is parked
659 IPState domeState = watchdogClient->getDomeParkState();
660
661 if (domeState == IPS_OK || domeState == IPS_IDLE)
662 {
663 LOG_INFO("Dome parked.");
664
665 if (ShutdownProcedureS[EXECUTE_SCRIPT].s == ISS_ON)
666 executeScript();
667 else
668 m_ShutdownStage = WATCHDOG_COMPLETE;
669 }
670 }
671 break;
672
673 case WATCHDOG_COMPLETE:
674 LOG_INFO("Shutdown procedure complete.");
675 ShutdownProcedureSP.s = IPS_OK;
676 IDSetSwitch(&ShutdownProcedureSP, nullptr);
677 // If watch dog client still connected, keep it as such
678 if (watchdogClient->isConnected())
679 m_ShutdownStage = WATCHDOG_CLIENT_STARTED;
680 // If server is shutdown, then we reset to IDLE
681 else
682 m_ShutdownStage = WATCHDOG_IDLE;
683 return;
684
685 case WATCHDOG_ERROR:
686 ShutdownProcedureSP.s = IPS_ALERT;
687 IDSetSwitch(&ShutdownProcedureSP, nullptr);
688 return;
689 }
690
691 SetTimer(getCurrentPollingPeriod());
692 }
693
parkDome()694 void WatchDog::parkDome()
695 {
696 if (watchdogClient->parkDome() == false)
697 {
698 LOG_ERROR("Error: Unable to park dome! Shutdown procedure terminated.");
699 m_ShutdownStage = WATCHDOG_ERROR;
700 return;
701 }
702
703 LOG_INFO("Parking dome...");
704 m_ShutdownStage = WATCHDOG_DOME_PARKED;
705 }
706
parkMount()707 void WatchDog::parkMount()
708 {
709 if (watchdogClient->parkMount() == false)
710 {
711 LOG_ERROR("Error: Unable to park mount! Shutdown procedure terminated.");
712 m_ShutdownStage = WATCHDOG_ERROR;
713 return;
714 }
715
716 LOG_INFO("Parking mount...");
717
718 // If mount is set to ignored, and we have active dome shutdown then we start
719 // parking the dome immediately.
720 if (MountPolicyS[MOUNT_IGNORED].s == ISS_ON && ShutdownProcedureS[PARK_DOME].s == ISS_ON)
721 parkDome();
722 else
723 m_ShutdownStage = WATCHDOG_MOUNT_PARKED;
724 }
725
executeScript()726 void WatchDog::executeScript()
727 {
728 // child
729 if (fork() == 0)
730 {
731 int rc = execlp(SettingsT[EXECUTE_SCRIPT].text, SettingsT[EXECUTE_SCRIPT].text, nullptr);
732
733 if (rc)
734 exit(rc);
735 }
736 // parent
737 else
738 {
739 int statval;
740 LOGF_INFO("Executing script %s...", SettingsT[EXECUTE_SCRIPT].text);
741 LOGF_INFO("Waiting for script with PID %d to complete...", getpid());
742 wait(&statval);
743 if (WIFEXITED(statval))
744 {
745 int exit_code = WEXITSTATUS(statval);
746 LOGF_INFO("Script complete with exit code %d", exit_code);
747
748 if (exit_code == 0)
749 m_ShutdownStage = WATCHDOG_COMPLETE;
750 else
751 {
752 LOGF_ERROR("Error: script %s failed. Shutdown procedure terminated.",
753 SettingsT[EXECUTE_SCRIPT].text);
754 m_ShutdownStage = WATCHDOG_ERROR;
755 return;
756 }
757 }
758 else
759 {
760 LOGF_ERROR(
761 "Error: script %s did not terminate with exit. Shutdown procedure terminated.",
762 SettingsT[EXECUTE_SCRIPT].text);
763 m_ShutdownStage = WATCHDOG_ERROR;
764 return;
765 }
766 }
767 }
768