Implementing the Telescope Interface
This guide provides a comprehensive overview of implementing the telescope interface in INDI drivers. It covers the basic structure of a telescope driver, how to implement the required methods, and how to handle device-specific functionality.
Introduction to the Telescope Interface
The telescope interface in INDI is designed for telescope mounts and other pointing devices. It provides a standardized way for clients to control telescope mounts, including slewing to coordinates, tracking celestial objects, and controlling various mount features.
The telescope interface is implemented by inheriting from the INDI::Telescope
base class, which provides a set of standard properties and methods for controlling telescope mounts. By implementing this interface, your driver can be used with any INDI client that supports telescope devices.
Prerequisites
Before implementing the telescope interface, you should have:
- Basic knowledge of C++ programming
- Understanding of the INDI protocol and architecture
- Familiarity with the device’s communication protocol
- Development environment set up (compiler, build tools, etc.)
- INDI library installed
Telescope Interface Structure
The telescope interface consists of several key components:
- Base Class:
INDI::Telescope
is the base class for all telescope drivers. - Standard Properties: A set of standard properties for controlling telescope mounts.
- Virtual Methods: A set of virtual methods that must be implemented by the driver.
- Helper Methods: A set of helper methods for common telescope operations.
Base Class
The INDI::Telescope
base class inherits from INDI::DefaultDevice
and provides additional functionality specific to telescope mounts. It defines standard properties for coordinate control, motion control, tracking, and more.
Standard Properties
The telescope interface defines several standard properties:
- EQUATORIAL_EOD_COORD: Controls the equatorial coordinates (right ascension and declination).
- TARGET_EOD_COORD: Sets the target equatorial coordinates.
- HORIZONTAL_COORD: Controls the horizontal coordinates (altitude and azimuth).
- TELESCOPE_ABORT_MOTION: Aborts the current motion.
- TELESCOPE_MOTION_NS: Controls the north/south motion.
- TELESCOPE_MOTION_WE: Controls the west/east motion.
- TELESCOPE_PARK: Parks the telescope.
- TELESCOPE_PARK_POSITION: Sets the park position.
- TELESCOPE_PARK_OPTION: Sets the park options.
- TELESCOPE_TRACK_MODE: Controls the tracking mode.
- TELESCOPE_TRACK_STATE: Controls the tracking state.
- TELESCOPE_TRACK_RATE: Controls the tracking rate.
- TELESCOPE_INFO: Provides information about the telescope.
- TELESCOPE_PIER_SIDE: Indicates the pier side.
Virtual Methods
The telescope interface defines several virtual methods that must be implemented by the driver:
- MoveNS: Moves the telescope in the north/south direction.
- MoveWE: Moves the telescope in the west/east direction.
- Abort: Aborts the current motion.
- Park: Parks the telescope.
- UnPark: Unparks the telescope.
- SetTrackMode: Sets the tracking mode.
- SetTrackEnabled: Enables or disables tracking.
- SetTrackRate: Sets the tracking rate.
- Goto: Slews the telescope to the specified coordinates.
- Sync: Synchronizes the telescope’s internal coordinates with the specified coordinates.
- UpdateLocation: Updates the telescope’s location.
- UpdateTime: Updates the telescope’s time.
Helper Methods
The telescope interface provides several helper methods for common telescope operations:
- SetTelescopeCapability: Sets the telescope capabilities.
- SetParkDataType: Sets the park data type.
- SetDefaultParkPosition: Sets the default park position.
- InitPark: Initializes the park data.
- SetParkPosition: Sets the park position.
- GetParkPosition: Gets the park position.
- IsLocked: Checks if the telescope is locked.
- SetTrackEnabled: Sets the tracking state.
- GetTrackEnabled: Gets the tracking state.
- SetTrackMode: Sets the tracking mode.
- GetTrackMode: Gets the tracking mode.
- SetTrackRate: Sets the tracking rate.
- GetTrackRate: Gets the tracking rate.
- SetPierSide: Sets the pier side.
- GetPierSide: Gets the pier side.
Implementing a Basic Telescope Driver
Let’s create a simple INDI driver for a hypothetical telescope mount called “MyMount”. This mount has a simple serial interface and supports basic commands for slewing, tracking, and parking.
Step 1: Create the Header File
Create a file named mymountdriver.h
with the following content:
#pragma once
#include <inditelescope.h>
class MyMountDriver : public INDI::Telescope
{
public:
MyMountDriver();
virtual ~MyMountDriver() = default;
// DefaultDevice overrides
virtual const char *getDefaultName() override;
virtual bool initProperties() override;
virtual bool updateProperties() override;
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override;
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override;
// Telescope specific overrides
virtual bool Abort() override;
virtual bool Park() override;
virtual bool UnPark() override;
virtual bool SetTrackMode(uint8_t mode) override;
virtual bool SetTrackEnabled(bool enabled) override;
virtual bool SetTrackRate(double raRate, double deRate) override;
virtual bool Goto(double ra, double dec) override;
virtual bool Sync(double ra, double dec) override;
virtual bool MoveNS(INDI_DIR_NS dir, TelescopeMotionCommand command) override;
virtual bool MoveWE(INDI_DIR_WE dir, TelescopeMotionCommand command) override;
virtual bool updateLocation(double latitude, double longitude, double elevation) override;
virtual bool updateTime(ln_date *utc, double utc_offset) override;
protected:
// Connection overrides
virtual bool Connect() override;
virtual bool Disconnect() override;
// Periodic updates
virtual void TimerHit() override;
// Helpers
bool sendCommand(const char *cmd, char *res = nullptr, int reslen = 0);
bool readResponse(char *res, int reslen);
bool getCoords(double *ra, double *dec);
bool setCoords(double ra, double dec);
bool isSlewing();
bool isParked();
bool isTracking();
private:
// Device handle
int PortFD = -1;
// Current state
bool Slewing = false;
bool Tracking = false;
double CurrentRA = 0;
double CurrentDEC = 0;
double TargetRA = 0;
double TargetDEC = 0;
};
Step 2: Create the Implementation File
Create a file named mymountdriver.cpp
with the following content:
#include "mymountdriver.h"
#include <memory>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <connectionplugins/connectionserial.h>
#include <libnova/julian_day.h>
#include <libnova/sidereal_time.h>
// We declare an auto pointer to MyMountDriver
static std::unique_ptr<MyMountDriver> mymount(new MyMountDriver());
MyMountDriver::MyMountDriver()
{
// Set the driver version
setVersion(1, 0);
// Set the telescope capabilities
SetTelescopeCapability(
TELESCOPE_CAN_GOTO |
TELESCOPE_CAN_SYNC |
TELESCOPE_CAN_PARK |
TELESCOPE_CAN_ABORT |
TELESCOPE_HAS_TIME |
TELESCOPE_HAS_LOCATION |
TELESCOPE_HAS_TRACK_MODE |
TELESCOPE_CAN_CONTROL_TRACK |
TELESCOPE_HAS_TRACK_RATE,
4); // 4 slew rates
}
const char *MyMountDriver::getDefaultName()
{
return "My Mount";
}
bool MyMountDriver::initProperties()
{
// Initialize the parent's properties
INDI::Telescope::initProperties();
// Add debug, simulation, and configuration controls
addAuxControls();
// Initialize park data
InitPark();
return true;
}
bool MyMountDriver::updateProperties()
{
// Call the parent's updateProperties
INDI::Telescope::updateProperties();
if (isConnected())
{
// Define properties when connected
// These are already defined by the parent class
}
else
{
// Delete properties when disconnected
// These are already deleted by the parent class
}
return true;
}
bool MyMountDriver::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
{
// Check if the message is for this device
if (!strcmp(dev, getDeviceName()))
{
// Handle custom number properties here
}
// If the message is not for this device or property, call the parent's ISNewNumber
return INDI::Telescope::ISNewNumber(dev, name, values, names, n);
}
bool MyMountDriver::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
{
// Check if the message is for this device
if (!strcmp(dev, getDeviceName()))
{
// Handle custom switch properties here
}
// If the message is not for this device or property, call the parent's ISNewSwitch
return INDI::Telescope::ISNewSwitch(dev, name, states, names, n);
}
bool MyMountDriver::Connect()
{
// Call the parent's Connect method
bool result = INDI::Telescope::Connect();
if (result)
{
// Get the file descriptor for the serial port
PortFD = serialConnection->getPortFD();
// Send a test command to verify the connection
if (!sendCommand("PING\r\n"))
{
LOG_ERROR("Failed to communicate with the mount");
return false;
}
// Get the current coordinates
if (!getCoords(&CurrentRA, &CurrentDEC))
{
LOG_ERROR("Failed to get coordinates from the mount");
return false;
}
// Set the initial target coordinates to the current coordinates
TargetRA = CurrentRA;
TargetDEC = CurrentDEC;
// Check if the mount is parked
SetParked(isParked());
// Check if the mount is tracking
Tracking = isTracking();
TrackState = Tracking ? SCOPE_TRACKING : SCOPE_IDLE;
// Start the timer
SetTimer(POLLMS);
LOG_INFO("Mount connected successfully");
}
return result;
}
bool MyMountDriver::Disconnect()
{
// Close the serial port
if (PortFD > 0)
{
close(PortFD);
PortFD = -1;
}
// Call the parent's Disconnect method
return INDI::Telescope::Disconnect();
}
bool MyMountDriver::Abort()
{
// Send the abort command to the mount
if (!sendCommand("ABORT\r\n"))
{
LOG_ERROR("Failed to abort mount motion");
return false;
}
// Update the state
Slewing = false;
TrackState = SCOPE_IDLE;
LOG_INFO("Mount motion aborted");
return true;
}
bool MyMountDriver::Park()
{
// Check if we're already parked
if (isParked())
{
LOG_INFO("Mount is already parked");
return true;
}
// Send the park command to the mount
if (!sendCommand("PARK\r\n"))
{
LOG_ERROR("Failed to park the mount");
return false;
}
// Update the state
Slewing = true;
TrackState = SCOPE_PARKING;
LOG_INFO("Parking mount...");
return true;
}
bool MyMountDriver::UnPark()
{
// Check if we're already unparked
if (!isParked())
{
LOG_INFO("Mount is already unparked");
return true;
}
// Send the unpark command to the mount
if (!sendCommand("UNPARK\r\n"))
{
LOG_ERROR("Failed to unpark the mount");
return false;
}
// Update the state
SetParked(false);
TrackState = SCOPE_IDLE;
LOG_INFO("Mount unparked");
return true;
}
bool MyMountDriver::SetTrackMode(uint8_t mode)
{
// Send the track mode command to the mount
char cmd[32];
snprintf(cmd, sizeof(cmd), "TRACK_MODE %d\r\n", mode);
if (!sendCommand(cmd))
{
LOG_ERROR("Failed to set track mode");
return false;
}
// Update the state
LOG_INFO("Track mode set");
return true;
}
bool MyMountDriver::SetTrackEnabled(bool enabled)
{
// Send the track state command to the mount
const char *cmd = enabled ? "TRACK_ON\r\n" : "TRACK_OFF\r\n";
if (!sendCommand(cmd))
{
LOG_ERROR("Failed to set track state");
return false;
}
// Update the state
Tracking = enabled;
TrackState = enabled ? SCOPE_TRACKING : SCOPE_IDLE;
LOG_INFO("Track state set");
return true;
}
bool MyMountDriver::SetTrackRate(double raRate, double deRate)
{
// Send the track rate command to the mount
char cmd[32];
snprintf(cmd, sizeof(cmd), "TRACK_RATE %.6f %.6f\r\n", raRate, deRate);
if (!sendCommand(cmd))
{
LOG_ERROR("Failed to set track rate");
return false;
}
// Update the state
LOG_INFO("Track rate set");
return true;
}
bool MyMountDriver::Goto(double ra, double dec)
{
// Check if we're already slewing
if (Slewing)
{
LOG_ERROR("Mount is already slewing");
return false;
}
// Send the goto command to the mount
char cmd[32];
snprintf(cmd, sizeof(cmd), "GOTO %.6f %.6f\r\n", ra, dec);
if (!sendCommand(cmd))
{
LOG_ERROR("Failed to slew the mount");
return false;
}
// Update the state
TargetRA = ra;
TargetDEC = dec;
Slewing = true;
TrackState = SCOPE_SLEWING;
LOGF_INFO("Slewing to RA: %.6f - DEC: %.6f", ra, dec);
return true;
}
bool MyMountDriver::Sync(double ra, double dec)
{
// Send the sync command to the mount
char cmd[32];
snprintf(cmd, sizeof(cmd), "SYNC %.6f %.6f\r\n", ra, dec);
if (!sendCommand(cmd))
{
LOG_ERROR("Failed to sync the mount");
return false;
}
// Update the state
CurrentRA = ra;
CurrentDEC = dec;
TargetRA = ra;
TargetDEC = dec;
LOGF_INFO("Synced to RA: %.6f - DEC: %.6f", ra, dec);
return true;
}
bool MyMountDriver::MoveNS(INDI_DIR_NS dir, TelescopeMotionCommand command)
{
// Send the move command to the mount
char cmd[32];
if (command == MOTION_START)
{
snprintf(cmd, sizeof(cmd), "MOVE_NS %d\r\n", (dir == DIRECTION_NORTH) ? 1 : 0);
}
else
{
snprintf(cmd, sizeof(cmd), "STOP_NS\r\n");
}
if (!sendCommand(cmd))
{
LOG_ERROR("Failed to move the mount");
return false;
}
// Update the state
LOGF_INFO("Moving %s", (dir == DIRECTION_NORTH) ? "North" : "South");
return true;
}
bool MyMountDriver::MoveWE(INDI_DIR_WE dir, TelescopeMotionCommand command)
{
// Send the move command to the mount
char cmd[32];
if (command == MOTION_START)
{
snprintf(cmd, sizeof(cmd), "MOVE_WE %d\r\n", (dir == DIRECTION_WEST) ? 1 : 0);
}
else
{
snprintf(cmd, sizeof(cmd), "STOP_WE\r\n");
}
if (!sendCommand(cmd))
{
LOG_ERROR("Failed to move the mount");
return false;
}
// Update the state
LOGF_INFO("Moving %s", (dir == DIRECTION_WEST) ? "West" : "East");
return true;
}
bool MyMountDriver::updateLocation(double latitude, double longitude, double elevation)
{
// Send the location command to the mount
char cmd[64];
snprintf(cmd, sizeof(cmd), "LOCATION %.6f %.6f %.2f\r\n", latitude, longitude, elevation);
if (!sendCommand(cmd))
{
LOG_ERROR("Failed to update location");
return false;
}
// Update the state
LOGF_INFO("Location updated: Lat %.6f, Lon %.6f, Elev %.2f", latitude, longitude, elevation);
return true;
}
bool MyMountDriver::updateTime(ln_date *utc, double utc_offset)
{
// Convert the date to a Julian day
double jd = ln_get_julian_day(utc);
// Send the time command to the mount
char cmd[64];
snprintf(cmd, sizeof(cmd), "TIME %.6f %.2f\r\n", jd, utc_offset);
if (!sendCommand(cmd))
{
LOG_ERROR("Failed to update time");
return false;
}
// Update the state
LOGF_INFO("Time updated: JD %.6f, Offset %.2f", jd, utc_offset);
return true;
}
void MyMountDriver::TimerHit()
{
// Check if we're connected
if (!isConnected())
return;
// Get the current coordinates
double newRA, newDEC;
if (getCoords(&newRA, &newDEC))
{
// Update the current coordinates
CurrentRA = newRA;
CurrentDEC = newDEC;
// Update the telescope state
NewRaDec(CurrentRA, CurrentDEC);
}
// Check if we're slewing
if (Slewing)
{
// Check if the slew is complete
if (!isSlewing())
{
// Slew is complete
Slewing = false;
if (TrackState == SCOPE_SLEWING)
TrackState = SCOPE_TRACKING;
else if (TrackState == SCOPE_PARKING)
SetParked(true);
LOG_INFO("Slew complete");
}
}
// Check if we're tracking
if (TrackState == SCOPE_TRACKING)
{
// Check if tracking is still enabled
if (!isTracking())
{
// Tracking is disabled
Tracking = false;
TrackState = SCOPE_IDLE;
LOG_INFO("Tracking stopped");
}
}
// Set the timer for the next update
SetTimer(POLLMS);
}
bool MyMountDriver::sendCommand(const char *cmd, char *res, int reslen)
{
// Check if the port is open
if (PortFD < 0)
{
LOG_ERROR("Serial port not open");
return false;
}
// Write the command
int nbytes_written = write(PortFD, cmd, strlen(cmd));
if (nbytes_written < 0)
{
LOGF_ERROR("Error writing to mount: %s", strerror(errno));
return false;
}
// If no response is expected, return success
if (res == nullptr || reslen <= 0)
return true;
// Read the response
if (!readResponse(res, reslen))
{
LOG_ERROR("Error reading response from mount");
return false;
}
return true;
}
bool MyMountDriver::readResponse(char *res, int reslen)
{
// Check if the port is open
if (PortFD < 0)
{
LOG_ERROR("Serial port not open");
return false;
}
// Read the response
int nbytes_read = read(PortFD, res, reslen - 1);
if (nbytes_read < 0)
{
LOGF_ERROR("Error reading from mount: %s", strerror(errno));
return false;
}
// Null-terminate the response
res[nbytes_read] = '\0';
return true;
}
bool MyMountDriver::getCoords(double *ra, double *dec)
{
// Send the get coordinates command to the mount
char res[32];
if (!sendCommand("GET_COORDS\r\n", res, sizeof(res)))
{
LOG_ERROR("Failed to get coordinates from the mount");
return false;
}
// Parse the response
if (sscanf(res, "COORDS %lf %lf", ra, dec) != 2)
{
LOGF_ERROR("Failed to parse coordinates: %s", res);
return false;
}
return true;
}
bool MyMountDriver::setCoords(double ra, double dec)
{
// Send the set coordinates command to the mount
char cmd[32];
snprintf(cmd, sizeof(cmd), "SET_COORDS %.6f %.6f\r\n", ra, dec);
if (!sendCommand(cmd))
{
LOG_ERROR("Failed to set coordinates");
return false;
}
return true;
}
bool MyMountDriver::isSlewing()
{
// Send the get status command to the mount
char res[32];
if (!sendCommand("GET_STATUS\r\n", res, sizeof(res)))
{
LOG_ERROR("Failed to get status from the mount");
return false;
}
// Parse the response
int slewing = 0;
if (sscanf(res, "STATUS %d", &slewing) != 1)
{
LOGF_ERROR("Failed to parse status: %s", res);
return false;
}
return slewing != 0;
}
bool MyMountDriver::isParked()
{
// Send the get park status command to the mount
char res[32];
if (!sendCommand("GET_PARK\r\n", res, sizeof(res)))
{
LOG_ERROR("Failed to get park status from the mount");
return false;
}
// Parse the response
int parked = 0;
if (sscanf(res, "PARK %d", &parked) != 1)
{
LOGF_ERROR("Failed to parse park status: %s", res);
return false;
}
return parked != 0;
}
bool MyMountDriver::isTracking()
{
// Send the get tracking status command to the mount
char res[32];
if (!sendCommand("GET_TRACK\r\n", res, sizeof(res)))
{
LOG_ERROR("Failed to get tracking status from the mount");
return false;
}
// Parse the response
int tracking = 0;
if (sscanf(res, "TRACK %d", &tracking) != 1)
{
LOGF_ERROR("Failed to parse tracking status: %s", res);
return false;
}
return tracking != 0;
}
Step 3: Create the Main File
Create a file named main.cpp
with the following content:
#include "mymountdriver.h"
int main(int argc, char *argv[])
{
// Create and initialize the driver
std::unique_ptr<MyMountDriver> mymount(new MyMountDriver());
// Set the driver version
mymount->setVersion(1, 0);
// Start the driver
mymount->ISGetProperties(nullptr);
// Run the driver
return mymount->run();
}
Step 4: Create the CMakeLists.txt File
Create a file named CMakeLists.txt
with the following content:
cmake_minimum_required(VERSION 3.0)
project(indi-mymount CXX C)
include(GNUInstallDirs)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules/")
find_package(INDI REQUIRED)
find_package(Nova REQUIRED)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
include_directories(${INDI_INCLUDE_DIR})
include_directories(${NOVA_INCLUDE_DIR})
include(CMakeCommon)
add_executable(indi_mymount mymountdriver.cpp main.cpp)
target_link_libraries(indi_mymount ${INDI_LIBRARIES} ${NOVA_LIBRARIES})
install(TARGETS indi_mymount RUNTIME DESTINATION bin)
Step 5: Create the XML File
Create a file named indi_mymount.xml
with the following content:
<?xml version="1.0" encoding="UTF-8"?>
<driversList>
<devGroup group="Telescopes">
<device label="My Mount" manufacturer="INDI">
<driver name="My Mount">indi_mymount</driver>
<version>1.0</version>
</device>
</devGroup>
</driversList>
Step 6: Build the Driver
To build the driver, create a build
directory and run CMake:
mkdir build
cd build
cmake ..
make
Step 7: Install the Driver
To install the driver, run:
sudo make install
This will install the driver executable to /usr/bin
and the XML file to /usr/share/indi
.
Step 8: Test the Driver
To test the driver, start the INDI server with your driver:
indiserver -v indi_mymount
Then, connect to the INDI server using an INDI client, such as the INDI Control Panel:
indi_control_panel
Advanced Topics
Pier Side
The telescope interface includes support for pier side, which indicates which side of the pier the telescope is on. This is important for German equatorial mounts (GEMs), which can point to the same position in the sky from two different physical orientations.
To implement pier side support, you need to set the pier side capability and implement the pier side-specific methods.
To set the pier side capability, use the SetTelescopeCapability
method:
MyMountDriver::MyMountDriver()
{
// Set the driver version
setVersion(1, 0);
// Set the telescope capabilities
SetTelescopeCapability(
TELESCOPE_CAN_GOTO |
TELESCOPE_CAN_SYNC |
TELESCOPE_CAN_PARK |
TELESCOPE_CAN_ABORT |
TELESCOPE_HAS_TIME |
TELESCOPE_HAS_LOCATION |
TELESCOPE_HAS_TRACK_MODE |
TELESCOPE_CAN_CONTROL_TRACK |
TELESCOPE_HAS_TRACK_RATE |
TELESCOPE_HAS_PIER_SIDE,
4); // 4 slew rates
}
To implement the pier side-specific methods, override the GetPierSide
method:
IPState MyMountDriver::GetPierSide()
{
// Send the get pier side command to the mount
char res[32];
if (!sendCommand("GET_PIER_SIDE\r\n", res, sizeof(res)))
{
LOG_ERROR("Failed to get pier side from the mount");
return IPS_ALERT;
}
// Parse the response
int pierSide = 0;
if (sscanf(res, "PIER_SIDE %d", &pierSide) != 1)
{
LOGF_ERROR("Failed to parse pier side: %s", res);
return IPS_ALERT;
}
// Set the pier side
setPierSide(pierSide == 0 ? PIER_EAST : PIER_WEST);
return IPS_OK;
}
Parking
The telescope interface includes support for parking, which allows the telescope to be moved to a safe position when not in use. To implement parking support, you need to set the park capability and implement the park-specific methods.
To set the park capability, use the SetTelescopeCapability
method:
MyMountDriver::MyMountDriver()
{
// Set the driver version
setVersion(1, 0);
// Set the telescope capabilities
SetTelescopeCapability(
TELESCOPE_CAN_GOTO |
TELESCOPE_CAN_SYNC |
TELESCOPE_CAN_PARK |
TELESCOPE_CAN_ABORT |
TELESCOPE_HAS_TIME |
TELESCOPE_HAS_LOCATION |
TELESCOPE_HAS_TRACK_MODE |
TELESCOPE_CAN_CONTROL_TRACK |
TELESCOPE_HAS_TRACK_RATE,
4); // 4 slew rates
}
To implement the park-specific methods, override the Park
and UnPark
methods:
bool MyMountDriver::Park()
{
// Check if we're already parked
if (isParked())
{
LOG_INFO("Mount is already parked");
return true;
}
// Send the park command to the mount
if (!sendCommand("PARK\r\n"))
{
LOG_ERROR("Failed to park the mount");
return false;
}
// Update the state
Slewing = true;
TrackState = SCOPE_PARKING;
LOG_INFO("Parking mount...");
return true;
}
bool MyMountDriver::UnPark()
{
// Check if we're already unparked
if (!isParked())
{
LOG_INFO("Mount is already unparked");
return true;
}
// Send the unpark command to the mount
if (!sendCommand("UNPARK\r\n"))
{
LOG_ERROR("Failed to unpark the mount");
return false;
}
// Update the state
SetParked(false);
TrackState = SCOPE_IDLE;
LOG_INFO("Mount unparked");
return true;
}
Tracking Modes
The telescope interface includes support for different tracking modes, such as sidereal, lunar, and solar. To implement tracking mode support, you need to set the track mode capability and implement the track mode-specific methods.
To set the track mode capability, use the SetTelescopeCapability
method:
MyMountDriver::MyMountDriver()
{
// Set the driver version
setVersion(1, 0);
// Set the telescope capabilities
SetTelescopeCapability(
TELESCOPE_CAN_GOTO |
TELESCOPE_CAN_SYNC |
TELESCOPE_CAN_PARK |
TELESCOPE_CAN_ABORT |
TELESCOPE_HAS_TIME |
TELESCOPE_HAS_LOCATION |
TELESCOPE_HAS_TRACK_MODE |
TELESCOPE_CAN_CONTROL_TRACK |
TELESCOPE_HAS_TRACK_RATE,
4); // 4 slew rates
}
To implement the track mode-specific methods, override the SetTrackMode
method:
bool MyMountDriver::SetTrackMode(uint8_t mode)
{
// Send the track mode command to the mount
char cmd[32];
snprintf(cmd, sizeof(cmd), "TRACK_MODE %d\r\n", mode);
if (!sendCommand(cmd))
{
LOG_ERROR("Failed to set track mode");
return false;
}
// Update the state
LOG_INFO("Track mode set");
return true;
}
Simulation Mode
The telescope interface includes support for simulation mode, which can be used to test the driver without connecting to the actual hardware. To implement simulation mode, check the isSimulation()
flag and provide simulated responses.
bool MyMountDriver::Connect()
{
// Call the parent's Connect method
bool result = INDI::Telescope::Connect();
if (result)
{
// Check if we're in simulation mode
if (isSimulation())
{
LOG_INFO("Simulation mode enabled");
PortFD = 1;
}
else
{
// Get the file descriptor for the serial port
PortFD = serialConnection->getPortFD();
// Send a test command to verify the connection
if (!sendCommand("PING\r\n"))
{
LOG_ERROR("Failed to communicate with the mount");
return false;
}
}
// Get the current coordinates
if (!getCoords(&CurrentRA, &CurrentDEC))
{
LOG_ERROR("Failed to get coordinates from the mount");
return false;
}
// Set the initial target coordinates to the current coordinates
TargetRA = CurrentRA;
TargetDEC = CurrentDEC;
// Check if the mount is parked
SetParked(isParked());
// Check if the mount is tracking
Tracking = isTracking();
TrackState = Tracking ? SCOPE_TRACKING : SCOPE_IDLE;
// Start the timer
SetTimer(POLLMS);
LOG_INFO("Mount connected successfully");
}
return result;
}
bool MyMountDriver::sendCommand(const char *cmd, char *res, int reslen)
{
// Check if we're in simulation mode
if (isSimulation())
{
// Simulate a response
if (res && reslen > 0)
{
if (!strcmp(cmd, "GET_COORDS\r\n"))
snprintf(res, reslen, "COORDS %.6f %.6f\r\n", CurrentRA, CurrentDEC);
else if (!strcmp(cmd, "GET_STATUS\r\n"))
snprintf(res, reslen, "STATUS %d\r\n", Slewing ? 1 : 0);
else if (!strcmp(cmd, "GET_PARK\r\n"))
snprintf(res, reslen, "PARK %d\r\n", isParked() ? 1 : 0);
else if (!strcmp(cmd, "GET_TRACK\r\n"))
snprintf(res, reslen, "TRACK %d\r\n", Tracking ? 1 : 0);
else if (!strcmp(cmd, "PING\r\n"))
snprintf(res, reslen, "PONG\r\n");
else
snprintf(res, reslen, "ERROR\r\n");
}
return true;
}
// Check if the port is open
if (PortFD < 0)
{
LOG_ERROR("Serial port not open");
return false;
}
// Write the command
int nbytes_written = write(PortFD, cmd, strlen(cmd));
if (nbytes_written < 0)
{
LOGF_ERROR("Error writing to mount: %s", strerror(errno));
return false;
}
// If no response is expected, return success
if (res == nullptr || reslen <= 0)
return true;
// Read the response
if (!readResponse(res, reslen))
{
LOG_ERROR("Error reading response from mount");
return false;
}
return true;
}
Custom Properties
The telescope interface allows you to add custom properties to the driver. This can be useful for adding mount-specific functionality that is not covered by the standard properties.
To add a custom property, define the property in the header file and initialize it in the initProperties
method:
// In the header file
private:
// Custom properties
INDI::PropertyNumber CustomRateNP {2};
// In the implementation file
bool MyMountDriver::initProperties()
{
// Initialize the parent's properties
INDI::Telescope::initProperties();
// Initialize custom rate property
CustomRateNP[0].fill("CUSTOM_RATE_RA", "RA Rate (arcsec/s)", "%6.2f", 0, 100, 1, 15.0);
CustomRateNP[1].fill("CUSTOM_RATE_DEC", "DEC Rate (arcsec/s)", "%6.2f", 0, 100, 1, 15.0);
CustomRateNP.fill(getDeviceName(), "CUSTOM_RATE", "Custom Rate", MOTION_TAB, IP_RW, 60, IPS_IDLE);
// Add debug, simulation, and configuration controls
addAuxControls();
// Initialize park data
InitPark();
return true;
}
bool MyMountDriver::updateProperties()
{
// Call the parent's updateProperties
INDI::Telescope::updateProperties();
if (isConnected())
{
// Define custom properties when connected
defineProperty(&CustomRateNP);
}
else
{
// Delete custom properties when disconnected
deleteProperty(CustomRateNP.name);
}
return true;
}
bool MyMountDriver::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
{
// Check if the message is for this device
if (!strcmp(dev, getDeviceName()))
{
// Check if the message is for the custom rate property
if (!strcmp(name, CustomRateNP.name))
{
// Update the property values
CustomRateNP.update(values, names, n);
// Send the custom rate command to the mount
char cmd[32];
snprintf(cmd, sizeof(cmd), "CUSTOM_RATE %.6f %.6f\r\n", CustomRateNP[0].getValue(), CustomRateNP[1].getValue());
if (!sendCommand(cmd))
{
LOG_ERROR("Failed to set custom rate");
CustomRateNP.setState(IPS_ALERT);
CustomRateNP.apply();
return false;
}
// Update the property state
LOG_INFO("Custom rate set");
CustomRateNP.setState(IPS_OK);
CustomRateNP.apply();
return true;
}
}
// If the message is not for this device or property, call the parent's ISNewNumber
return INDI::Telescope::ISNewNumber(dev, name, values, names, n);
}
Best Practices
When implementing the telescope interface, follow these best practices:
- Use the appropriate base class for your device.
- Implement simulation mode to allow testing without hardware.
- Provide informative error messages to help users troubleshoot issues.
- Handle connection and disconnection gracefully to avoid resource leaks.
- Update property states to reflect the current state of the device.
- Use appropriate property types for different kinds of data.
- Follow the INDI naming conventions for properties and elements.
- Document your driver to help users understand how to use it.
- Test your driver thoroughly with different clients and configurations.
Conclusion
Implementing the telescope interface in INDI drivers involves inheriting from the INDI::Telescope
base class, implementing the required methods, and handling device-specific functionality. By following the steps and best practices outlined in this guide, you can create robust and feature-rich telescope drivers for your mounts.
For more information, refer to the INDI Library Documentation and the INDI Driver Development Guide.