INDI modules

This is where it all happens

class IndiModule : public Basemodule, public INDI::BaseClient

A single line, and yet the whole framework is right there: IndiModule is the meeting point between two worlds.

  • Basemodule talks to the user.
  • INDI::BaseClient talks to the hardware.
  • IndiModule doesn’t inherit a bit of each: it truly inherits from both at once, and it is precisely this position, right between the two, that makes it the place to write a device’s actual intelligence — a focuser, a sequencer, a polar alignment module are nothing more than that: translating a user request into a hardware command, and a hardware measurement into user-facing information.

And this is not just a figure of speech: an IndiModule is a full-fledged INDI client, exactly on the same footing as Ekos or any other client. All of INDI::BaseClient’s methods are right there, usable directly (connectServer(), watchDevice(), setBLOBMode()…) — a module is never locked into the giveMeA*()/defineMeAsXxx() helpers seen further below, they’re only a shortcut for the most common cases.

How the INDI client itself works — protocol, device/property lifecycle, BLOBs — is for the INDI development documentation to cover, not this one: here, we only talk about what IndiModule adds on top.

Two layers of callbacks

IndiModule itself intercepts INDI::BaseClient’s raw callbacks (newDevice, newProperty, removeProperty, updateProperty) to do its own internal bookkeeping (refreshing the device list of values, automatic connection at startup…), then calls a public, virtual version meant to be overridden by the concrete module:

newDevice()       (privé, IndiModule)  →  onNewDevice()       (virtuel, à surcharger)
newProperty()     (privé, IndiModule)  →  onNewProperty()     (virtuel, à surcharger)
removeProperty()  (privé, IndiModule)  →  onRemoveProperty()  (virtuel, à surcharger)

updateProperty() is an exception: it’s protected, not private, precisely so that a module needing more direct access (image capture, see below) can override it itself — provided it calls back IndiModule::updateProperty(property) first, or lose the automatic refresh of devices.* on connection.

onUpdateProperty() is enough in the vast majority of cases

Override onUpdateProperty() (public, virtual) rather than updateProperty() (protected) directly — that’s what helloindi and helloindiwrite do below. Only a module that needs to distinguish a property type very early in the chain, like an image capture (BLOB), has a reason to override updateProperty() itself.

The device picker: giveMeADevice()

giveMeADevice("device", "Device", INDI::BaseDevice::GENERAL_INTERFACE);

Adds a selection element to the devices property (created on the fly), whose list of values only offers devices actually present on the INDI bus and exposing the requested interface (GENERAL_INTERFACE, TELESCOPE_INTERFACE, CCD_INTERFACE, FOCUSER_INTERFACE…). The name passed as the first argument ("device" here) is the element’s key within devices — getString("devices", "device") retrieves the device chosen by the user.

A few common standard INDI vectors

The INDI protocol itself remains out of scope (see above), but a few vectors come up in almost every module driving a camera or focuser — no point rediscovering them every time:

NeedVectorElementHow
Move a focuser to an absolute positionABS_FOCUS_POSITIONFOCUS_ABSOLUTE_POSITIONsendModNewNumber() to move there, onUpdateProperty() or getModNumber() to track the current position
Reframe a cameraCCD_FRAMEX/Y/WIDTH/HEIGHTframeSet(device, x, y, w, h) — IndiModule helper, no need for the raw vector
Reset a camera’s frameCCD_FRAME_RESET— (switch)frameReset(device) — IndiModule helper, same
Abort an ongoing exposureCCD_ABORT_EXPOSUREABORTsendModNewSwitch(device, "CCD_ABORT_EXPOSURE", "ABORT", ISS_ON)
abortfocus (defineMeAsFocuser) sends nothing to the device by default

The abortfocus action set up by defineMeAsFocuser() (Ready-made families) stops the sequence on the OST side — the core Focus module never commands FOCUS_ABORT_MOTION on the device, it simply stops sending a new position. Check whether the driver in use needs an explicit abort mid-move.

The five examples

Each example adds a single new concept to the previous one: