Les familles prêtes à l'emploi

Module d’exemple : helloindifamily — un vrai module focuser, utilisable comme esclave par hellomaster ou par le sequencer.

Relation maître/esclave montrait declareFamily("hello") — une famille arbitraire, propre à la paire d’exemples. IndiModule propose, pour les rôles courants d’un module piloté par INDI, une version prête à l’emploi qui fait tout d’un coup :

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

Un seul appel, defineMeAsFocuser() :

  • déclare la famille focus (même mécanisme que declareFamily()) ;
  • construit la propriété actions, avec sa règle AtMostOne déjà posée entre abortfocus/autofocus/loop — rien à déclarer en JSON. autofocus est la séquence complète (déplacements + mesures jusqu’à l’optimum) ; loop est différent — voir l’encart ci-dessous ;
  • construit aussi parms et signals, mais vides au-delà de leur coquille générique — voir ci-dessous ;
  • appelle en interne defineMeAsImager(), qui ajoute en prime les capacités optique (giveMeAnOptic()) et image (giveMeAnImage(), voir Capturer une image) — un module focuser a donc déjà image/imgparms disponibles sans rien demander de plus ;
  • rend isFocuser() vrai à partir de là.

defineMeAsGuider(), defineMeAsSequencer(), defineMeAsImager(), defineMeAsNavigator() et defineMeAsPlanner() suivent le même principe pour les autres rôles ; chacun a son is...() associé (isGuider(), isSequencer()…).

parms et signals : une coquille, pas un contenu

parms est créée vide (giveMeAParms()) : un focuser réel doit y ajouter lui-même, avec addElt() (Créer des propriétés dynamiquement), les éléments qui lui sont propres — pas de pas, de backlash, de délai entre mesures… rien n’est fourni par défaut.

signals (et non state — c’est le même nom que celui créé par giveMeAState(), vu dans Relation maître/esclave) arrive avec un jeu fixe d’éléments génériques : state (lumière), statedescription, event, eventdescription, et trois éléments “boîte aux lettres” sans sémantique imposée — val_num (float), val_int, val_str — qu’un module peut détourner pour exposer une valeur ponctuelle sans créer de propriété dédiée.

loop : une mesure en continu, pas un autofocus qui se répète

autofocus et loop ne font pas la même chose, et rien d’autre que le nom de l’action ne le dit. Dans le vrai module Focus du core, loop ne déplace jamais le focuser lui-même — c’est une mesure répétée de la métrique de netteté (HFR ou équivalent) sans aucun mouvement, pensée pour que l’utilisateur tourne le focuser à la main et voie la métrique réagir en direct. Les deux actions tournent jusqu’à abortfocus, mais autofocus pilote le device, loop se contente de l’observer.

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", "");
    }
}

Le module n’a plus qu’à réagir aux actions déjà déclarées — setStateEvent() (vu dans Relation maître/esclave) reste le seul moyen de faire avancer son état.

Verrouiller une propriété pendant une opération longue

Pendant une séquence d’autofocus réelle, rien n’empêche l’utilisateur d’éditer parms en plein milieu — sauf à le décider explicitement. getProperty(nom)->disable() verrouille toute la propriété en lecture côté client (grisée côté interface), ->enable() la déverrouille :

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

C’est le verrou — pas l’état OST::Busy — qui empêche réellement une édition concurrente ; setStateEvent() seul ne fait qu’informer, il ne bloque rien côté serveur.