Controller, GlobalDatastore and files

This page describes what falls outside the “modules → properties → elements” scheme of the previous pages: the controller data, the central GlobalDatastore module and the server’s file system. A client that only displays modules does not need them; a client that wants to offer what a global settings page provides (configurations, INDI drivers, server load, equipment, images) has to handle them.

Controller data (controllerdata)

The controller is the server component that loads the modules and drives the embedded INDI server. It exposes its own data in the dump, under the controllerdata key (see d), then keeps it up to date with uc messages.

KeyContent
availableconfsSaved module configurations: object configuration name → list of {modulelabel, moduletype, profilename}
currentconfName of the current configuration
bannerServer banner (free text, --banner option)
gitServer version: Gitdate, Githash, Gitmessage, Gittag
librariesAvailable module libraries, one metadata object per library — keys are prefixed with libost (libostfocus, libostdummy…); strip it before passing to ML. Read again by MR
profilesAvailable profiles: object transformed library name → list of profile names — see the keying convention below
indiserver"N" if the embedded INDI server is disabled (--indiserver); otherwise "U" once it is confirmed running, "D" once it isn’t (startup fails, crashes, or is stopped)
indidriversCatalogue of installed INDI drivers: list of {binary, family, label, mdpd}
indiactivedriversINDI drivers currently running
indiconfigurationsSaved INDI driver configurations: object configuration name → list of driver labels
systemwatcherServer load, see below
files, foldersCopy of d.files.files and d.files.folders, see File system
profiles keying: not the library name as-is, not infos.template

profiles is keyed by the module’s library name (the same string passed as lib to ML, i.e. the key in controllerdata.libraries with the libost prefix stripped) — but transformed: first letter uppercased, the rest lowercased ("focus" → "Focus", not a simple capitalize — a library name containing another uppercase letter would still be flattened, e.g. a hypothetical "myFocus" → "Myfocus"). This key is neither the module’s instance name/label, nor necessarily identical to infos.template (they usually match in practice, but the two are set independently and nothing in the protocol guarantees it).

This convention is undocumented anywhere else and isn’t enforced by the backend — it was reverse-engineered from Rooster’s own lookup logic. Two independent clients built from this documentation alone got tripped up by it (a profile picker silently rendering empty). If you need to look up a module’s available profiles, replicate this exact transform on the library name rather than guessing.

uc: one key per message, value replaced as a whole

A uc message carries a single key of controllerdata, and its value entirely replaces the old one (no merge). A client that only reads the first key of the payload (as Rooster does) is therefore correct as long as the server sends only one key per message, which is the case today.

systemwatcher: server load

The server measures its own load at regular intervals and broadcasts it to all clients. The interval is set with the --systemwatchinterval option (seconds, 10 by default, 0 disables it). The message is an ordinary uc, and the latest value is also present in controllerdata.systemwatcher of the dump.

{
  "uc-update controller data": {
    "systemwatcher": {
      "cpu": { "load_1m": 10.42, "load_5m": 9.79, "load_15m": 4.51 },
      "ram": { "used_mb": 12194, "total_mb": 31941 },
      "disks": [
        { "device": "/dev/nvme0n1p2", "mountpoint": "/", "used_gb": 789.4, "total_gb": 915.3 }
      ],
      "network": [
        { "iface": "enp4s0", "rx_kbps": 16.27, "tx_kbps": 448.8 }
      ],
      "temperatures": [
        { "type": "Tctl", "temp_c": 75.375 }
      ]
    }
  }
}
FieldMeaning
cpu.load_1m / load_5m / load_15mSystem load average (as in /proc/loadavg), not a utilisation percentage: 1.0 = one core saturated on average
ram.used_mb / total_mbUsed memory (total − available) and total memory, in MB
disks[]One entry per mounted storage device (/dev/*, excluding loop): device, mountpoint, used_gb, total_gb
network[]One entry per interface (excluding lo): rx_kbps / tx_kbps rates measured between two messages; the first measurement of an interface is 0
temperatures[]Processor hardware sensors: type, temp_c. The list can be empty depending on the machine (only Intel coretemp and AMD k10temp sensors are read)
Don’t assume the arrays are filled

disks, network and especially temperatures can be empty. A client should hide the corresponding section rather than display an empty array. The protocol does not provide the server’s number of cores: a CPU gauge therefore cannot be a real percentage, the value has to be shown as a load.

Configurations and module lifecycle

A configuration is a named set of modules (library, label, profile) that the server can reload in one go. The list is in controllerdata.availableconfs, the active configuration in controllerdata.currentconf.

ActionCommandDetail
Load a configurationCLLoads all the modules of the configuration
Save the current configurationCSUnder the given name
Load a moduleMLLibrary, label, optional profile
Unload a moduleMKBy module name

The modules actually loaded are those of the dump (d.m); they appear and disappear through aa and dm. A configuration never contains GlobalDatastore (see below), which is always present.

A configuration silently includes the active INDI drivers

CS also saves the currently running INDI drivers alongside the module list, and CL restarts them — but this is invisible in controllerdata.availableconfs, whose shape only describes modules ({modulelabel, moduletype, profilename}), never drivers. A client building a configuration editor that only shows/edits the module list is not showing the whole picture of what will actually happen on CL.

No delete operation

There is no command to remove a saved configuration — CL and CS are the only two operations. A configuration a client no longer wants stays in controllerdata.availableconfs indefinitely (it can only be overwritten under the same name by another CS).

INDI server and drivers

Unless controllerdata.indiserver is "N", the server drives an embedded INDI instance. The YA/YZ/YL/YR/YS commands start it, stop it and manage the drivers; controllerdata.indidrivers gives the available catalogue and controllerdata.indiactivedrivers what is running. binary is the value to pass as driver in YL, YR and YS.

controllerdata.indidrivers and controllerdata.indiactivedrivers are both lists of objects {binary, family, label} (indidrivers additionally has mdpd, see the table above).

A whole saved set of drivers can be started in one go with YK, by name, from controllerdata.indiconfigurations — saved by CS alongside a module configuration (see the warning above), but loadable on its own, independently of CL.

YA/YZ/YL/YR/YS/YK have no dedicated acknowledgment

None of these commands trigger a confirmation message of their own. Success or failure has to be inferred indirectly: YL/YR/YS/YK are followed by a uc update of indiactivedrivers reflecting the requested change (but this only confirms the server attempted it, not that the underlying INDI driver actually came up), while YA/YZ produce no uc at all — only l (log) messages, which are meant for a human, not for a client to parse programmatically. controllerdata.indiserver does track the embedded server’s own up/down state live (see the table above), but that is a side effect of YA/YZ/a crash, not a direct acknowledgment of the command that caused it.

The GlobalDatastore module

GlobalDatastore is a module like any other in the dump (d.m.GlobalDatastore, with properties, elements, grids, profiles), but its role is central:

  • it is created by the server before all other modules and is always present: MK on it is refused, and it is never included in a configuration;
  • it holds the shared equipment reference that all the other modules consult: optics, locations, equipments (sets of devices) and INDI servers.

Its grids feed lists of values (LOVs) of the same name, present in d.lovs (optics, locations, equipments, servers) and rebuilt on every change of the grid. The other modules use them like this: choosing an optic fills in the focal length and diameter, choosing a location fills in latitude, longitude and altitude, choosing an equipment set resolves the devices (camera, mount, focuser, filter wheel, guide camera, GPS, dome, weather) and the INDI server to contact.

Exposed properties:

PropertyRole
devicesSelected equipment set and GPS (equipments, gps)
devicesactionsButtons: connect/disconnect the devices, load the configurations (condevs, discondevs, loadconfs)
serverINDI server host and port (host, port)
serveractionsButtons: connect/disconnect the INDI server (conserv, disconserv)
startupAuto-start checkboxes (indiatstart, devatstart, confsatstart)
equipmentsGrid of equipment sets
locationsGrid of locations (icons refresh = read the GPS, add = add the current line)
opticsGrid of optics
serversGrid of INDI servers

All its properties are handled with the standard messages (SV, SA, GC…, I3/I4 on locations); a generic client displays them like any other module. Rooster presents them in a dedicated tab of its settings page, keeping the properties that have a profile (hasprofile) and grouping them by level1/level2.

File system

The server publishes the list of the files it produces (images, sequences, archives) in the --webroot folder, without the client having to query for it. This mechanism is independent of the modules.

In the dump: d.files

"files": {
  "folders": ["", "/Allsky", "/Sequencer/gam Cyg/LIGHT/Luminance"],
  "files": ["/image1.jpeg", "/report.txt"],
  "selectedfolder": "/home/gilles/ostmedia"
}
FieldContent
foldersAll the sub-folders of the webroot, recursively, paths relative to the webroot starting with /. The root is the empty string "". Not sorted.
filesThe files of the selected folder only (not recursive), paths relative to the webroot starting with /. All files, no extension filter.
selectedfolderThe selected folder, as an absolute path on the server’s file system (not a relative one).

There is no pagination and no size limit: on a very full webroot, the list can be long.

Choosing the folder: FS

FS designates the folder to watch and list files from. The server watches a single folder for file content at a time; the folder tree itself (folders) is tracked independently, see below.

The selected folder is server-wide

There is no per-client folder: an FS changes the file list seen by all connected clients. A client should not use it for simple “local” browsing if other clients may be connected.

The path is validated: the target must exist and resolve inside the webroot (no ../ escape). An invalid request is rejected server-side (logged, no state change) — it does not silently return an empty list.

A successful FS replaces the file list wholesale: the client receives one uc files message with the complete new list, not a stream of af/df — selecting a folder is not a filesystem mutation, nothing was actually added or removed. There is no message announcing the new selectedfolder itself, which only exists in the dump.

Folder tree vs. selected folder’s files

folders (the whole recursive tree) and files (the selected folder’s content) do not share the same scope:

ScopeLive updates
folders / ad / ddThe whole webroot, recursively — independent of what is currently selectedAlways, regardless of the current FS selection
files / af / dfThe selected folder onlyOnly for changes to the folder the last FS selected

A folder created or deleted anywhere in the webroot is reported live via ad/dd even if no client currently has it selected. A file created or deleted is only reported via af/df if it happens in the folder a client’s FS last selected — files/af/df say nothing about other folders’ content, a client has to FS into them to find out.

Updates: af / df / ad / dd

When the folder tree or the selected folder’s content actually changes, the server sends one message per affected path — the value is the bare path itself, not wrapped in an object or array:

{ "af": "/Sequencer/gam Cyg/LIGHT/Luminance/img_001.FITS" }
{ "df": "/report.txt" }
{ "ad": "/Sequencer/new target" }
KeyMeaning
afFile appeared in the selected folder
dfFile disappeared from the selected folder
adSub-folder appeared anywhere in the webroot
ddSub-folder disappeared anywhere in the webroot

Each message carries exactly one path (N files = N messages) — see af/df/ad/dd for the full reference. A rename is reported as a deletion followed by a creation: the server diffs a before/after listing, it does not detect renames as such.

After the per-path messages, the server sends uc files and/or folders with the complete updated list — but only for whichever of the two actually changed on this pass, never unconditionally. A client can therefore either apply the per-path events incrementally, or simply replace its list(s) with the matching uc message(s).

Building a file URL

The paths in files are relative to the webroot and start with /. A file’s URL is the server’s media base URL (http(s)://hostname/ostmedia/, see the “Media URLs” note of the Data model) followed by the path. Mind the leading /: concatenating it to a base that already ends with / gives a double //.

What the event does not do

The server detects the addition, removal and renaming of files, not a change to the content of an existing file: an image rewritten under the same name produces no event. This is the case for the modules’ preview images, whose URL stays identical on every shot: a client must add a cache-busting parameter (?t= + timestamp) when it reloads an image after an update of its property.