Skip to content

Instantly share code, notes, and snippets.

@magnue
Created December 29, 2015 17:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save magnue/65317ea1fe3893341540 to your computer and use it in GitHub Desktop.
Save magnue/65317ea1fe3893341540 to your computer and use it in GitHub Desktop.
Edited indi_watchdog for enabeling choise on parking mount -> dome, or dome -> mount
/*******************************************************************************
Copyright(c) 2015 Jasem Mutlaq. All rights reserved.
INDI Watchdog driver.
The driver expects a heartbeat from the client every X minutes. If no heartbeat
is received, the driver executes the shutdown procedures.
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your option)
any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
The full GNU General Public License is included in this distribution in the
file called LICENSE.
*******************************************************************************/
#include <memory>
#include <libnova.h>
#include <time.h>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>
#include "watchdog.h"
#include "watchdogclient.h"
#define POLLMS 1000
// We declare unique pointer to my lovely German Shephard Juli (http://indilib.org/images/juli_tommy.jpg)
std::unique_ptr<WatchDog> goodgirrrl(new WatchDog());
void ISGetProperties(const char *dev)
{
goodgirrrl->ISGetProperties(dev);
}
void ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int num)
{
goodgirrrl->ISNewSwitch(dev, name, states, names, num);
}
void ISNewText( const char *dev, const char *name, char *texts[], char *names[], int num)
{
goodgirrrl->ISNewText(dev, name, texts, names, num);
}
void ISNewNumber(const char *dev, const char *name, double values[], char *names[], int num)
{
goodgirrrl->ISNewNumber(dev, name, values, names, num);
}
void ISNewBLOB (const char *dev, const char *name, int sizes[], int blobsizes[], char *blobs[], char *formats[], char *names[], int n)
{
INDI_UNUSED(dev);
INDI_UNUSED(name);
INDI_UNUSED(sizes);
INDI_UNUSED(blobsizes);
INDI_UNUSED(blobs);
INDI_UNUSED(formats);
INDI_UNUSED(names);
INDI_UNUSED(n);
}
void ISSnoopDevice (XMLEle *root)
{
INDI_UNUSED(root);
}
WatchDog::WatchDog()
{
setVersion(0,1);
setDriverInterface(AUX_INTERFACE);
watchdogClient = new WatchDogClient();
watchDogTimer = -1;
shutdownStage = WATCHDOG_IDLE;
}
WatchDog::~WatchDog()
{
delete(watchdogClient);
}
const char * WatchDog::getDefaultName()
{
return (char *)"WatchDog";
}
bool WatchDog::Connect()
{
if (HeartBeatN[0].value > 0)
{
DEBUGF(INDI::Logger::DBG_SESSION, "Watchdog is enabled. Shutdown is triggered after %g minutes of communication loss with the client.", HeartBeatN[0].value);
watchDogTimer = SetTimer(HeartBeatN[0].value*60*1000);
}
else
DEBUG(INDI::Logger::DBG_SESSION, "Watchdog is disabled.");
return true;
}
bool WatchDog::Disconnect()
{
if (watchDogTimer > 0)
{
RemoveTimer(watchDogTimer);
DEBUG(INDI::Logger::DBG_SESSION, "Watchdog is disabled.");
}
shutdownStage = WATCHDOG_IDLE;
return true;
}
bool WatchDog::initProperties()
{
INDI::DefaultDevice::initProperties();
IUFillNumber(&HeartBeatN[0],"WATCHDOG_HEARTBEAT_VALUE","Threshold (min)","%g",0,180,10,0);
IUFillNumberVector(&HeartBeatNP,HeartBeatN,1,getDeviceName(),"WATCHDOG_HEARTBEAT","Heart beat", MAIN_CONTROL_TAB,IP_RW,60,IPS_IDLE);
IUFillText(&SettingsT[0],"INDISERVER_HOST","indiserver host","localhost");
IUFillText(&SettingsT[1],"INDISERVER_PORT","indiserver port","7624");
IUFillText(&SettingsT[2],"SHUTDOWN_SCRIPT","shutdown script",NULL);
IUFillTextVector(&SettingsTP,SettingsT,3,getDeviceName(),"WATCHDOG_SETTINGS","Settings",MAIN_CONTROL_TAB,IP_RW,60,IPS_IDLE);
IUFillSwitch(&ShutdownProcedureS[0],"PARK_DOME","Park Dome",ISS_OFF);
IUFillSwitch(&ShutdownProcedureS[1],"PARK_MOUNT","Park Mount",ISS_OFF);
IUFillSwitch(&ShutdownProcedureS[2],"EXECUTE_SCRIPT","Execute Script",ISS_OFF);
IUFillSwitchVector(&ShutdownProcedureSP,ShutdownProcedureS,3,getDeviceName(),"WATCHDOG_SHUTDOWN","Shutdown",MAIN_CONTROL_TAB,IP_RW,ISR_NOFMANY,60,IPS_IDLE);
IUFillSwitch(&ShutdownProcedureFlipS[0],"PARK_MOUNT","Park Mount",ISS_OFF);
IUFillSwitch(&ShutdownProcedureFlipS[1],"PARK_DOME","Park Dome",ISS_OFF);
IUFillSwitch(&ShutdownProcedureFlipS[2],"EXECUTE_SCRIPT","Execute Script",ISS_OFF);
IUFillSwitchVector(&ShutdownProcedureFlipSP,ShutdownProcedureFlipS,3,getDeviceName(),"WATCHDOG_SHUTDOWN_FLIP","Shutdown",MAIN_CONTROL_TAB,IP_RW,ISR_NOFMANY,60,IPS_IDLE);
IUFillText(&ActiveDeviceT[0],"ACTIVE_TELESCOPE","Telescope","Telescope Simulator");
IUFillText(&ActiveDeviceT[1],"ACTIVE_DOME","Dome","Dome Simulator");
IUFillTextVector(&ActiveDeviceTP,ActiveDeviceT,2,getDeviceName(),"ACTIVE_DEVICES","Active devices",OPTIONS_TAB,IP_RW,60,IPS_IDLE);
IUFillSwitch(&ShutdownMountDomeFlipS[0],"DOME_FIRST","Dome first",ISS_ON);
IUFillSwitch(&ShutdownMountDomeFlipS[1],"MOUNT_FIRST","Mount first",ISS_OFF);
IUFillSwitchVector(&ShutdownMountDomeFlipSP,ShutdownMountDomeFlipS,2,getDeviceName(),"WATCHDOG_SHUTDOWN_FLIP_ENABLE","Park order",OPTIONS_TAB,IP_RW,ISR_1OFMANY,60,IPS_IDLE);
addDebugControl();
return true;
}
void WatchDog::ISGetProperties (const char *dev)
{
// First we let our parent populate
DefaultDevice::ISGetProperties(dev);
defineNumber(&HeartBeatNP);
defineText(&SettingsTP);
defineSwitch(&ShutdownProcedureSP);
defineText(&ActiveDeviceTP);
defineSwitch(&ShutdownMountDomeFlipSP);
loadConfig(true);
//watchdogClient->setTelescope(ActiveDeviceT[0].text);
//watchdogClient->setDome(ActiveDeviceT[1].text);
}
bool WatchDog::ISNewText (const char *dev, const char *name, char *texts[], char *names[], int n)
{
// first check if it's for our device
if(strcmp(dev,getDeviceName())==0)
{
if (!strcmp(SettingsTP.name, name))
{
IUUpdateText(&SettingsTP, texts, names, n);
SettingsTP.s = IPS_OK;
IDSetText(&SettingsTP, NULL);
return true;
}
if (!strcmp(ActiveDeviceTP.name, name))
{
if (watchdogClient->isBusy())
{
ActiveDeviceTP.s = IPS_ALERT;
IDSetText(&ActiveDeviceTP, NULL);
DEBUG(INDI::Logger::DBG_ERROR, "Cannot change devices names while shutdown is in progress...");
return true;
}
IUUpdateText(&ActiveDeviceTP, texts, names, n);
ActiveDeviceTP.s = IPS_OK;
IDSetText(&ActiveDeviceTP, NULL);
//watchdogClient->setTelescope(ActiveDeviceT[0].text);
//watchdogClient->setDome(ActiveDeviceT[1].text);
return true;
}
}
return INDI::DefaultDevice::ISNewText(dev,name,texts,names,n);
}
bool WatchDog::ISNewNumber (const char *dev, const char *name, double values[], char *names[], int n)
{
if(strcmp(dev,getDeviceName())==0)
{
if (!strcmp(HeartBeatNP.name, name))
{
double prevHeartBeat = HeartBeatN[0].value;
if (watchdogClient->isBusy())
{
HeartBeatNP.s = IPS_ALERT;
IDSetNumber(&HeartBeatNP, NULL);
DEBUG(INDI::Logger::DBG_ERROR, "Cannot change heart beat while shutdown is in progress...");
return true;
}
IUUpdateNumber(&HeartBeatNP, values, names, n);
HeartBeatNP.s = IPS_OK;
if (HeartBeatN[0].value == 0)
{
DEBUG(INDI::Logger::DBG_SESSION, "Watchdog is disabled.");
}
else
{
if (isConnected())
{
if (prevHeartBeat != HeartBeatN[0].value)
DEBUGF(INDI::Logger::DBG_SESSION, "Watchdog is enabled. Shutdown is triggered after %g minutes of communication loss with the client.", HeartBeatN[0].value);
DEBUG(INDI::Logger::DBG_DEBUG, "Received heart beat from client.");
RemoveTimer(watchDogTimer);
watchDogTimer = SetTimer(HeartBeatN[0].value*60*1000);
}
else
DEBUG(INDI::Logger::DBG_SESSION, "Watchdog is armed. Please connect to enable it.");
}
IDSetNumber(&HeartBeatNP, NULL);
return true;
}
}
return DefaultDevice::ISNewNumber(dev,name,values,names,n);
}
bool WatchDog::ISNewSwitch (const char *dev, const char *name, ISState *states, char *names[], int n)
{
if(strcmp(dev,getDeviceName())==0)
{
if (!strcmp(ShutdownProcedureSP.name, name) || !strcmp(ShutdownProcedureFlipSP.name, name))
{
IUUpdateSwitch(activeProcedureSP, states, names, n);
if (activeProcedureS[EXECUTE_SCRIPT].s == ISS_ON && (SettingsT[EXECUTE_SCRIPT].text == NULL || SettingsT[EXECUTE_SCRIPT].text[0] == '\0'))
{
DEBUG(INDI::Logger::DBG_ERROR, "Error: shutdown script file is not set.");
activeProcedureSP->s = IPS_ALERT;
activeProcedureS[EXECUTE_SCRIPT].s = ISS_OFF;
}
else
activeProcedureSP->s = IPS_OK;
IDSetSwitch(activeProcedureSP, NULL);
return true;
}
else if (!strcmp(ShutdownMountDomeFlipSP.name, name))
{
IUUpdateSwitch(&ShutdownMountDomeFlipSP, states, names, n);
if (ShutdownMountDomeFlipS[0].s == ISS_ON)
{
deleteProperty(ShutdownProcedureFlipSP.name);
defineSwitch(&ShutdownProcedureSP);
ShutdownProcedureS[PARK_DOME].s = ShutdownProcedureFlipS[PARK_DOME_FLIP].s;
ShutdownProcedureS[PARK_MOUNT].s = ShutdownProcedureFlipS[PARK_MOUNT_FLIP].s;
ShutdownProcedureS[EXECUTE_SCRIPT].s = ShutdownProcedureFlipS[EXECUTE_SCRIPT].s;
activeProcedureSP = &ShutdownProcedureSP;
activeProcedureS = ShutdownProcedureS;
activeProcedureSP->s = IPS_OK;
IDSetSwitch(activeProcedureSP,NULL);
DEBUG(INDI::Logger::DBG_SESSION,"Set to park Dome first. Only use this option if parking dome will not interfere with mount");
}
else if (ShutdownMountDomeFlipS[1].s == ISS_ON)
{
deleteProperty(ShutdownProcedureSP.name);
defineSwitch(&ShutdownProcedureFlipSP);
ShutdownProcedureFlipS[PARK_MOUNT_FLIP].s = ShutdownProcedureS[PARK_MOUNT].s;
ShutdownProcedureFlipS[PARK_DOME_FLIP].s = ShutdownProcedureS[PARK_DOME].s;
ShutdownProcedureFlipS[EXECUTE_SCRIPT].s = ShutdownProcedureS[EXECUTE_SCRIPT].s;
activeProcedureSP = &ShutdownProcedureFlipSP;
activeProcedureS = ShutdownProcedureFlipS;
activeProcedureSP->s = IPS_OK;
IDSetSwitch(activeProcedureSP,NULL);
DEBUG(INDI::Logger::DBG_SESSION,"Set to park Mount first. If mount failes to park, the dome will be left open!");
}
ShutdownMountDomeFlipSP.s = IPS_OK;
IDSetSwitch(&ShutdownMountDomeFlipSP, NULL);
return true;
}
}
return DefaultDevice::ISNewSwitch(dev, name, states, names, n);
}
bool WatchDog::saveConfigItems(FILE *fp)
{
IUSaveConfigNumber(fp, &HeartBeatNP);
IUSaveConfigText(fp, &SettingsTP);
IUSaveConfigText(fp, &ActiveDeviceTP);
IUSaveConfigSwitch(fp, &ShutdownMountDomeFlipSP);
IUSaveConfigSwitch(fp, activeProcedureSP);
}
void WatchDog::TimerHit()
{
// Timer is up, we need to start shutdown procedure
// If nothing to do, then return
if (activeProcedureS[PARK_DOME].s == ISS_OFF && activeProcedureS[PARK_MOUNT].s == ISS_OFF && activeProcedureS[EXECUTE_SCRIPT].s == ISS_OFF)
return;
bool defaultProcedure = ShutdownMountDomeFlipS[0].s == ISS_ON ? true : false;
switch (shutdownStage)
{
// Connect to server
case WATCHDOG_IDLE:
activeProcedureSP->s = IPS_BUSY;
IDSetSwitch(activeProcedureSP, NULL);
DEBUG(INDI::Logger::DBG_WARNING, "Warning! Heartbeat threshold timed out, executing shutdown procedure...");
// No need to start client if only we need to execute the script
if (activeProcedureS[PARK_DOME].s == ISS_OFF && activeProcedureS[PARK_MOUNT].s == ISS_OFF && activeProcedureS[EXECUTE_SCRIPT].s == ISS_ON)
{
executeScript();
break;
}
// Watch dome
watchdogClient->setDome(ActiveDeviceT[0].text);
// Watch mount
watchdogClient->setMount(ActiveDeviceT[1].text);
// Set indiserver host and port
watchdogClient->setServer(SettingsT[0].text, atoi(SettingsT[1].text));
DEBUG(INDI::Logger::DBG_DEBUG, "Connecting to INDI server...");
watchdogClient->connectServer();
shutdownStage = WATCHDOG_CLIENT_STARTED;
break;
case WATCHDOG_CLIENT_STARTED:
// Check if client is ready
if (watchdogClient->isConnected())
{
DEBUGF(INDI::Logger::DBG_DEBUG, "Connected to INDI server %s @ %s", SettingsT[0].text, SettingsT[1].text);
// Default procedure -> Dome -> Mount -> Script
if (defaultProcedure)
if (activeProcedureS[PARK_DOME].s == ISS_ON)
parkDome();
else if (activeProcedureS[PARK_MOUNT].s == ISS_ON)
parkMount();
else if (activeProcedureS[EXECUTE_SCRIPT].s == ISS_ON)
executeScript();
// Flip procedure -> Mount -> Dome -> Script
if (!defaultProcedure)
if (activeProcedureS[PARK_MOUNT_FLIP].s == ISS_ON)
parkMount();
else if (activeProcedureS[PARK_DOME_FLIP].s == ISS_ON)
parkDome();
else if (activeProcedureS[EXECUTE_SCRIPT].s == ISS_ON)
executeScript();
}
else
DEBUG(INDI::Logger::DBG_DEBUG, "Waiting for INDI server connection...");
break;
case WATCHDOG_DOME_PARKED:
{
// check if dome is parked
IPState domeState = watchdogClient->getDomeParkState();
if (domeState == IPS_OK || domeState == IPS_IDLE)
{
DEBUG(INDI::Logger::DBG_SESSION, "Dome parked.");
if (defaultProcedure && activeProcedureS[PARK_MOUNT].s == ISS_ON)
parkMount();
else if (activeProcedureS[EXECUTE_SCRIPT].s == ISS_ON)
executeScript();
else
shutdownStage = WATCHDOG_COMPLETE;
}
}
break;
case WATCHDOG_MOUNT_PARKED:
{
// check if mount is parked
IPState mountState = watchdogClient->getMountParkState();
if (mountState == IPS_OK || mountState == IPS_IDLE)
{
DEBUG(INDI::Logger::DBG_SESSION, "Mount parked.");
if (!defaultProcedure && activeProcedureS[PARK_DOME_FLIP].s == ISS_ON)
parkDome();
else if (activeProcedureS[EXECUTE_SCRIPT].s == ISS_ON)
executeScript();
else
shutdownStage = WATCHDOG_COMPLETE;
}
}
break;
case WATCHDOG_COMPLETE:
DEBUG(INDI::Logger::DBG_SESSION, "Shutdown procedure complete.");
activeProcedureSP->s = IPS_OK;
IDSetSwitch(activeProcedureSP, NULL);
return;
case WATCHDOG_ERROR:
activeProcedureSP->s = IPS_ALERT;
IDSetSwitch(activeProcedureSP, NULL);
return;
}
SetTimer(POLLMS);
}
void WatchDog::parkDome()
{
if (watchdogClient->parkDome() == false)
{
DEBUG(INDI::Logger::DBG_ERROR, "Error: Unable to park dome! Shutdown procedure terminated.");
shutdownStage = WATCHDOG_ERROR;
return;
}
DEBUG(INDI::Logger::DBG_SESSION, "Parking dome...");
shutdownStage = WATCHDOG_DOME_PARKED;
}
void WatchDog::parkMount()
{
if (watchdogClient->parkMount() == false)
{
DEBUG(INDI::Logger::DBG_ERROR, "Error: Unable to park mount! Shutdown procedure terminated.");
shutdownStage = WATCHDOG_ERROR;
return;
}
DEBUG(INDI::Logger::DBG_SESSION, "Parking mount...");
shutdownStage = WATCHDOG_MOUNT_PARKED;
}
void WatchDog::executeScript()
{
// child
if(fork() == 0)
{
int rc = execlp(SettingsT[EXECUTE_SCRIPT].text, SettingsT[EXECUTE_SCRIPT].text, NULL);
if (rc)
exit(rc);
}
// parent
else
{
int statval;
DEBUGF(INDI::Logger::DBG_SESSION, "Executing script %s...", SettingsT[EXECUTE_SCRIPT].text);
DEBUGF(INDI::Logger::DBG_SESSION, "Waiting for script with PID %d to complete...", getpid());
wait(&statval);
if(WIFEXITED(statval))
{
int exit_code = WEXITSTATUS(statval);
DEBUGF(INDI::Logger::DBG_SESSION, "Script complete with exit code %d", exit_code);
if (exit_code == 0)
shutdownStage = WATCHDOG_COMPLETE;
else
{
DEBUGF(INDI::Logger::DBG_ERROR, "Error: script %s failed. Shutdown procedure terminated.", SettingsT[EXECUTE_SCRIPT].text);
shutdownStage = WATCHDOG_ERROR;
return;
}
}
else
{
DEBUGF(INDI::Logger::DBG_ERROR, "Error: script %s did not terminate with exit. Shutdown procedure terminated.", SettingsT[EXECUTE_SCRIPT].text);
shutdownStage = WATCHDOG_ERROR;
return;
}
}
}
/*******************************************************************************
Copyright(c) 2015 Jasem Mutlaq. All rights reserved.
INDI Watchdog driver.
The driver expects a heartbeat from the client every X minutes. If no heartbeat
is received, the driver executes the shutdown procedures.
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your option)
any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
The full GNU General Public License is included in this distribution in the
file called LICENSE.
*******************************************************************************/
#ifndef WATCHDOG_H
#define WATCHDOG_H
#include "defaultdevice.h"
class WatchDogClient;
class WatchDog : public INDI::DefaultDevice
{
public:
typedef enum { WATCHDOG_IDLE, WATCHDOG_CLIENT_STARTED, WATCHDOG_DOME_PARKED, WATCHDOG_MOUNT_PARKED, WATCHDOG_COMPLETE, WATCHDOG_ERROR } ShutdownStages;
typedef enum { PARK_DOME, PARK_MOUNT, EXECUTE_SCRIPT } ShutdownProcedure;
typedef enum { PARK_MOUNT_FLIP, PARK_DOME_FLIP } ShutdownProcedureFlip;
WatchDog();
virtual ~WatchDog();
virtual void ISGetProperties (const char *dev);
//virtual bool ISSnoopDevice (XMLEle *root);
virtual bool ISNewText (const char *dev, const char *name, char *texts[], char *names[], int n);
virtual bool ISNewNumber (const char *dev, const char *name, double values[], char *names[], int n);
virtual bool ISNewSwitch (const char *dev, const char *name, ISState *states, char *names[], int n);
protected:
virtual bool initProperties();
//virtual bool updateProperties();
virtual void TimerHit();
virtual bool Connect();
virtual bool Disconnect();
virtual const char *getDefaultName();
virtual bool saveConfigItems(FILE *fp);
private:
void parkDome();
void parkMount();
void executeScript();
INumberVectorProperty HeartBeatNP;
INumber HeartBeatN[1];
ITextVectorProperty SettingsTP;
IText SettingsT[3];
ISwitchVectorProperty ShutdownProcedureSP;
ISwitch ShutdownProcedureS[3];
ISwitchVectorProperty ShutdownProcedureFlipSP;
ISwitch ShutdownProcedureFlipS[3];
ISwitchVectorProperty *activeProcedureSP {&ShutdownProcedureSP};
ISwitch *activeProcedureS {ShutdownProcedureS};
ITextVectorProperty ActiveDeviceTP;
IText ActiveDeviceT[2];
ISwitchVectorProperty ShutdownMountDomeFlipSP;
ISwitch ShutdownMountDomeFlipS[2];
WatchDogClient *watchdogClient;
int watchDogTimer;
ShutdownStages shutdownStage;
};
#endif
@magnue
Copy link
Author

magnue commented Dec 29, 2015

As i need the mount to be parked before dome i made it a user option

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment