Server → Client Messages
Message format
Every message is a JSON object with one top-level key. That key encodes the event type and may include a human-readable description separated by a dash:
"aa-dump all data" → event type = first 2 characters = "aa"
"ea" → event type = "ea"
"d" → event type = "d" (single character)Clients should match on the first 1 or 2 characters of the key, not the full string, since the description part can change.
All server-sent event keys are lowercase (e.g. d, ea, ap, gc).
Event type index
| Key | Description |
|---|---|
d | Full initial dump (response to DU) |
aa | Full module dump (module loaded/reloaded) |
ap | Full property replacement |
ea | Set all element values for a property |
ee | Set one element value |
ev | Set element value with updated constraints |
ps | Property status and enabled update |
dm | Module deleted |
dp | Property deleted |
gc | Grid line created |
gu | Grid line updated |
gd | Grid line deleted |
gr | Grid reset (all rows cleared) |
lc | LOV created or merged |
lu | LOV replaced |
ld | LOV deleted |
fs | Profile saved |
fl | Profile loaded |
fc | Profile has unsaved changes |
l | Log entry |
uc | Controller data update |
af | File added |
df | File deleted |
ad | Directory added |
dd | Directory deleted |
qa | Answer to a module query (inter-module mechanism, not client-facing) |
xx | Keepalive pong |
d — Full initial dump
Sent in response to a DU request. Contains everything: all modules, logs, file lists, and controller data. The keys of controllerdata and files are detailed in Controller, GlobalDatastore and files; the example below only shows a few of them.
{
"d": {
"grant-client": "1",
"grant-server": "0",
"serverlng": "en",
"m": {
"Focus": {
"infos": { "modulelabel": "Focus", "description": "..." },
"f": { "name": "default", "changed": false },
"l": {
"myLov": { "label": "My LOV", "type": "string", "values": { "a": "Option A" } }
},
"p": {
"parameters": {
"label": "Parameters",
"order": "10",
"level1": "Focus",
"level2": "Config",
"status": 0,
"permission": 2,
"enabled": true,
"showElts": true,
"hasGrid": false,
"hasGraph": false,
"rule": 0,
"badge": false,
"preicon1": "", "preicon2": "", "posticon1": "", "posticon2": "",
"hasprofile": true,
"freevalue": "",
"e": {
"iterations": {
"type": "int",
"label": "Iterations",
"order": "10",
"hint": "",
"autoupdate": false,
"badge": false,
"directedit": false,
"value": 5,
"min": 1,
"max": 20,
"step": 1,
"format": "",
"slider": 0
}
}
}
}
}
},
"files": {
"folders": ["", "/Allsky", "/Allsky/archives"],
"files": ["/image.jpeg"],
"selectedfolder": "/home/user/ostmedia"
},
"logs": [
{ "d": "2024-01-01T12:00:00.000", "c": "Focus", "t": "Module loaded", "l": 1 }
],
"controllerdata": {
"profiles": {
"Focus": ["default", "test"]
},
"currentconf": "default"
},
"lovs": {
"sharedLov": { "label": "Shared LOV", "type": "string", "values": {} }
}
}
}m = modules, p = properties, e = elements, l = globallovs, f = profile.
Normalize these to their full names when storing internally.
aa — Full module dump (module loaded/reloaded)
Sent when a module is loaded or reloaded at runtime. Contains the complete module state. Key starts with "aa".
{
"aa-dump all data": {
"Guider": {
"infos": { "modulelabel": "Guider", "description": "..." },
"f": { "name": "default", "changed": false },
"l": {},
"p": {
"status": { ... }
}
}
}
}ap — Full property replacement
Replaces one or more properties entirely. Key starts with "ap".
{
"ap-dump all property data": {
"Focus": {
"p": {
"parameters": {
"label": "Parameters",
"status": 0,
"permission": 2,
"enabled": true,
"showElts": true,
"hasGrid": false,
"hasGraph": false,
"rule": 0,
"e": {
"iterations": {
"type": "int",
"value": 7,
"min": 1, "max": 20, "step": 1,
"label": "Iterations", "order": "10", "hint": "",
"autoupdate": false, "badge": false, "directedit": false,
"format": "", "slider": 0
}
}
}
}
}
}
}ea — Set all element values (property-level update)
The most common update message. Sends the current values of all elements in a property. Contains values only — no metadata. Key is exactly "ea".
{
"ea": {
"Allsky": {
"p": {
"coming": {
"e": {
"sunrise": { "hh": 7, "mm": 6, "ss": 51 },
"sunset": { "hh": 21, "mm": 15, "ss": 1 }
}
}
}
}
}
}For simple types (int, float, bool, string, light), the value is a scalar:
{
"ea": {
"Focus": {
"p": {
"parameters": {
"e": {
"iterations": 5,
"exposure": 3.5,
"active": true
}
}
}
}
}
}ee — Set one element value
Updates a single element’s value. Same format as ea but for one element only. Key is exactly "ee".
{
"ee": {
"Focus": {
"p": {
"parameters": {
"e": {
"iterations": 7
}
}
}
}
}
}ee is backend-initiated onlyee is only ever sent on the backend’s own initiative (e.g. a device-driven value change, internal polling) — never as a direct response to a client write. Any response to a client-initiated SV or SA is always a full-property ea, regardless of how many elements actually changed.
ev — Set element with metadata
Updates an element’s value and its constraints (min, max, format). Key starts with "ev".
{
"ev-set one element value/min/max/format ": {
"Focus": {
"p": {
"parameters": {
"e": {
"temperature": {
"value": 12.5,
"min": -40.0,
"max": 80.0,
"format": "%.1f"
}
}
}
}
}
}
}ps — Property status update
Updates only the status and enabled fields of a property. Key starts with "ps".
{
"ps-only property state": {
"Focus": {
"p": {
"autofocus": {
"status": 2,
"enabled": true
}
}
}
}
}Status values: 0 Standby · 1 OK · 2 Busy · 3 Error
dm — Module deleted
Sent when a module is unloaded. The client should remove all data for this module. Key starts with "dm".
{
"dm-delete/remove module": {
"Focus": {}
}
}dp — Property deleted
Sent when a property is removed from a module. Key starts with "dp".
{
"dp-delete/remove property": {
"Focus": {
"p": {
"oldProperty": ""
}
}
}
}gc — Grid line created
Sent after a new row is added to a grid property. Key starts with "gc".
{
"gc-grid new line ": {
"Sequencer": {
"p": {
"sequence": {
"i": 3,
"values": {
"target": "M31",
"exposure": 120,
"count": 10
}
}
}
}
}
}i is the index of the newly created row. values is an object keyed by element name.
gu — Grid line updated
Sent when an existing grid row is modified. Key starts with "gu".
{
"gu-grid update line": {
"Sequencer": {
"p": {
"sequence": {
"i": 1,
"values": {
"target": "M42",
"exposure": 60,
"count": 20
}
}
}
}
}
}gd — Grid line deleted
Sent when a grid row is removed. Key starts with "gd".
{
"gd-grid delete line": {
"Sequencer": {
"p": {
"sequence": {
"i": 0
}
}
}
}
}i is the (zero-based) index of the deleted row. Rows after i shift down by one.
gr — Grid reset
Clears the entire grid for a property. Key starts with "gr".
{
"gr-grid reset": {
"Sequencer": {
"p": {
"sequence": {}
}
}
}
}lc / lu / ld — LOV events
lc — LOV created/merged (key starts with "lc"):
Module LOV:
{
"lc-lov create": {
"Focus": {
"l": {
"myLov": {
"label": "My LOV",
"type": "string",
"values": { "a": "Option A", "b": "Option B" }
}
}
}
}
}Controller LOV (no module wrapper, uses lovs key):
{
"lc-lov create": {
"lovs": {
"sharedLov": {
"label": "Shared LOV",
"type": "string",
"values": { "x": "Value X" }
}
}
}
}lu — LOV replaced (same format as lc, full replacement).
ld — LOV deleted (key starts with "ld"):
{
"ld-lov delete": {
"lovs": {
"sharedLov": {}
}
}
}fs / fl / fc — Profile events
Sent when a profile is saved, loaded, or modified.
{
"fs-profile saved": {
"Focus": {
"f": { "name": "myProfile", "changed": false }
}
}
}| Key prefix | Event |
|---|---|
fs | Profile saved |
fl | Profile loaded |
fc | Profile has unsaved changes |
l — Log entry
Sent for every log message generated by the server. Key is exactly "l".
{
"l": {
"d": "2024-01-01T12:00:00.000",
"c": "Focus",
"t": "Autofocus completed successfully",
"l": 1
}
}| Field | Description |
|---|---|
d | ISO 8601 datetime with milliseconds |
c | Context/source (module name or "WS") |
t | Message text (translated to client language) |
l | Log level: 0 Debug · 1 Info · 2 Warning · 3 Error · 4 Critical |
uc — Controller data update
Sent when global controller data changes: profiles, configurations, active INDI drivers, file lists (files, folders), system load (systemwatcher, every 10 s by default). Key starts with "uc". The possible keys are described in Controller, GlobalDatastore and files.
{
"uc-update controller data": {
"profiles": {
"Focus": ["default", "highres"],
"Guider": ["default"]
}
}
}The payload is {"key": value} where value can be a string, array, or object. Each message carries a single key, and its value entirely replaces the previous one.
af / df / ad / dd — File and folder events
Sent to all clients when the content of the watched file folder changes (see File system). Unlike other messages, the value is a bare string: the affected path, not wrapped in an object or array — the server only ever reports one path per message.
{ "af": "/Allsky/image_001.jpeg" }
{ "df": "/Allsky/old_image.jpeg" }| Key | Meaning |
|---|---|
af | File added to the selected folder |
df | File removed from the selected folder |
ad | Sub-folder added anywhere in the webroot |
dd | Sub-folder removed anywhere in the webroot |
af/df only concern the folder currently selected via FS — nothing is reported for other folders’ content. ad/dd are global: they fire for the whole webroot tree regardless of what is currently selected. See File system for the full picture.
qa — Answer to a module query
See the note on QY: this mechanism exists primarily for one module to query another internally. Documented here for completeness, not as a client-facing feature.
Reply to a QY query. QY/qa is a generic, per-module request/answer mechanism: a client (or another module, internally) asks a module a named question with parameters, and gets a named answer back. What query names a module actually understands, and what its answer looks like, is entirely up to that module — the protocol only defines the envelope, not the content.
{
"qa-query answer": {
"Sequencer": {
"q": {
"query": "profileduration",
"result": { "seconds": 3600 }
}
}
}
}result is whatever the module returned for that query — its shape depends entirely on query. A module with no matching case for the requested query name returns an empty result, silently.
Today only the Sequencer module implements a real answer (profileduration), used internally by the Planner module rather than by any documented client workflow. No other shipped module responds meaningfully to any query name — this mechanism exists and is fully wired, but there is little to query in practice right now.
xx — Keepalive pong
Sent by the server in response to a client heartbeat (XX). No useful payload; clients can ignore it.
{ "xx": {} }