Messages Client → Serveur

Format des messages

Chaque message est un objet JSON avec une seule clé de premier niveau identifiant la commande. La valeur est un objet dont la structure dépend de la commande.

Toutes les clés de commandes client sont en MAJUSCULES (ex. DU, SV, SA, GC).


Index des commandes

CléDescription
DUDemander le dump complet
XXHeartbeat / keepalive
LOConnexion
ILChanger la langue
SVEnvoyer la valeur d’un seul élément
SAEnvoyer toutes les valeurs d’éléments
DPDemander le dump d’une propriété
I1Clic sur preicon1 de propriété
I2Clic sur preicon2 de propriété
I3Clic sur posticon1 de propriété
I4Clic sur posticon2 de propriété
J1Clic sur preicon d’élément
J2Clic sur posticon d’élément
PLCharger un profil
PSSauvegarder un profil
GCCréer une ligne de grille
GUMettre à jour une ligne de grille
GDSupprimer une ligne de grille
GFSélectionner une ligne de grille
GHMonter une ligne de grille
GBDescendre une ligne de grille
GRRéinitialiser (vider) la grille
CLCharger une configuration de modules
CSSauvegarder la configuration courante
FSChoisir le dossier de fichiers surveillé
MLCharger un module
MKDécharger un module
QYInterroger un module (mécanisme inter-module, pas destiné au client)
YADémarrer le serveur INDI embarqué
YZArrêter le serveur INDI embarqué
YLCharger un driver INDI
YRRecharger un driver INDI
YSArrêter un driver INDI

Tous les messages ciblant un module ou une propriété utilisent l’enveloppe suivante :

{
  "COMMANDE": {
    "m": {
      "NomDuModule": {
        "p": {
          "nomDeLaPropriété": { ... }
        }
      }
    }
  }
}

où m désigne modules et p désigne properties.


Gestion de session

DU — Demander le dump complet

Doit être envoyé immédiatement après la connexion. Le serveur répond avec un message d contenant tous les modules.

{ "DU": { "language": "fr" } }
ChampDescription
languageLangue préférée pour les libellés traduits ("en", "fr", etc.)

XX — Heartbeat

À envoyer toutes les 30 secondes pour maintenir la connexion active. Le serveur répond avec xx.

{ "XX": {} }

LO — Connexion

Requis lorsque le serveur a le contrôle d’accès activé (grant-client vaut "-1" dans la réponse dump).

{
  "LO": {
    "user": "username",
    "pw": "password",
    "language": "fr"
  }
}

IL — Changer la langue

Modifie la langue des libellés traduits sans se reconnecter.

{ "IL": { "language": "fr" } }

Mise à jour des valeurs de propriété

SV — Envoyer la valeur d’un seul élément

À utiliser lorsque directedit est true sur l’élément — envoie immédiatement la valeur d’un seul élément. Sur un élément à directedit: false, le backend le rejette (erreur dans le log du module, rien n’est appliqué) : utiliser SA à la place.

{
  "SV": {
    "m": {
      "Focus": {
        "p": {
          "parameters": {
            "e": {
              "iterations": 7
            }
          }
        }
      }
    }
  }
}

e contient une seule paire nomÉlément: valeur.


SA — Envoyer toutes les valeurs d’éléments

Utilisé pour soumettre tous les éléments éditables d’une propriété en une fois (ex. depuis un formulaire). Également utilisé lorsque directedit est false.

{
  "SA": {
    "m": {
      "Focus": {
        "p": {
          "parameters": {
            "e": {
              "iterations": 7,
              "exposure": 3.5,
              "active": true
            }
          }
        }
      }
    }
  }
}
Toujours envoyer tous les éléments

Un SA doit contenir tous les éléments éditables de la propriété. Si un seul manque, le backend n’applique rien du tout — pas même les éléments présents — et journalise un avertissement côté serveur (visible côté client via un message l). Ce n’est pas un cas à gérer avec une dégradation gracieuse : c’est le signe d’un bug dans le client, qui doit toujours envoyer l’ensemble complet des éléments.


DP — Demander le dump d’une propriété

Demande au serveur de renvoyer l’état complet d’une propriété, comme si elle venait d’être (re)chargée — utile pour forcer le rafraîchissement d’une propriété qu’un client soupçonne d’être désynchronisée, sans recharger tout le module. Le serveur répond par un message ap pour cette seule propriété.

{
  "DP": {
    "m": {
      "Focus": {
        "p": {
          "parameters": {}
        }
      }
    }
  }
}

Pas de champ e : DP cible la propriété entière, pas un élément particulier.


Actions sur les icônes

preicon1/preicon2/posticon1/posticon2 (propriétés) et preicon/posticon (éléments, voir Modèle de données) ne sont pas des décorations : chacune est une action cliquable. Une valeur non vide est le nom de l’icône Google Fonts (Material Icons) à afficher ; cliquer sur l’icône affichée envoie la commande correspondante ci-dessous sans aucune valeur ni paramètre — un payload vide qui cible simplement la propriété (ou l’élément). Ce que fait réellement l’action côté serveur (sauvegarder un profil, charger un profil, lancer un processus…) dépend entièrement du module ; le seul rôle du client est d’afficher l’icône et de transmettre le clic tel quel.

Une chaîne vide signifie qu’il n’y a aucune icône dans cet emplacement — rien à afficher, et donc rien à cliquer ni à envoyer. Ne pas afficher de substitut ni brancher de gestionnaire de clic pour un champ icône vide.

I1 — Clic sur preicon1 de propriété

{
  "I1": {
    "m": {
      "Focus": {
        "p": {
          "saveprofile": {}
        }
      }
    }
  }
}

I2 — Clic sur preicon2 de propriété

{
  "I2": {
    "m": {
      "Focus": {
        "p": {
          "loadprofile": {}
        }
      }
    }
  }
}

I3 — Clic sur posticon1 de propriété

Même structure que I1 / I2.

I4 — Clic sur posticon2 de propriété

Même structure que I1 / I2.


J1 — Clic sur preicon d’élément

{
  "J1": {
    "m": {
      "Focus": {
        "p": {
          "devices": {
            "e": {
              "myDevice": {}
            }
          }
        }
      }
    }
  }
}

J2 — Clic sur posticon d’élément

Même structure que J1.


Gestion des profils

PL — Charger un profil

{
  "PL": {
    "m": {
      "Focus": {
        "profile": "myProfile"
      }
    }
  }
}

Le serveur répond avec un message fl (profil chargé) puis envoie les valeurs de propriétés mises à jour.

Un fc transitoire précède fl

Le serveur envoie un message fc (modifications non sauvegardées) juste avant fl, comme étape intermédiaire du chargement du profil, avant que les valeurs des propriétés ne changent réellement. Un client qui réagit à fc en affichant un indicateur « modifications non sauvegardées » le verra clignoter le temps d’un message et se corriger de lui-même dès que fl arrive juste après — c’est attendu, pas un bug.


PS — Sauvegarder un profil

{
  "PS": {
    "m": {
      "Focus": {
        "profile": "myProfile"
      }
    }
  }
}

Le serveur répond avec un message fs (profil sauvegardé).


Opérations sur les grilles

Les commandes de grille ciblent une propriété spécifique au sein d’un module. Toutes utilisent l’enveloppe m / p.

GC — Créer une ligne de grille

Ajoute une nouvelle ligne à la grille avec les valeurs d’éléments fournies.

{
  "GC": {
    "m": {
      "Sequencer": {
        "p": {
          "sequence": {
            "e": {
              "target": "M31",
              "exposure": 120,
              "count": 10
            }
          }
        }
      }
    }
  }
}

Le serveur répond avec un message gc confirmant l’index de la nouvelle ligne.

Applique chaque ea tel qu’il arrive — rien à se demander

Créer une ligne de grille applique d’abord les valeurs soumises aux éléments de base de la propriété, puis ajoute la ligne et émet gc. Ça peut prendre plusieurs messages ea (un par élément de base modifié, envoyés l’un après l’autre — soumettre target/exposure/count comme ci-dessus produit trois messages ea distincts avant le gc final), mais un client n’a jamais besoin de connaître ou de prévoir ce nombre : le serveur n’envoie jamais que le strict nécessaire pour garder le datastore du client exact, ni plus ni moins (voir Principes d’architecture client). Applique chaque message tel qu’il se présente, comme d’habitude, et le résultat est correct quel que soit le nombre de messages reçus.

Le seul effet visible : une propriété qui affiche à la fois ses éléments de base et sa grille (showElts et showGrid tous les deux actifs — combinaison rare) clignotera à travers ces états intermédiaires avant que la nouvelle ligne apparaisse. C’est le vrai état du datastore, pas un bug, et ça se résout tout seul — pas besoin de le filtrer ni de faire du polling pour le contourner.


GU — Mettre à jour une ligne de grille

Met à jour une ligne spécifique (index base zéro i) avec de nouvelles valeurs d’éléments.

{
  "GU": {
    "m": {
      "Sequencer": {
        "p": {
          "sequence": {
            "i": 1,
            "e": {
              "target": "M42",
              "exposure": 60
            }
          }
        }
      }
    }
  }
}

GD — Supprimer une ligne de grille

Supprime la ligne à l’index i (base zéro).

{
  "GD": {
    "m": {
      "Sequencer": {
        "p": {
          "sequence": {
            "i": 0
          }
        }
      }
    }
  }
}

GF — Sélectionner une ligne de grille

Demande au serveur de charger une ligne de la grille dans les éléments éditables de la propriété (utilisé lors du clic sur une ligne pour l’éditer).

{
  "GF": {
    "m": {
      "Sequencer": {
        "p": {
          "sequence": {
            "i": 2
          }
        }
      }
    }
  }
}

GH — Monter une ligne de grille

Déplace la ligne à l’index i d’une position vers le haut.

{
  "GH": {
    "m": {
      "Sequencer": {
        "p": {
          "sequence": {
            "i": 3
          }
        }
      }
    }
  }
}

GB — Descendre une ligne de grille

Déplace la ligne à l’index i d’une position vers le bas.

{
  "GB": {
    "m": {
      "Sequencer": {
        "p": {
          "sequence": {
            "i": 2
          }
        }
      }
    }
  }
}

GR — Réinitialiser la grille

Vide entièrement la grille d’une propriété (toutes les lignes supprimées) — contrairement à GD qui ne supprime qu’une ligne. Le serveur diffuse gr en réponse.

{
  "GR": {
    "m": {
      "Sequencer": {
        "p": {
          "sequence": {}
        }
      }
    }
  }
}

Pas de champ i : GR cible la grille entière, pas une ligne particulière.


Cycle de vie des modules

CL — Charger une configuration

Charge tous les modules d’une configuration sauvegardée (liste dans controllerdata.availableconfs, voir Contrôleur).

{ "CL": { "name": "modules" } }

CS — Sauvegarder la configuration courante

Enregistre l’ensemble des modules actuellement chargés sous le nom donné (GlobalDatastore n’est jamais inclus).

{ "CS": { "name": "modules" } }

ML — Charger un module

Demande au serveur de charger dynamiquement un module. Ces commandes visent le contrôleur : le payload n’utilise pas l’enveloppe m/p.

{ "ML": { "lib": "focus", "label": "MyFocuser", "profile": "default" } }
ChampDescription
libBibliothèque du module, sans le préfixe libost (le serveur l’ajoute) : focus pour libostfocus. Les bibliothèques disponibles sont les clés de controllerdata.libraries
labelLibellé de cette instance ; le nom du module est ce libellé sans les espaces. Si un module de ce nom est déjà chargé, la demande est refusée
profileNom de profil à charger (optionnel)
Ne pas passer telle quelle une clé de controllerdata.libraries

Les clés de controllerdata.libraries sont déjà préfixées (libostfocus, libostdummy…). Le serveur ajoute libost une nouvelle fois, sans condition : transmettre la clé telle quelle double le préfixe et échoue :

{ "ML": { "lib": "libostdummy", "label": "MyDummy" } }
Error loading library MyDummy - Cannot load library libostlibostdummy: cannot open shared object file: No such file or directory

Retirer d’abord le préfixe libost de la clé (même transformation que celle sur laquelle s’appuie la convention de clé de profiles, voir Données du contrôleur) :

{ "ML": { "lib": "dummy", "label": "MyDummy" } }

MK — Décharger un module

Demande au serveur de décharger un module en cours d’exécution, désigné par son nom.

{ "MK": { "name": "MyFocuser" } }

GlobalDatastore ne peut pas être déchargé : la demande est refusée (message d’erreur dans les logs).


Interroger un module

QY — Interroger un module

Communication inter-module, pas une fonctionnalité client

QY/qa existe avant tout pour qu’un module puisse poser une question à un autre module en interne (un module qui appelle Basemodule::otherModuleQuery(), qui passe par ce même mécanisme). Un client peut techniquement envoyer QY aussi — l’enveloppe est identique — mais il n’y a aujourd’hui aucun cas d’usage établi côté client. Documenté ici pour l’exhaustivité, et parce que ça comptera quand on documentera comment construire un module — pas comme quelque chose sur lequel un client devrait construire une fonctionnalité.

Mécanisme générique de requête/réponse propre à chaque module : poser à un module une question nommée avec des paramètres, recevoir une réponse nommée en retour via qa. Les noms de requête qu’un module comprend réellement (s’il en comprend) dépendent entièrement de ce module — le protocole ne définit que l’enveloppe. À noter : pas d’enveloppe p ici, QY cible le module lui-même, pas une de ses propriétés.

{
  "QY": {
    "m": {
      "Sequencer": {
        "query": "profileduration",
        "params": { "profile": "default" }
      }
    }
  }
}
Pas grand-chose à voir pour l’instant

Aujourd’hui seul le module Sequencer implémente une vraie réponse (profileduration), utilisée en interne par le module Planner. Aucun autre module livré ne répond de façon utile à une quelconque requête pour l’instant.


Système de fichiers

FS — Choisir le dossier surveillé

Désigne le dossier dont le serveur publie la liste de fichiers (voir Système de fichiers). Le chemin est relatif au webroot ; la chaîne vide désigne la racine.

{ "FS": { "folder": "/Sequencer/gam Cyg/LIGHT/Luminance" } }

Le changement est global au serveur (tous les clients voient la nouvelle liste). Le chemin n’est pas validé : un dossier inexistant donne une liste vide.


Contrôle du serveur INDI embarqué

Ces commandes ne sont disponibles que lorsque le serveur dispose d’une instance INDI embarquée.

CommandeDescription
YADémarrer le serveur INDI embarqué
YZArrêter le serveur INDI embarqué
YLCharger un driver INDI ({"YL": {"driver": "<binary>"}})
YRRecharger un driver INDI ({"YR": {"driver": "<binary>"}})
YSArrêter un driver INDI ({"YS": {"driver": "<binary>"}})

YA et YZ n’ont pas de payload ({"YA": {}}). Pour YL, YR et YS, driver est le champ binary d’une entrée de controllerdata.indidrivers.


Récapitulatif du contrôle d’accès

Valeur grant serveurCommandes autorisées
grant-client: "1"Toutes les commandes
grant-client: "0"DU, LO, IL uniquement
grant-client: "-1"LO uniquement (authentification requise)

Les commandes envoyées sans droits suffisants sont silencieusement ignorées par le serveur.