Anatomía de un módulo

Un módulo OST es una biblioteca compartida (.so), cargada dinámicamente por el servidor, que expone datos estructurados (propiedades, que contienen elementos) y reacciona a las escrituras enviadas por los clientes.

Dos clases base

  • Basemodule — lo estrictamente necesario: propiedades, elementos, eventos, persistencia en perfil. Sin ninguna dependencia de INDI. Es la clase a usar para todo módulo que no controle directamente un device INDI (lógica de negocio, agregación, procesamiento…).
  • IndiModule (deriva de Basemodule) — añade toda la mecánica de conexión a un servidor INDI, de gestión de devices, y las propiedades derivadas comúnmente útiles (óptica, equipos, ubicación…). Ver el capítulo Módulos INDI.

Este capítulo solo cubre Basemodule — todo lo que describe se aplica también a IndiModule, que hereda de él.

El trío de archivos

Un módulo mínimo cabe en tres archivos, en su propia subcarpeta:

  • <nombre>.h — declara la clase (derivada de Basemodule), su constructor/destructor, y los métodos que sobrecarga (lo más frecuente, onExternalEvent()).
  • <nombre>.cpp — implementa el constructor (que carga la estructura del módulo) y los métodos sobrecargados. Nada más es obligatorio: un módulo que no tiene ninguna acción que procesar no necesita sobrecargar ningún método.
  • <nombre>.json — declara la estructura del módulo: sus propiedades y sus elementos. Embebido en el binario mediante un archivo <nombre>.qrc (recurso Qt) y cargado al inicio.
// <nom>.h
#ifndef HELLO_H
#define HELLO_H
#include <basemodule.h>

class Hello : public Basemodule
{
        Q_OBJECT
    public:
        Hello(QString name, QString label, QString profile, QVariantMap availableModuleLibs);
        ~Hello();
};

extern "C" Hello* initialize(QString name, QString label, QString profile, QVariantMap availableModuleLibs);
#endif
// <nom>.cpp
#include "hello.h"

Hello* initialize(QString name, QString label, QString profile, QVariantMap availableModuleLibs)
{
    return new Hello(name, label, profile, availableModuleLibs);
}

Hello::Hello(QString name, QString label, QString profile, QVariantMap availableModuleLibs)
    : Basemodule(name, label, profile, availableModuleLibs)
{
    Q_INIT_RESOURCE(hello);
    loadOstPropertiesFromFile(":hello.json");
}

Hello::~Hello() {}
  • initialize() es la función de exportación, con enlace C (extern "C") para poder ser encontrada por su nombre tal cual una vez que el .so se carga dinámicamente. Su firma es siempre la misma; se limita a instanciar la clase.
  • loadOstPropertiesFromFile(":nombre.json") lee el archivo JSON embebido y construye las propiedades/elementos que describe — ver El módulo más pequeño.
Identidad git del módulo

Un módulo normalmente registra su propia identidad (hash/fecha/mensaje del commit, versión) mediante setMetadata() en su constructor, a partir de las constantes generadas por el build (Version::GIT_SHA1, etc.):

setMetadata("description", "Ce que fait ce module");
setMetadata("thisGithash", QString::fromStdString(Version::GIT_SHA1));

setMetadata() es un metadato libre, sin declaración JSON previa.

La cadena de eventos

Cuando un cliente escribe un valor (o dispara una acción), el servidor llama, en este orden, a tres métodos virtuales — nunca directamente, siempre a través de un punto de entrada final que el desarrollador del módulo no toca ni sobrecarga nunca:

onExternalEventRoot()     (final — nunca sobrecargado)
  → onExternalEventBase()   (validación común: existe la propiedad/elemento,
                              permiso, directedit, actualización automática)
  → onExternalEventIndi()   (hook IndiModule — vacío para un Basemodule)
  → onExternalEvent()        (el módulo responde aquí, si necesita reaccionar)

onExternalEventBase() ya aplica por sí sola el valor escrito al datastore si el elemento (o su propiedad) tiene autoupdate:true — en ese caso, onExternalEvent() a menudo no tiene nada que hacer, o solo necesita releer el valor aplicado para reaccionar. Ver Reaccionar a una escritura para el detalle y una trampa frecuente.

Un módulo que no tiene ninguna acción propia que gestionar (como el ejemplo hello anterior) simplemente no tiene nada que sobrecargar: la implementación vacía por defecto de onExternalEvent() es suficiente.