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

ClaveDescripción
DUSolicitar el dump completo
XXHeartbeat / keepalive
LOConexión
ILCambiar el idioma
SVEnviar el valor de un solo elemento
SAEnviar todos los valores de elementos
DPSolicitar el dump de una propiedad
I1Clic en preicon1 de propiedad
I2Clic en preicon2 de propiedad
I3Clic en posticon1 de propiedad
I4Clic en posticon2 de propiedad
J1Clic en preicon de elemento
J2Clic en posticon de elemento
PLCargar un perfil
PSGuardar un perfil
GCCrear una línea de cuadrícula
GUActualizar una línea de cuadrícula
GDEliminar una línea de cuadrícula
GFSeleccionar una línea de cuadrícula
GHSubir una línea de cuadrícula
GBBajar una línea de cuadrícula
GRRestablecer (vaciar) la cuadrícula
CLCargar una configuración de módulos
CSGuardar la configuración actual
FSElegir la carpeta de archivos vigilada
MLCargar un módulo
MKDescargar un módulo
QYConsultar un módulo (mecanismo inter-módulo, no pensado para el cliente)
YAIniciar el servidor INDI embebido
YZDetener el servidor INDI embebido
YLCargar un driver INDI
YRRecargar un driver INDI
YSDetener un driver INDI
YCConectar el dispositivo de un driver INDI
YDDesconectar el dispositivo de un driver INDI
YKCargar 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" } }
CampoDescripción
languageIdioma 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
            }
          }
        }
      }
    }
  }
}
Enviar siempre todos los elementos

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.

Un fc transitorio precede a fl

El 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.

Aplique cada ea tal cual llega — nada que cuestionarse

Crear 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" } }
CampoDescripción
libBiblioteca del módulo, sin el prefijo libost (lo añade el servidor): focus para libostfocus. Las bibliotecas disponibles son las claves de controllerdata.libraries
labelEtiqueta 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
profileNombre del perfil a cargar (opcional)
No pasar tal cual una clave de controllerdata.libraries

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 directory

Quitar 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

Comunicación inter-módulo, no una funcionalidad de cliente

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" }
      }
    }
  }
}
Todavía poco que ver

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.

ComandoDescripción
YAIniciar el servidor INDI embebido
YZDetener el servidor INDI embebido
YLCargar un driver INDI ({"YL": {"driver": "<binary>"}})
YRRecargar un driver INDI ({"YR": {"driver": "<binary>"}})
YSDetener un driver INDI ({"YS": {"driver": "<binary>"}})
YCConectar el dispositivo de un driver INDI ({"YC": {"driver": "<binary>"}})
YDDesconectar el dispositivo de un driver INDI ({"YD": {"driver": "<binary>"}})
YKCargar 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.

Cargar un driver no conecta su dispositivo

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 servidorComandos 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.