Anatomie d'un module

Un module OST est une bibliothèque partagée (.so), chargée dynamiquement par le serveur, qui expose des données structurées (des propriétés, contenant des éléments) et réagit aux écritures envoyées par les clients.

Deux classes de base

  • Basemodule — le strict nécessaire : propriétés, éléments, événements, persistance en profil. Aucune dépendance à INDI. C’est la classe à utiliser pour tout module qui ne pilote pas directement un device INDI (logique métier, agrégation, traitement…).
  • IndiModule (dérive de Basemodule) — ajoute toute la mécanique de connexion à un serveur INDI, de gestion de devices, et les propriétés dérivées communément utiles (optique, équipements, localisation…). Voir le chapitre Modules INDI.

Ce chapitre ne couvre que Basemodule — tout ce qu’il décrit s’applique aussi à IndiModule, qui en hérite.

Le triplet de fichiers

Un module minimal tient en trois fichiers, dans son propre sous-dossier :

  • <nom>.h — déclare la classe (dérivée de Basemodule), son constructeur/destructeur, et les méthodes qu’elle surcharge (le plus souvent onExternalEvent()).
  • <nom>.cpp — implémente le constructeur (qui charge la structure du module) et les méthodes surchargées. Rien d’autre n’est obligatoire : un module qui n’a aucune action à traiter n’a besoin de surcharger aucune méthode.
  • <nom>.json — déclare la structure du module : ses propriétés et leurs éléments. Embarqué dans le binaire via un fichier <nom>.qrc (ressource Qt) et chargé au démarrage.
// <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() est la fonction d’export, en liaison C (extern "C") pour être retrouvable par son nom brut une fois le .so chargé dynamiquement. Sa signature est toujours la même ; elle se contente d’instancier la classe.
  • loadOstPropertiesFromFile(":nom.json") lit le fichier JSON embarqué et construit les propriétés/éléments qu’il décrit — voir Le plus petit module.
Identité git du module

Un module renseigne en général sa propre identité (hash/date/message du commit, version) via setMetadata() dans son constructeur, à partir des constantes générées par le build (Version::GIT_SHA1, etc.) :

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

setMetadata() est une métadonnée libre, sans déclaration JSON préalable.

La chaîne d’événements

Quand un client écrit une valeur (ou déclenche une action), le serveur appelle, dans cet ordre, trois méthodes virtuelles — jamais directement, toujours via un point d’entrée final que le développeur de module ne touche ni ne surcharge jamais :

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() applique déjà seule la valeur écrite au datastore si l’élément (ou sa propriété) a autoupdate:true — dans ce cas, onExternalEvent() n’a souvent rien à faire du tout, ou juste à relire la valeur appliquée pour réagir. Voir Réagir à une écriture pour le détail et un piège fréquent.

Un module qui n’a aucune action propre à gérer (comme l’exemple hello ci-dessus) n’a tout simplement rien à surcharger : l’implémentation vide par défaut de onExternalEvent() suffit.