Anatomia di un modulo

Un modulo OST è una libreria condivisa (.so), caricata dinamicamente dal server, che espone dati strutturati (delle proprietà, contenenti degli elementi) e reagisce alle scritture inviate dai client.

Due classi di base

  • Basemodule — lo stretto necessario: proprietà, elementi, eventi, persistenza in profilo. Nessuna dipendenza da INDI. È la classe da usare per qualsiasi modulo che non pilota direttamente un device INDI (logica di business, aggregazione, elaborazione…).
  • IndiModule (deriva da Basemodule) — aggiunge tutta la meccanica di connessione a un server INDI, di gestione dei device, e le proprietà derivate comunemente utili (ottica, equipaggiamenti, localizzazione…). Vedi il capitolo Moduli INDI.

Questo capitolo copre solo Basemodule — tutto ciò che descrive si applica anche a IndiModule, che ne eredita.

La tripletta di file

Un modulo minimale sta in tre file, nella sua propria sottocartella:

  • <nome>.h — dichiara la classe (derivata da Basemodule), il suo costruttore/distruttore, e i metodi che sovrascrive (il più spesso onExternalEvent()).
  • <nome>.cpp — implementa il costruttore (che carica la struttura del modulo) e i metodi sovrascritti. Nulla di più è obbligatorio: un modulo che non ha alcuna azione da gestire non ha bisogno di sovrascrivere alcun metodo.
  • <nome>.json — dichiara la struttura del modulo: le sue proprietà e i loro elementi. Incorporato nel binario tramite un file <nome>.qrc (risorsa Qt) e caricato all’avvio.
// <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() è la funzione di export, con linkage C (extern "C") per essere rintracciabile dal suo nome grezzo una volta che il .so è caricato dinamicamente. La sua firma è sempre la stessa; si limita a istanziare la classe.
  • loadOstPropertiesFromFile(":nom.json") legge il file JSON incorporato e costruisce le proprietà/elementi che descrive — vedi Il modulo più piccolo.
Identità git del modulo

Un modulo in genere dichiara la propria identità (hash/data/messaggio del commit, versione) tramite setMetadata() nel suo costruttore, a partire dalle costanti generate dalla build (Version::GIT_SHA1, ecc.):

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

setMetadata() è un metadato libero, senza dichiarazione JSON preliminare.

La catena di eventi

Quando un client scrive un valore (o attiva un’azione), il server chiama, in questo ordine, tre metodi virtuali — mai direttamente, sempre tramite un punto di ingresso finale che lo sviluppatore del modulo non tocca né sovrascrive mai:

onExternalEventRoot()     (final — jamais surchargé)
  → onExternalEventBase()   (validation commune : propriété/élément existe,
                              permission, directedit, mise à jour automatique)
  → onExternalEventIndi()   (hook IndiModule — vide pour un Basemodule)
  → onExternalEvent()        (le module répond ici, s'il a besoin de réagir)

onExternalEventBase() applica già da sola il valore scritto al datastore se l’elemento (o la sua proprietà) ha autoupdate:true — in questo caso, onExternalEvent() spesso non ha nulla da fare, o solo da rileggere il valore applicato per reagire. Vedi Reagire a una scrittura per il dettaglio e un’insidia frequente.

Un modulo che non ha alcuna azione propria da gestire (come l’esempio hello sopra) semplicemente non ha nulla da sovrascrivere: l’implementazione vuota predefinita di onExternalEvent() è sufficiente.