Tutorial Two: Simple Telescope
This tutorial demonstrates how to create a simple telescope simulator that can perform GOTO operations. It builds on the concepts introduced in Tutorial One and introduces more advanced INDI functionality.
What You’ll Learn
- How to create a telescope driver by inheriting from
INDI::Telescope
- How to implement GOTO functionality
- How to simulate telescope movement
- How to report telescope status to clients
Source Code
simplescope.h
/*
INDI Developers Manual
Tutorial #2
"Simple Telescope Driver"
We develop a simple telescope simulator.
Refer to README which contains instruction on how to build this driver and use it
with an INDI-compatible client.
*/
/** \file simplescope.h
\brief Construct a basic INDI telescope device that simulates GOTO commands.
\author Jasem Mutlaq
\example simplescope.h
A simple GOTO telescope that simulator slewing operation.
*/
#pragma once
#include "inditelescope.h"
class SimpleScope : public INDI::Telescope
{
public:
SimpleScope();
protected:
bool Handshake() override;
const char *getDefaultName() override;
bool initProperties() override;
// Telescope specific functions
bool ReadScopeStatus() override;
bool Goto(double, double) override;
bool Abort() override;
private:
double currentRA {0};
double currentDEC {90};
double targetRA {0};
double targetDEC {0};
// Debug channel to write mount logs to
// Default INDI::Logger debugging/logging channel are Message, Warn, Error, and Debug
// Since scope information can be _very_ verbose we create another channel SCOPE specifically
// for extra debug logs. This way the user can turn it on/off as desired.
uint8_t DBG_SCOPE { INDI::Logger::DBG_IGNORE };
// slew rate degrees/s
static const uint8_t SLEW_RATE = 3;
};
simplescope.cpp
/*
INDI Developers Manual
Tutorial #2
"Simple Telescope Driver"
We develop a simple telescope simulator.
Refer to README which contains instruction on how to build this driver and use it
with an INDI-compatible client.
*/
/** \file simplescope.cpp
\brief Construct a basic INDI telescope device that simulates GOTO commands.
\author Jasem Mutlaq
\example simplescope.cpp
A simple GOTO telescope that simulator slewing operation.
*/
#include "simplescope.h"
#include "indicom.h"
#include <cmath>
#include <memory>
static std::unique_ptr<SimpleScope> simpleScope(new SimpleScope());
SimpleScope::SimpleScope()
{
// We add an additional debug level so we can log verbose scope status
DBG_SCOPE = INDI::Logger::getInstance().addDebugLevel("Scope Verbose", "SCOPE");
}
/**************************************************************************************
** We init our properties here. The only thing we want to init are the Debug controls
***************************************************************************************/
bool SimpleScope::initProperties()
{
// ALWAYS call initProperties() of parent first
INDI::Telescope::initProperties();
// Add Debug control so end user can turn debugging/logging on and off
addDebugControl();
// Enable simulation mode so that serial connection in INDI::Telescope does not try
// to attempt to perform a physical connection to the serial port.
setSimulation(true);
// Set telescope capabilities. 0 is for the the number of slew rates that we support. We have none for this simple driver.
SetTelescopeCapability(TELESCOPE_CAN_GOTO | TELESCOPE_CAN_ABORT, 0);
return true;
}
/**************************************************************************************
** INDI is asking us to check communication with the device via a handshake
***************************************************************************************/
bool SimpleScope::Handshake()
{
// When communicating with a real mount we check here if commands are received
// and acknolowedged by the mount. For SimpleScope we simply return true.
return true;
}
/**************************************************************************************
** INDI is asking us for our default device name
***************************************************************************************/
const char *SimpleScope::getDefaultName()
{
return "Simple Scope";
}
/**************************************************************************************
** Client is asking us to slew to a new position
***************************************************************************************/
bool SimpleScope::Goto(double ra, double dec)
{
targetRA = ra;
targetDEC = dec;
char RAStr[64] = {0}, DecStr[64] = {0};
// Parse the RA/DEC into strings
fs_sexa(RAStr, targetRA, 2, 3600);
fs_sexa(DecStr, targetDEC, 2, 3600);
// Mark state as slewing
TrackState = SCOPE_SLEWING;
// Inform client we are slewing to a new position
LOGF_INFO("Slewing to RA: %s - DEC: %s", RAStr, DecStr);
// Success!
return true;
}
/**************************************************************************************
** Client is asking us to abort our motion
***************************************************************************************/
bool SimpleScope::Abort()
{
return true;
}
/**************************************************************************************
** Client is asking us to report telescope status
***************************************************************************************/
bool SimpleScope::ReadScopeStatus()
{
static struct timeval ltv
{
0, 0
};
struct timeval tv
{
0, 0
};
double dt = 0, da_ra = 0, da_dec = 0, dx = 0, dy = 0;
int nlocked;
/* update elapsed time since last poll, don't presume exactly POLLMS */
gettimeofday(&tv, nullptr);
if (ltv.tv_sec == 0 && ltv.tv_usec == 0)
ltv = tv;
dt = tv.tv_sec - ltv.tv_sec + (tv.tv_usec - ltv.tv_usec) / 1e6;
ltv = tv;
// Calculate how much we moved since last time
da_ra = SLEW_RATE * dt;
da_dec = SLEW_RATE * dt;
/* Process per current state. We check the state of EQUATORIAL_EOD_COORDS_REQUEST and act accordingly */
switch (TrackState)
{
case SCOPE_SLEWING:
// Wait until we are "locked" into position for both RA & DEC axis
nlocked = 0;
// Calculate diff in RA
dx = targetRA - currentRA;
// If diff is very small i.e. smaller than how much we changed since last time then we reached target RA.
if (fabs(dx) * 15. <= da_ra)
{
currentRA = targetRA;
nlocked++;
}
// Otherwise increase RA
else if (dx > 0)
currentRA += da_ra / 15.;
// Otherwise decrease RA
else
currentRA -= da_ra / 15.;
// Calculate diff in DEC
dy = targetDEC - currentDEC;
// If diff is very small i.e. smaller than how much we changed since last time then we reached target DEC.
if (fabs(dy) <= da_dec)
{
currentDEC = targetDEC;
nlocked++;
}
// Otherwise increase DEC
else if (dy > 0)
currentDEC += da_dec;
// Otherwise decrease DEC
else
currentDEC -= da_dec;
// Let's check if we reached position for both RA/DEC
if (nlocked == 2)
{
// Let's set state to TRACKING
TrackState = SCOPE_TRACKING;
LOG_INFO("Telescope slew is complete. Tracking...");
}
break;
default:
break;
}
char RAStr[64] = {0}, DecStr[64] = {0};
// Parse the RA/DEC into strings
fs_sexa(RAStr, currentRA, 2, 3600);
fs_sexa(DecStr, currentDEC, 2, 3600);
DEBUGF(DBG_SCOPE, "Current RA: %s Current DEC: %s", RAStr, DecStr);
NewRaDec(currentRA, currentDEC);
return true;
}
Code Explanation
This driver simulates a simple telescope that can perform GOTO operations. Let’s break down the key components:
-
Class Definition: The
SimpleScope
class inherits fromINDI::Telescope
, which provides the basic functionality required for telescope devices. -
Properties Initialization:
- In
initProperties()
, we initialize the telescope properties and set the telescope capabilities. - We enable simulation mode to avoid attempting physical connections.
- We set the telescope capabilities to indicate it can perform GOTO operations and abort slews.
- In
-
GOTO Implementation:
- The
Goto(double ra, double dec)
function is called when a client requests a slew to a new position. - It stores the target coordinates and sets the telescope state to
SCOPE_SLEWING
.
- The
-
Status Reporting:
- The
ReadScopeStatus()
function is called periodically to update the telescope status. - It simulates the telescope movement by gradually changing the current coordinates towards the target coordinates.
- When the telescope reaches the target position, it changes the state to
SCOPE_TRACKING
.
- The
-
Debugging:
- We create a custom debug channel (
DBG_SCOPE
) for verbose telescope status logging. - This allows users to enable detailed logging specifically for telescope operations.
- We create a custom debug channel (
Running the Tutorial
To run this tutorial:
-
Build the tutorial using CMake:
cd /path/to/indi/build make
-
Run the INDI server with the tutorial driver:
indiserver -v ./tutorial_two
-
Connect to the server using an INDI client (like KStars/EKOS or INDI Control Panel).
-
You should see the “Simple Scope” in the client, and you can connect to it and send GOTO commands.
Next Steps
This tutorial demonstrates a simple telescope simulator. In Tutorial Three, we’ll build a CCD simulator that can generate and transmit FITS images.