Implementing the Output Interface
This guide provides a comprehensive overview of implementing the Output Interface in INDI drivers. It covers the basic structure of an output driver, how to implement the required methods, and how to handle device-specific functionality for digital outputs.
Introduction to the Output Interface
The INDI Output Interface (INDI::OutputInterface) provides a framework for implementing digital boolean output (On/Off) functionality in INDI devices. This is particularly useful for devices like web-enabled output controllers and General Purpose Input/Output (GPIO) systems, where controlling the state of physical outputs (e.g., relays, LEDs) is crucial.
IMPORTANT: The initProperties() method must be called before any other function to properly initialize the output properties.
Prerequisites
Before implementing the Output 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
Output Interface Structure
The Output Interface consists of several key components:
- Base Class:
INDI::OutputInterfaceis the base class for all output drivers. - Output State: Defines the possible states for an output (On/Off).
Base Class
The INDI::OutputInterface base class provides functionality specific to devices that need to control digital outputs. It defines standard properties for output states, labels, and pulse durations.
OutputState Enum
The OutputState enum defines the possible boolean states for an output.
typedef enum
{
Off, /*!< Output is off. For Relays, it is open circuit. */
On, /*!< Output is on. For Relays, it is closed circuit. */
} OutputState;
Off: Indicates the output is in an ‘off’ state (e.g., open circuit for a relay).On: Indicates the output is in an ‘on’ state (e.g., closed circuit for a relay).
Key Methods
A driver implementing the INDI::OutputInterface must override and implement the following virtual methods to control output devices:
virtual bool UpdateDigitalOutputs() = 0;This is a crucial method that your driver must implement. It is responsible for updating the state of all digital outputs from the device or service.- Returns
trueif the operation is successful,falseotherwise. - Note:
UpdateDigitalOutputsshould be called periodically (e.g., in the child’sTimerHitor a custom timer function) or when an interrupt/trigger warrants updating the digital outputs. Only updated properties that had a change in status since the last call should be sent to clients to reduce unnecessary updates. Implementation can be polling or event-driven depending on hardware capabilities.
- Returns
virtual bool CommandOutput(uint32_t index, OutputState command) = 0;This is a crucial method that your driver must implement. It is responsible for sending a command to a specific output.index: The zero-based index of the output to command.command: The desiredOutputState(On or Off).- Returns
trueif the operation is successful,falseotherwise.
The following protected methods are provided by the OutputInterface for managing properties:
explicit OutputInterface(DefaultDevice *defaultDevice);Constructor for theOutputInterface.defaultDevice: A pointer to theDefaultDevicethat owns this interface.
-
~OutputInterface();Destructor for theOutputInterface. void initProperties(const char *groupName, uint8_t outputs, const std::string &prefix = "Output");Initializes the INDI properties related to the Output Interface. It is recommended to call this function within the driver’sinitProperties()method.groupName: Group or tab name to be used to define output properties (e.g., “Outputs”).outputs: Number of outputs.prefix: Prefix used to label outputs (default: “Output”).
bool updateProperties();Defines or deletes output properties based on the connection status of the default device.- Returns
trueif all is OK,falseotherwise.
- Returns
-
bool processSwitch(const char *dev, const char *name, ISState states[], char *names[], int n);Processes incoming client requests for switch properties related to the output interface (e.g., toggling outputs). -
bool processText(const char *dev, const char *name, char *texts[], char *names[], int n);Processes incoming client requests for text properties related to the output interface (e.g., output labels). -
bool processNumber(const char *dev, const char *name, double values[], char *names[], int n);Processes incoming client requests for number properties related to the output interface (e.g., pulse duration). bool saveConfigItems(FILE *fp);Saves output labels in the configuration file.fp: Pointer to the configuration file.- Always returns
true.
Member Variables
The INDI::OutputInterface class includes the following important member variables:
-
std::vector<INDI::PropertySwitch> DigitalOutputsSP;A vector ofPropertySwitchobjects representing the digital outputs. -
INDI::PropertyText DigitalOutputLabelsTP;APropertyTextobject used to manage labels for digital outputs. -
std::vector<INDI::PropertyNumber> PulseDurationNP;A vector ofPropertyNumberobjects for setting pulse durations for outputs. -
bool m_DigitalOutputLabelsConfig;Indicates whether digital output labels were successfully loaded from the configuration file. -
DefaultDevice *m_defaultDevice;A pointer to theDefaultDevicethat owns this interface.
Example Implementation
Here’s a simplified example of how a driver might implement the INDI::OutputInterface. This example focuses on the core structure and omits complex hardware interaction details for clarity.
```cpp
#include “indibase.h”
#include “indioutputinterface.h”
#include
// Forward declaration of a dummy DefaultDevice for the example class MyOutputDevice;
class MyOutputDevice : public INDI::DefaultDevice, public INDI::OutputInterface { public: MyOutputDevice() : INDI::DefaultDevice(), INDI::OutputInterface(this) { setDriverInterface(GENERAL_INTERFACE); // Or a more specific interface if applicable }
virtual const char *getDefaultName() override
{
return "MyOutputDevice";
}
virtual bool initProperties() override
{
INDI::DefaultDevice::initProperties();
// Initialize Output properties. "Outputs" for the group name, 4 outputs.
// Using default prefix "Output".
initProperties("Outputs", 4);
return true;
}
virtual bool updateProperties() override
{
INDI::DefaultDevice::updateProperties();
if (isConnected())
{
// Define Output properties when connected
INDI::OutputInterface::updateProperties();
// Start a timer to periodically update output data (if needed for status feedback)
SetTimer(getCurrentPollingPeriod());
}
else
{
// Delete Output properties when disconnected
INDI::OutputInterface::updateProperties();
}
return true;
}
// Implement the crucial UpdateDigitalOutputs method
virtual bool UpdateDigitalOutputs() override
{
// In a real driver, you would read the actual state of the outputs from hardware
// and update the DigitalOutputsSP vector accordingly.
// For this example, we'll just log the current states.
std::cout << "Updating Digital Outputs status:" << std::endl;
for (size_t i = 0; i < DigitalOutputsSP.size(); ++i)
{
std::cout << " Output #" << (i + 1) << ": "
<< (DigitalOutputsSP[i].getState() == INDI::OutputInterface::On ? "On" : "Off")
<< std::endl;
// If the state changed, you would call IDSetSwitch(&DigitalOutputsSP[i], nullptr);
}
return true; // Indicate successful update
}
// Implement the crucial CommandOutput method
virtual bool CommandOutput(uint32_t index, OutputState command) override
{
if (index >= DigitalOutputsSP.size())
{
IDLog("CommandOutput: Invalid output index %u", index);
return false;
}
// In a real driver, you would send the command to the hardware
// For this example, we'll just simulate the command and update the property.
DigitalOutputsSP[index].setState(command);
IDSetSwitch(&DigitalOutputsSP[index], nullptr); // Notify clients of the new state
std::cout << "Commanded Output #" << (index + 1) << " to "
<< (command == INDI::OutputInterface::On ? "On" : "Off") << std::endl;
return true; // Indicate successful command
}
// Other INDI::DefaultDevice methods would also be implemented here
// e.g., ISNewSwitch, ISNewNumber, ISNewText for processing client requests
// You would typically forward relevant properties to OutputInterface::processSwitch/processText/processNumber
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
{
if (dev && !strcmp(dev, getDeviceName()))
{
if (INDI::OutputInterface::processSwitch(dev, name, states, names, n))
return true;
}
return INDI::DefaultDevice::ISNewSwitch(dev, name, states, names, n);
}
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
{
if (dev && !strcmp(dev, getDeviceName()))
{
if (INDI::OutputInterface::processText(dev, name, texts, names, n))
return true;
}
return INDI::DefaultDevice::ISNewText(dev, name, texts, names, n);
}
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
{
if (dev && !strcmp(dev, getDeviceName()))
{
if (INDI::OutputInterface::processNumber(dev, name, values, names, n))
return true;
}
return INDI::DefaultDevice::ISNewNumber(dev, name, values, names, n);
}
// TimerHit for periodic updates
virtual void TimerHit() override
{
if (!isConnected())
{
SetTimer(getCurrentPollingPeriod());
return;
}
// Update digital outputs (e.g., read their actual state from hardware)
UpdateDigitalOutputs();
SetTimer(getCurrentPollingPeriod());
} };
// This is typically how an INDI driver is instantiated // static MyOutputDevice myOutputDevice;