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

KeyDescription
dFull initial dump (response to DU)
aaFull module dump (module loaded/reloaded)
apFull property replacement
eaSet all element values for a property
eeSet one element value
evSet element value with updated constraints
psProperty status and enabled update
dmModule deleted
dpProperty deleted
gcGrid line created
guGrid line updated
gdGrid line deleted
grGrid reset (all rows cleared)
lcLOV created or merged
luLOV replaced
ldLOV deleted
fsProfile saved
flProfile loaded
fcProfile has unsaved changes
lLog entry
ucController data update
afFile added
dfFile deleted
adDirectory added
ddDirectory deleted
qaAnswer to a module query (inter-module mechanism, not client-facing)
xxKeepalive 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": {} }
    }
  }
}
Wire format abbreviations

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 only

ee 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 prefixEvent
fsProfile saved
flProfile loaded
fcProfile 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
  }
}
FieldDescription
dISO 8601 datetime with milliseconds
cContext/source (module name or "WS")
tMessage text (translated to client language)
lLog 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" }
KeyMeaning
afFile added to the selected folder
dfFile removed from the selected folder
adSub-folder added anywhere in the webroot
ddSub-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

Inter-module communication, not a client feature

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.

Not much to see yet

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": {} }