INDI modules
This is where it all happens
class IndiModule : public Basemodule, public INDI::BaseClientA single line, and yet the whole framework is right there: IndiModule is the meeting point between two worlds.
Basemoduletalks to the user.INDI::BaseClienttalks to the hardware.IndiModuledoesn’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.
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:
| Need | Vector | Element | How |
|---|---|---|---|
| Move a focuser to an absolute position | ABS_FOCUS_POSITION | FOCUS_ABSOLUTE_POSITION | sendModNewNumber() to move there, onUpdateProperty() or getModNumber() to track the current position |
| Reframe a camera | CCD_FRAME | X/Y/WIDTH/HEIGHT | frameSet(device, x, y, w, h) — IndiModule helper, no need for the raw vector |
| Reset a camera’s frame | CCD_FRAME_RESET | — (switch) | frameReset(device) — IndiModule helper, same |
| Abort an ongoing exposure | CCD_ABORT_EXPOSURE | ABORT | sendModNewSwitch(device, "CCD_ABORT_EXPOSURE", "ABORT", ISS_ON) |
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:
- Connecting to a device
giveMeADevice, automatic connection, reflecting CONNECTION state in a property
- Mirroring a device property
Reading and writing a real INDI property in both directions
- Capturing an image
giveMeAnImage, receiving a BLOB, and the loadImageFromBlob/refreshPreview chain
- Acting on the device
giveMeAnActions, sending a standard INDI switch to the device rather than the module's internal state
- Ready-made families
defineMeAsFocuser() and the other defineMeAsXxx(): the ready-made version of declareFamily()