Ready-made families

Example module: helloindifamily — a real focuser module, usable as a slave by hellomaster or by the sequencer.

Master/slave relationship showed declareFamily("hello") — an arbitrary family, specific to that pair of examples. For the common roles of an INDI-driven module, IndiModule offers a ready-made version that does everything at once:

HelloIndiFamily::HelloIndiFamily(...)
    : IndiModule(...)
{
    giveMeADevice("focuser", "Focuser", INDI::BaseDevice::FOCUSER_INTERFACE);
    defineMeAsFocuser();
}

A single call, defineMeAsFocuser():

  • declares the focus family (same mechanism as declareFamily());
  • builds the actions property, with its AtMostOne rule already set between abortfocus/autofocus/loop — nothing to declare in JSON. autofocus is the complete sequence (moves + measurements up to the optimum); loop is different — see the box below;
  • also builds parms and signals, but empty beyond their generic shell — see below;
  • internally calls defineMeAsImager(), which adds, on top, optics capability (giveMeAnOptic()) and image capability (giveMeAnImage(), see Capturing an image) — a focuser module therefore already has image/imgparms available with nothing more to ask for;
  • makes isFocuser() true from that point on.

defineMeAsGuider(), defineMeAsSequencer(), defineMeAsImager(), defineMeAsNavigator() and defineMeAsPlanner() follow the same principle for the other roles; each has its associated is...() (isGuider(), isSequencer()…).

parms and signals: a shell, not content

parms is created empty (giveMeAParms()): a real focuser must add, itself, with addElt() (Creating properties dynamically), the elements specific to it — step size, backlash, delay between measurements… nothing is provided by default.

signals (not state — it’s the same name as the one created by giveMeAState(), seen in Master/slave relationship) arrives with a fixed set of generic elements: state (light), statedescription, event, eventdescription, and three “mailbox” elements with no imposed semantics — val_num (float), val_int, val_str — which a module can repurpose to expose a one-off value without creating a dedicated property.

loop: a continuous measurement, not a repeating autofocus

autofocus and loop do not do the same thing, and nothing but the action’s name says so. In the real core Focus module, loop never moves the focuser itself — it’s a repeated measurement of the sharpness metric (HFR or equivalent) with no movement at all, meant for the user to turn the focuser by hand and watch the metric react live. Both actions run until abortfocus, but autofocus drives the device, loop only observes it.

void HelloIndiFamily::onExternalEvent(OST::ExtEvent event)
{
    if (event.ev != OST::ExtEvType::SV || event.prpkey != "actions") return;

    // abortfocus/autofocus/loop sont déjà mutuellement exclusifs
    // (AtMostOne, posé par defineMeAsFocuser()) - rien à gérer pour ça.
    if (event.eltkey == "autofocus")
    {
        setStateEvent(OST::Busy, "focusing", "autofocus", "");
        QTimer::singleShot(2000, this, [this]()
        {
            setStateEvent(OST::Ok, "ready", "autofocus", "");
            getEltBool("actions", "autofocus")->setValue(false, true);
        });
    }

    if (event.eltkey == "abortfocus")
    {
        setStateEvent(OST::Ok, "ready", "abortfocus", "");
    }
}

The module just has to react to the actions already declared — setStateEvent() (seen in Master/slave relationship) remains the only way to move its state forward.

Locking a property during a long-running operation

During a real autofocus sequence, nothing stops the user from editing parms in the middle of it — unless explicitly decided otherwise. getProperty(name)->disable() locks the whole property to read-only on the client side (greyed out on the interface), ->enable() unlocks it:

getProperty("parms")->disable();
// ... séquence en cours ...
getProperty("parms")->enable();

It’s this lock — not the OST::Busy state — that actually prevents concurrent editing; setStateEvent() alone is only informational, it blocks nothing server-side.