Las familias listas para usar

Módulo de ejemplo: helloindifamily — un verdadero módulo focuser, utilizable como esclavo por hellomaster o por el sequencer.

Relación maestro/esclavo mostraba declareFamily("hello") — una familia arbitraria, propia del par de ejemplos. IndiModule propone, para los roles habituales de un módulo controlado por INDI, una versión lista para usar que hace todo de una vez:

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

Una sola llamada, defineMeAsFocuser():

  • declara la familia focus (mismo mecanismo que declareFamily());
  • construye la propiedad actions, con su regla AtMostOne ya puesta entre abortfocus/autofocus/loop — nada que declarar en JSON. autofocus es la secuencia completa (desplazamientos + mediciones hasta el óptimo); loop es diferente — ver el recuadro más abajo;
  • construye también parms y signals, pero vacías más allá de su cascarón genérico — ver más abajo;
  • llama internamente a defineMeAsImager(), que añade de propina las capacidades óptica (giveMeAnOptic()) e imagen (giveMeAnImage(), ver Capturar una imagen) — un módulo focuser ya tiene por tanto image/imgparms disponibles sin pedir nada más;
  • hace que isFocuser() sea verdadero a partir de ahí.

defineMeAsGuider(), defineMeAsSequencer(), defineMeAsImager(), defineMeAsNavigator() y defineMeAsPlanner() siguen el mismo principio para los demás roles; cada uno tiene su is...() asociado (isGuider(), isSequencer()…).

parms y signals: un cascarón, no un contenido

parms se crea vacía (giveMeAParms()): un focuser real debe añadirle él mismo, con addElt() (Crear propiedades dinámicamente), los elementos propios de él — paso, backlash, retardo entre mediciones… nada se proporciona por defecto.

signals (y no state — es el mismo nombre que el creado por giveMeAState(), visto en Relación maestro/esclavo) llega con un conjunto fijo de elementos genéricos: state (luz), statedescription, event, eventdescription, y tres elementos “buzón” sin semántica impuesta — val_num (float), val_int, val_str — que un módulo puede reutilizar para exponer un valor puntual sin crear una propiedad dedicada.

loop: una medición continua, no un autoenfoque que se repite

autofocus y loop no hacen lo mismo, y nada más que el nombre de la acción lo indica. En el verdadero módulo Focus del core, loop nunca mueve el focuser por sí mismo — es una medición repetida de la métrica de nitidez (HFR o equivalente) sin ningún movimiento, pensada para que el usuario gire el focuser a mano y vea la métrica reaccionar en directo. Ambas acciones se ejecutan hasta abortfocus, pero autofocus controla el device, loop se limita a observarlo.

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

El módulo solo tiene que reaccionar a las acciones ya declaradas — setStateEvent() (visto en Relación maestro/esclavo) sigue siendo el único medio de hacer avanzar su estado.

Bloquear una propiedad durante una operación larga

Durante una secuencia de autoenfoque real, nada impide al usuario editar parms en plena mitad — salvo que se decida explícitamente lo contrario. getProperty(nombre)->disable() bloquea toda la propiedad en lectura del lado del cliente (atenuada en la interfaz), ->enable() la desbloquea:

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

Es el bloqueo — no el estado OST::Busy — el que realmente impide una edición concurrente; setStateEvent() solo informa, no bloquea nada del lado del servidor.