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