Bus and events
Modules don't call each other directly. They publish events on the internal bus and subscribe to events they care about. This keeps the dependency graph shallow and means new modules can plug in without modifying existing ones.
How it works
A single in-process pub/sub bus, one topic per named event. When something publishes an event, every subscriber to that topic is called in turn, in the order they subscribed. Delivery is synchronous — the publisher waits for the handlers to run — but a handler that raises an error is caught and logged, so one misbehaving subscriber can never stop the others (or block the publisher).
publisher.publish("solo.reading", payload, source="solo")
↓
bus dispatches to every subscriber of "solo.reading"
↓
SafetySupervisor handler updates the verdict
↓
audit handler writes a row
↓
web server forwards to connected WebSocket clients
A subscriber can also listen on the special topic * to receive every event that flows through the bus, which is handy for logging and audit.
Topics in use today
Safety and sources
solo.reading,solo.error,solo.stale— the Solo weather/safety sensor: each reading, a read error, and a "we haven't heard from it recently" warningsafety.state_changed,safety.tick— the Safety Supervisor's verdict changing (SAFE / WARNING / UNSAFE / UNKNOWN) and its periodic heartbeathealth.state— the Health Monitor's overall level and per-check detail (memory, disk, heartbeat, wedged workers…)health.memory_countdown— the dismissible countdown before a soft memory trip forces a safe state, and its dismissal
Response Engine
response.plan_run— a response plan started because of a safety transitionresponse.action_done,response.action_failed— the outcome of each action in a plan- plus any custom topic a plan's
broadcastaction names (defaultresponse.event)
Equipment
equipment.device— ASCOM and Alpaca driver/device state (per role)equipment.available,equipment.unavailable— a transport coming up or going awayequipment.worker_stalled— the liveness watchdog spotting the ASCOM worker wedged inside a blocking callguiding.state,guiding.connection,guiding.raw_event— PHD2 guider status, connect/disconnect, and raw PHD2 events
Console and long-running jobs
console.command— the result of a Console action (also used to surface a few system events, like a health escalation, in the event feed)console.job— background-job progress (start/progress/done/failed)console.jog— a manual jog stopping itself (e.g. an auto-stop)autofocus.sample— live focus-run progress: each V-curve measurement, focus-star slews, warnings and errors, so the UI can plot the sweep as it builds
flat.progress— progress of an Auto Flat run
Sequencer and blockscript
sequencer.state,sequencer.event— the sequence's overall state and its journal of eventsblockscript.state,blockscript.action— a blockscript's run state (with a reason) and the outcome of each action it runs
Displays and files
cctv.snapshot,cctv.error— CCTV camera frames and errorsallsky.snapshot,allsky.error— all-sky camera frames and errorsfits.new_file,fits.error— the FITS watcher spotting a new saved frame, or failing to read oneplanetarium.state,planetarium.response,planetarium.target_selected— Cartes du Ciel connection state, command responses, and a target picked in the planetarium
Extending
Adding a new module means: instantiate a driver, publish its events on the bus, write a small status reader for module_status(), and add a handler in the web server that subscribes to the new topic and forwards it to WebSocket clients. There's no central registry to update — modules find each other only through the topics they publish and subscribe to.