Mensajes Cliente → Servidor
Formato de los mensajes
Cada mensaje es un objeto JSON con una única clave de primer nivel que identifica el comando. El valor es un objeto cuya estructura depende del comando.
Todas las claves de comandos de cliente están en MAYÚSCULAS (ej. DU, SV, SA, GC).
Índice de comandos
| Clave | Descripción |
|---|---|
DU | Solicitar el dump completo |
XX | Heartbeat / keepalive |
LO | Conexión |
IL | Cambiar el idioma |
SV | Enviar el valor de un solo elemento |
SA | Enviar todos los valores de elementos |
DP | Solicitar el dump de una propiedad |
I1 | Clic en preicon1 de propiedad |
I2 | Clic en preicon2 de propiedad |
I3 | Clic en posticon1 de propiedad |
I4 | Clic en posticon2 de propiedad |
J1 | Clic en preicon de elemento |
J2 | Clic en posticon de elemento |
PL | Cargar un perfil |
PS | Guardar un perfil |
GC | Crear una línea de cuadrícula |
GU | Actualizar una línea de cuadrícula |
GD | Eliminar una línea de cuadrícula |
GF | Seleccionar una línea de cuadrícula |
GH | Subir una línea de cuadrícula |
GB | Bajar una línea de cuadrícula |
GR | Restablecer (vaciar) la cuadrícula |
CL | Cargar una configuración de módulos |
CS | Guardar la configuración actual |
FS | Elegir la carpeta de archivos vigilada |
ML | Cargar un módulo |
MK | Descargar un módulo |
QY | Consultar un módulo (mecanismo inter-módulo, no pensado para el cliente) |
YA | Iniciar el servidor INDI embebido |
YZ | Detener el servidor INDI embebido |
YL | Cargar un driver INDI |
YR | Recargar un driver INDI |
YS | Detener un driver INDI |
YC | Conectar el dispositivo de un driver INDI |
YD | Desconectar el dispositivo de un driver INDI |
YK | Cargar una configuración de drivers INDI guardada |
Todos los mensajes dirigidos a un módulo o a una propiedad usan la siguiente envoltura:
{
"COMANDO": {
"m": {
"NombreDelModulo": {
"p": {
"nombreDeLaPropiedad": { ... }
}
}
}
}
}donde m designa modules y p designa properties.
Gestión de sesión
DU — Solicitar el dump completo
Debe enviarse inmediatamente tras la conexión. El servidor responde con un mensaje d que contiene todos los módulos.
{ "DU": { "language": "es" } }| Campo | Descripción |
|---|---|
language | Idioma preferido para las etiquetas traducidas ("en", "fr", etc.) |
XX — Heartbeat
Enviar cada 30 segundos para mantener la conexión activa. El servidor responde con xx.
{ "XX": {} }LO — Conexión
Requerido cuando el servidor tiene activado el control de acceso (grant-client vale "-1" en la respuesta dump).
{
"LO": {
"user": "username",
"pw": "password",
"language": "es"
}
}IL — Cambiar el idioma
Modifica el idioma de las etiquetas traducidas sin reconectar.
{ "IL": { "language": "es" } }Actualización de los valores de propiedad
SV — Enviar el valor de un solo elemento
A usar cuando directedit es true en el elemento — envía inmediatamente el valor de un solo elemento. Sobre un elemento con directedit: false el backend lo rechaza (error en el log del módulo, no se aplica nada): usar SA en su lugar.
{
"SV": {
"m": {
"Focus": {
"p": {
"parameters": {
"e": {
"iterations": 7
}
}
}
}
}
}
}e contiene un único par nombreElemento: valor.
SA — Enviar todos los valores de elementos
Usado para enviar todos los elementos editables de una propiedad de una vez (ej. desde un formulario). También usado cuando directedit es false.
{
"SA": {
"m": {
"Focus": {
"p": {
"parameters": {
"e": {
"iterations": 7,
"exposure": 3.5,
"active": true
}
}
}
}
}
}
}Un SA debe incluir todos los elementos editables de la propiedad. Si falta uno solo, el backend no aplica absolutamente nada — ni siquiera los elementos presentes — y registra una advertencia en el servidor (visible para el cliente mediante un mensaje l). No es un caso a gestionar con una degradación elegante: indica un error en el cliente, que siempre debe enviar el conjunto completo de elementos.
DP — Solicitar el dump de una propiedad
Pide al servidor que reenvíe el estado completo de una propiedad, como si acabara de (re)cargarse — útil para forzar la actualización de una propiedad que un cliente sospecha desincronizada, sin recargar todo el módulo. El servidor responde con un mensaje ap para esa sola propiedad.
{
"DP": {
"m": {
"Focus": {
"p": {
"parameters": {}
}
}
}
}
}Sin campo e: DP afecta a toda la propiedad, no a un elemento concreto.
Acciones sobre los iconos
preicon1/preicon2/posticon1/posticon2 (propiedades) y preicon/posticon (elementos, ver Modelo de datos) no son decoraciones: cada una es una acción clicable. Un valor no vacío es el nombre del icono de Google Fonts (Material Icons) a mostrar; al hacer clic en el icono mostrado se envía el comando correspondiente de abajo, sin ningún valor ni parámetro — un payload vacío que simplemente apunta a la propiedad (o al elemento). Lo que la acción hace realmente en el servidor (guardar un perfil, cargar un perfil, lanzar un proceso…) depende enteramente del módulo; la única tarea del cliente es mostrar el icono y reenviar el clic tal cual.
Una cadena vacía significa que no hay ningún icono en ese lugar — nada que mostrar, y por tanto nada que hacer clic ni que enviar. No mostrar un marcador de posición ni conectar un manejador de clic para un campo de icono vacío.
I1 — Clic en preicon1 de propiedad
{
"I1": {
"m": {
"Focus": {
"p": {
"saveprofile": {}
}
}
}
}
}I2 — Clic en preicon2 de propiedad
{
"I2": {
"m": {
"Focus": {
"p": {
"loadprofile": {}
}
}
}
}
}I3 — Clic en posticon1 de propiedad
Misma estructura que I1 / I2.
I4 — Clic en posticon2 de propiedad
Misma estructura que I1 / I2.
J1 — Clic en preicon de elemento
{
"J1": {
"m": {
"Focus": {
"p": {
"devices": {
"e": {
"myDevice": {}
}
}
}
}
}
}
}J2 — Clic en posticon de elemento
Misma estructura que J1.
Gestión de perfiles
PL — Cargar un perfil
{
"PL": {
"m": {
"Focus": {
"profile": "myProfile"
}
}
}
}El servidor responde con un mensaje fl (perfil cargado) y luego envía los valores de propiedades actualizados.
fc transitorio precede a flEl servidor envía un mensaje fc (modificaciones no guardadas) justo antes de fl, como paso intermedio de la carga del perfil, antes de que los valores de la propiedad cambien realmente. Un cliente que reaccione a fc mostrando un indicador de “modificaciones no guardadas” lo verá parpadear brevemente y autocorregirse en cuanto llegue fl justo después — es lo esperado, no un error.
PS — Guardar un perfil
{
"PS": {
"m": {
"Focus": {
"profile": "myProfile"
}
}
}
}El servidor responde con un mensaje fs (perfil guardado).
Operaciones sobre cuadrículas
Los comandos de cuadrícula apuntan a una propiedad específica dentro de un módulo. Todos usan la envoltura m / p.
GC — Crear una línea de cuadrícula
Añade una nueva línea a la cuadrícula con los valores de elementos proporcionados.
{
"GC": {
"m": {
"Sequencer": {
"p": {
"sequence": {
"e": {
"target": "M31",
"exposure": 120,
"count": 10
}
}
}
}
}
}
}El servidor responde con un mensaje gc confirmando el índice de la nueva línea.
ea tal cual llega — nada que cuestionarseCrear una línea de cuadrícula aplica primero los valores enviados a los elementos base de la propiedad, luego añade la fila y emite gc. Esto puede requerir más de un mensaje ea (uno por cada elemento base modificado, enviados uno tras otro — enviar target/exposure/count como en el ejemplo de arriba produce tres mensajes ea distintos antes del gc final), pero un cliente nunca necesita saber ni predecir ese número: el servidor solo envía exactamente lo necesario para mantener correcto el datastore del cliente, ni más ni menos (ver Principios de arquitectura del cliente). Aplique cada mensaje tal como llega, como hace siempre, y el resultado es correcto sea cual sea el número de mensajes recibidos.
El único efecto visible: una propiedad que muestra a la vez sus elementos base y su cuadrícula (showElts y showGrid ambos activos — combinación poco habitual) parpadeará a través de estos estados intermedios antes de que aparezca la nueva fila. Este es el estado real del datastore, no un error, y se resuelve solo — no hace falta filtrarlo ni hacer polling para evitarlo.
GU — Actualizar una línea de cuadrícula
Actualiza una línea específica (índice base cero i) con nuevos valores de elementos.
{
"GU": {
"m": {
"Sequencer": {
"p": {
"sequence": {
"i": 1,
"e": {
"target": "M42",
"exposure": 60
}
}
}
}
}
}
}GD — Eliminar una línea de cuadrícula
Elimina la línea en el índice i (base cero).
{
"GD": {
"m": {
"Sequencer": {
"p": {
"sequence": {
"i": 0
}
}
}
}
}
}GF — Seleccionar una línea de cuadrícula
Solicita al servidor que cargue una línea de la cuadrícula en los elementos editables de la propiedad (usado al hacer clic en una línea para editarla).
{
"GF": {
"m": {
"Sequencer": {
"p": {
"sequence": {
"i": 2
}
}
}
}
}
}GH — Subir una línea de cuadrícula
Desplaza la línea en el índice i una posición hacia arriba.
{
"GH": {
"m": {
"Sequencer": {
"p": {
"sequence": {
"i": 3
}
}
}
}
}
}GB — Bajar una línea de cuadrícula
Desplaza la línea en el índice i una posición hacia abajo.
{
"GB": {
"m": {
"Sequencer": {
"p": {
"sequence": {
"i": 2
}
}
}
}
}
}GR — Restablecer la cuadrícula
Vacía por completo la cuadrícula de una propiedad (todas las filas eliminadas) — no una sola fila como GD. El servidor difunde gr en respuesta.
{
"GR": {
"m": {
"Sequencer": {
"p": {
"sequence": {}
}
}
}
}
}Sin campo i: GR afecta a toda la cuadrícula, no a una fila concreta.
Ciclo de vida de los módulos
CL — Cargar una configuración
Carga todos los módulos de una configuración guardada (lista en controllerdata.availableconfs, ver Controlador).
{ "CL": { "name": "modules" } }CS — Guardar la configuración actual
Registra el conjunto de módulos actualmente cargados con el nombre indicado (GlobalDatastore nunca se incluye).
{ "CS": { "name": "modules" } }ML — Cargar un módulo
Pide al servidor que cargue dinámicamente un módulo. Estos comandos se dirigen al controlador: el payload no usa la envoltura m/p.
{ "ML": { "lib": "focus", "label": "MyFocuser", "profile": "default" } }| Campo | Descripción |
|---|---|
lib | Biblioteca del módulo, sin el prefijo libost (lo añade el servidor): focus para libostfocus. Las bibliotecas disponibles son las claves de controllerdata.libraries |
label | Etiqueta de esta instancia; el nombre del módulo es esa etiqueta sin espacios. Si ya hay cargado un módulo con ese nombre, la petición se rechaza |
profile | Nombre del perfil a cargar (opcional) |
Las claves de controllerdata.libraries ya están prefijadas (libostfocus, libostdummy…). El servidor vuelve a anteponer libost incondicionalmente, así que reenviar una clave tal cual la duplica y falla:
{ "ML": { "lib": "libostdummy", "label": "MyDummy" } }Error loading library MyDummy - Cannot load library libostlibostdummy: cannot open shared object file: No such file or directoryQuitar primero el prefijo libost de la clave (misma transformación de la que depende la convención de claves de profiles, ver Datos del controlador):
{ "ML": { "lib": "dummy", "label": "MyDummy" } }MK — Descargar un módulo
Pide al servidor que descargue un módulo en ejecución, designado por su nombre.
{ "MK": { "name": "MyFocuser" } }GlobalDatastore no puede descargarse: la petición se rechaza (mensaje de error en los logs).
Consultar un módulo
QY — Consultar un módulo
QY/qa existe principalmente para que un módulo pueda hacerle una pregunta a otro módulo internamente (un módulo que llama a Basemodule::otherModuleQuery(), que pasa por este mismo mecanismo). Un cliente puede técnicamente enviar QY también — el sobre es idéntico — pero hoy no hay ningún caso de uso establecido del lado del cliente. Documentado aquí por exhaustividad, y porque será relevante cuando se documente cómo construir un módulo — no como algo sobre lo que un cliente deba construir una funcionalidad.
Mecanismo genérico de consulta/respuesta propio de cada módulo: hacer a un módulo una pregunta con nombre y parámetros, recibir una respuesta con nombre vía qa. Qué nombres de consulta entiende realmente un módulo (si entiende alguno) depende enteramente de ese módulo — el protocolo solo define el sobre. Nótese: sin envoltorio p aquí, QY apunta al propio módulo, no a una de sus propiedades.
{
"QY": {
"m": {
"Sequencer": {
"query": "profileduration",
"params": { "profile": "default" }
}
}
}
}Hoy solo el módulo Sequencer implementa una respuesta real (profileduration), usada internamente por el módulo Planner. Ningún otro módulo incluido responde de forma significativa a ningún nombre de consulta por ahora.
Sistema de archivos
FS — Elegir la carpeta vigilada
Designa la carpeta cuya lista de archivos publica el servidor (ver Sistema de archivos). La ruta es relativa al webroot; la cadena vacía designa la raíz.
{ "FS": { "folder": "/Sequencer/gam Cyg/LIGHT/Luminance" } }El cambio es global en el servidor (todos los clientes ven la nueva lista). La ruta no se valida: una carpeta inexistente da una lista vacía.
Control del servidor INDI embebido
Estos comandos solo están disponibles cuando el servidor dispone de una instancia INDI embebida.
| Comando | Descripción |
|---|---|
YA | Iniciar el servidor INDI embebido |
YZ | Detener el servidor INDI embebido |
YL | Cargar un driver INDI ({"YL": {"driver": "<binary>"}}) |
YR | Recargar un driver INDI ({"YR": {"driver": "<binary>"}}) |
YS | Detener un driver INDI ({"YS": {"driver": "<binary>"}}) |
YC | Conectar el dispositivo de un driver INDI ({"YC": {"driver": "<binary>"}}) |
YD | Desconectar el dispositivo de un driver INDI ({"YD": {"driver": "<binary>"}}) |
YK | Cargar una configuración de drivers INDI guardada ({"YK": {"name": "<nombre de la configuración>"}}) |
YA y YZ no tienen payload ({"YA": {}}). Para YL, YR, YS, YC y YD, driver es el campo binary de una entrada de controllerdata.indidrivers. Para YK, name es una clave de controllerdata.indiconfigurations (ver Controlador) — inicia todos los drivers guardados bajo ese nombre, independientemente de CL/CS, que en cambio agrupan los drivers activos en una configuración de módulos.
YL solo inicia el proceso del driver - el dispositivo todavía debe conectarse antes de poder usarse, con YC (la propiedad INDI CONNECTION.CONNECT). Es un paso separado a propósito: INDI modela “proceso activo” y “dispositivo conectado” de forma independiente. Igual que YA/YZ, YC/YD no tienen confirmación propia (ver la advertencia anterior): actualmente no existe ningún campo controllerdata que indique si el dispositivo de un driver está realmente conectado, solo si su proceso está activo (indiactivedrivers).
Resumen del control de acceso
| Valor grant servidor | Comandos permitidos |
|---|---|
grant-client: "1" | Todos los comandos |
grant-client: "0" | Solo DU, LO, IL |
grant-client: "-1" | Solo LO (autenticación requerida) |
Los comandos enviados sin permisos suficientes son ignorados silenciosamente por el servidor.