Módulos INDI
Aquí es donde todo se juega
class IndiModule : public Basemodule, public INDI::BaseClientUna sola línea, y sin embargo todo el framework está ahí: IndiModule es el punto de encuentro entre dos mundos.
Basemodulehabla con el usuario.INDI::BaseClienthabla con el hardware.IndiModuleno hereda un poco de cada uno: hereda realmente de ambos a la vez, y es precisamente esta posición, justo entre los dos, la que lo convierte en el lugar donde escribir la inteligencia real de un device — un focuser, un sequencer, un polar alignment no son más que eso: la traducción de una petición del usuario en una orden de hardware, y de una medición de hardware en información para el usuario.
Y no es una forma de hablar: un IndiModule es un cliente INDI de pleno derecho, exactamente igual que Ekos o cualquier otro cliente. Todos los métodos de INDI::BaseClient están ahí, utilizables directamente (connectServer(), watchDevice(), setBLOBMode()…) — un módulo nunca está encerrado solo en los helpers giveMeA*()/defineMeAsXxx() vistos más abajo, estos no son más que un atajo para los casos más comunes.
El funcionamiento del cliente INDI en sí mismo — protocolo, ciclo de vida device/propiedad, BLOBs — le corresponde cubrirlo a la documentación de desarrollo de INDI, no a esta: aquí solo se habla de lo que IndiModule añade por encima.
Dos capas de callbacks
IndiModule intercepta él mismo los callbacks brutos de INDI::BaseClient (newDevice, newProperty, removeProperty, updateProperty) para hacer su propia contabilidad interna (refrescar la lista de valores de los devices, la conexión automática al arrancar…), y luego llama a una versión pública, virtual, pensada para ser sobrecargada por el módulo concreto:
newDevice() (privado, IndiModule) → onNewDevice() (virtual, a sobrecargar)
newProperty() (privado, IndiModule) → onNewProperty() (virtual, a sobrecargar)
removeProperty() (privado, IndiModule) → onRemoveProperty() (virtual, a sobrecargar)updateProperty() es una excepción: es protegido, no privado, precisamente para que un módulo que necesite un acceso más directo (captura de imagen, ver más abajo) pueda sobrecargarlo él mismo — a condición de volver a llamar a IndiModule::updateProperty(property) primero, bajo pena de perder el refresco automático de devices.* en la conexión.
Sobrecargar onUpdateProperty() (público, virtual) en lugar de updateProperty() (protegido) directamente — es lo que hacen helloindi y helloindiwrite más abajo. Solo un módulo que necesite distinguir un tipo de propiedad muy temprano en la cadena, como una captura de imagen (BLOB), tiene un motivo para sobrecargar updateProperty() él mismo.
El selector de device: giveMeADevice()
giveMeADevice("device", "Device", INDI::BaseDevice::GENERAL_INTERFACE);Añade un elemento de selección en la propiedad devices (creada al vuelo), cuya lista de valores solo propone los devices realmente presentes en el bus INDI y que exponen la interfaz solicitada (GENERAL_INTERFACE, TELESCOPE_INTERFACE, CCD_INTERFACE, FOCUSER_INTERFACE…). El nombre pasado como primer argumento ("device" aquí) es la clave del elemento en devices — getString("devices", "device") recupera el device elegido por el usuario.
Algunos vectores INDI estándar frecuentes
El protocolo INDI en sí mismo queda fuera de alcance (ver más arriba), pero algunos vectores aparecen en la casi totalidad de los módulos que controlan una cámara o un focuser — mejor no redescubrirlos cada vez:
| Necesidad | Vector | Elemento | Cómo |
|---|---|---|---|
| Mover un focuser a posición absoluta | ABS_FOCUS_POSITION | FOCUS_ABSOLUTE_POSITION | sendModNewNumber() para moverse hasta ahí, onUpdateProperty() o getModNumber() para seguir la posición actual |
| Recuadrar una cámara | CCD_FRAME | X/Y/WIDTH/HEIGHT | frameSet(device, x, y, w, h) — helper IndiModule, no se necesita el vector bruto |
| Reiniciar el cuadro de una cámara | CCD_FRAME_RESET | — (switch) | frameReset(device) — helper IndiModule, idem |
| Cancelar una exposición en curso | CCD_ABORT_EXPOSURE | ABORT | sendModNewSwitch(device, "CCD_ABORT_EXPOSURE", "ABORT", ISS_ON) |
La acción abortfocus puesta por defineMeAsFocuser() (Las familias listas para usar) detiene la secuencia del lado de OST — el módulo Focus del core nunca comanda FOCUS_ABORT_MOTION al device, se limita a dejar de enviar una nueva posición. Hay que verificar si el driver utilizado necesita un abort explícito durante el movimiento.
Los cinco ejemplos
Cada ejemplo añade un único concepto al anterior:
- Conectarse a un device
giveMeADevice, conexión automática, reflejar el estado CONNECTION en una propiedad
- Reflejar una propiedad del device
Leer y escribir una propiedad INDI real en ambos sentidos
- Capturar una imagen
giveMeAnImage, la recepción de un BLOB, y la cadena loadImageFromBlob/refreshPreview
- Actuar sobre el device
giveMeAnActions, enviar un switch INDI estándar al device en lugar de al estado interno del módulo
- Las familias listas para usar
defineMeAsFocuser() y los demás defineMeAsXxx(): la versión lista para usar de declareFamily()