Appearance
Reference
Lookup tables for everything the package installs and everything the two binaries accept. For what any of it means, see the chapter it links to.
Package
| Item | Value |
|---|---|
| Debian package | muxen-synapse |
| Architecture | any, Multi-Arch: foreign |
| Hard dependencies | muxen-systemd, muxen-sextant (>= 2.2.0), muxen-sensors (>= 5.0.0) |
| Recommended | muxen-boat (>= 9.4.0), muxen-energy, muxen-nmea2000 (>= 3.5.0) |
| Debian triggers | activate muxen-restart-target; activate-noawait nginx-reload — a running nginx reloads at the end of the transaction, so a changed muxen-ws-synapse.conf is served at once |
| Lua runtime | Lua 5.5, vendored and statically linked — no system Lua needed |
| Source | https://code.muxen.fr/muxen/brain/synapse |
Installed files
| Path | Content |
|---|---|
/usr/bin/muxen-synapsed | The daemon |
/usr/bin/muxen-synapse-ctl | The operator CLI |
/usr/share/muxen-synapse/*.lua | Template library, read-only |
/usr/share/muxen-synapse/fsm.lua | The state-machine module, loaded by the engine at runtime |
/usr/share/muxen-synapse/index.json | Generated template manifest (metadata + config schemas) |
/usr/lib/systemd/system/muxen-synapse.service | systemd unit |
/etc/nginx/snippets/muxen-synapse-templates.conf | Serves the template library over HTTP |
/etc/nginx/snippets/muxen-ws-synapse.conf | MQTT-over-WebSocket proxy at /ws/synapse |
/usr/share/bash-completion/completions/muxen-synapsed | Completion for muxen-synapsed |
/usr/share/bash-completion/completions/muxen-synapse-ctl | Completion for the CLI |
Runtime paths
| Path | Access | Content |
|---|---|---|
/etc/muxen/deploy.json | read-only | The synapse section — the instance list. Owned by the config daemon |
/var/lib/muxen-synapse/state.json | read-write | Enable overrides, snoozes, script state, FSM positions. The only writable path |
/usr/share/muxen-synapse/ | read-only | Template library |
systemd unit
| Directive | Value |
|---|---|
| Unit name | muxen-synapse.service |
Description | Muxen Synapse — Lua automation engine |
PartOf | muxen.target |
After | mosquitto.service |
WantedBy | muxen.target |
ExecStart | /usr/bin/muxen-synapsed |
User / Group | muxen / muxen (created by muxen-systemd) |
StateDirectory | muxen-synapse, mode 0750 |
Restart / RestartSec | always / 30 |
TimeoutStopSec | 20 (above the 10 s onStop budget) |
Hardening: NoNewPrivileges, ProtectSystem=strict, ProtectHome, PrivateTmp, PrivateDevices, ProtectKernelModules, ProtectKernelTunables, ProtectControlGroups, ProtectClock, ProtectKernelLogs, ProtectHostname, ProtectProc=invisible, ProcSubset=pid, RestrictRealtime, RestrictSUIDSGID, RestrictNamespaces, LockPersonality, MemoryDenyWriteExecute, DevicePolicy=closed, empty CapabilityBoundingSet and AmbientCapabilities, SystemCallArchitectures=native, SystemCallFilter=@system-service minus @privileged @debug @module @mount @reboot @swap @raw-io @clock @resources, SystemCallErrorNumber=EPERM.
Address families are limited to AF_UNIX AF_INET AF_INET6 and network interfaces to lo — the daemon can only reach a loopback broker when run under the unit.
muxen-synapsed — daemon CLI
usage: muxen-synapsed [OPTION]| Flag | Default | Meaning |
|---|---|---|
-h, --help | Print usage and exit | |
-v, --verbose | off | Increase verbosity. Repeatable: -v debug, -vv trace. Clamped at trace |
-V, --version | Print the git describe version and exit | |
--deploy=PATH | /etc/muxen/deploy.json | Deployed-config path |
--state=PATH | /var/lib/muxen-synapse/state.json | State file path |
--templates=DIR | /usr/share/muxen-synapse | Template library directory |
--host=HOST | 127.0.0.1 | MQTT broker host |
--port=PORT | 1883 | MQTT broker port (1–65535) |
--prefer-mqtt-time | auto | Always take wall-clock from the boat's system/time |
--prefer-local-time | auto | Always use the local system clock |
--prefer-mqtt-time and --prefer-local-time are mutually exclusive. The default is automatic: a loopback --host (127.0.0.1, localhost, ::1, unset) uses the local clock, any other host tracks boat time. The resolved source is printed in the startup banner.
Verbosity levels
| Level | Flag | What it logs |
|---|---|---|
| Normal | (none) | Errors only — daemon errors and per-script last_error |
| Debug | -v | Lifecycle, enable/disable, FSM transitions, popups, watchdog aborts |
| Trace | -vv | Everything above plus per-message dispatch, timer firings, FSM detail, full Lua tracebacks |
Errors always go to stdout, so they appear both at a console and in journald under systemd. Verbosity changes stdout only — it never changes what is published on synapse/….
Signals
| Signal | Effect |
|---|---|
SIGTERM, SIGINT | Graceful shutdown: persist state.json first, then run onStop for each running instance within the 10 s budget, then exit 0. A second signal is ignored |
SIGUSR1 | Print a one-line-per-instance audit to stdout: run state, effective enabled, consecutive fault count, Lua heap usage, pending popup or last error. Repeatable, no side effects |
Exit status
| Code | Meaning |
|---|---|
0 | Normal shutdown; also -h, -V, and a CLI usage error (usage is printed to stdout and the process exits 0) |
1 | Engine initialisation failed |
Environment overrides
Read from the process environment. The unit ships every default as an Environment= line mirroring src/daemon/context.h; override them with a drop-in (systemctl edit muxen-synapse). Applied before the command line, so an explicit flag always wins.
SYNAPSE_MQTT_HOST is only useful for another local address: the unit sets RestrictNetworkInterfaces=lo, which silently drops traffic to a remote broker.
| Variable | Overrides | Notes |
|---|---|---|
SYNAPSE_DEPLOY_PATH | --deploy | |
SYNAPSE_STATE_PATH | --state | |
SYNAPSE_TEMPLATE_DIR | --templates | |
SYNAPSE_MQTT_HOST | --host | Falls back to MQTT_HOST |
SYNAPSE_MQTT_PORT | --port | Falls back to MQTT_PORT. Ignored unless 1–65535 |
SYNAPSE_LUA_MEM_MAX | per-automation Lua heap ceiling, in bytes | 0 disables the cap. No equivalent CLI flag |
max_runtime_ms is deliberately not overridable from the environment — it is the locked engine-wide cap.
muxen-synapse-ctl — operator CLI
muxen-synapse-ctl [--host H] [--port P] [--json] <command> [args]| Command | Arguments |
|---|---|
list | — |
debug <id> | --what a,b,… (sections: fsm, variables, config, state, wiring, commands) |
config <id> | — |
start <id> | — |
stop <id> | — |
restart <id> | — |
journal <id> | -f/--follow, -n N/--lines N (default 50) |
Global flags: --host, --port, --json, -h/--help, -V/--version.
Broker resolution: --host/--port → $SYNAPSE_MQTT_HOST / $SYNAPSE_MQTT_PORT → $MQTT_HOST / $MQTT_PORT → 127.0.0.1:1883.
| Exit code | Meaning |
|---|---|
0 | Success |
1 | Request timed out (unknown id or daemon down); or start/restart did not reach running; or stop left it running |
2 | Usage error |
127 | journal could not execute journalctl |
Timeouts: 2 s to connect, 500 ms to settle retained statuses for list, 3 s for a debug reply, 4 s for a start/stop to be reflected in the status.
Runtime limits
Engine-wide, not per-script. These are the locked v1 values.
| Setting | Value | Meaning |
|---|---|---|
max_runtime_ms | 50 | Hard cap on one Lua callback's CPU time. Not overridable by a script or the environment |
watchdog_check_instructions | 1000 | How often the watchdog samples the CPU clock, in Lua VM instructions |
status_period | 30 s | Heartbeat interval for republishing every instance's retained status |
expireAfterSec | 60 s | Freshness window advertised in each status metadata — twice the heartbeat |
default_expire_sec | 30 s | Fallback freshness window for a cached device/<fc>/<inst>/{io,state} payload that carries no metadata.expireAfterSec of its own. synapse.variable.get no longer consults it: an app/sensor/<name> payload without metadata.rxTimestamp and expireAfterSec is permanently stale |
sextant expireAfterSec | 3600 s | Freshness window for the cached sextant/* topics |
fault_threshold | 5 | Consecutive callback faults before an instance is auto-disabled. A success resets the count |
shutdown_grace | 10 s | Aggregate budget for running all onStop hooks on SIGTERM. Kept below the unit's 20 s TimeoutStopSec |
lua_mem_max_bytes | 16 MB | Per-automation Lua heap ceiling. Overridable via SYNAPSE_LUA_MEM_MAX; 0 disables |
| inline script soft cap | 64 KB | A larger inline script warns but still runs |
| popup timeout | 300 s | Default when a prompt sets no timeout of its own |
| MQTT client id | muxen-synapse | |
| MQTT keepalive | 5 s |
deploy.json instance fields
Under the synapse object, keyed by instance id.
| Field | Type | Meaning |
|---|---|---|
| key | string [a-zA-Z0-9-]+ | Instance id. Unique. Becomes synapse/<id> |
template | string | Template name (filename without .lua). Mutually exclusive with script |
script | string | Inline Lua source. Mutually exclusive with template |
config | object | Per-instance tuning, validated against the script's schema |
enabled | boolean | Deployed default, defaults to true. false is a hard force-off |
name | string | Display name (English), overriding the script's own |
name.<LANG> | string | Localised display names, e.g. name.FR, name.ES |
state.json structure
| Key | Meaning |
|---|---|
automations.<id>.enabled | Runtime enable override; outranks the deploy default but not a force-off |
automations.<id>.snoozes.<key>.until | Snooze expiry, ISO-8601, keyed by prompt key |
automations.<id>.state | The script's synapse.state key/value store |
automations.<id>.fsm.<name> | Current state of each named state machine |
automations.<id>.fsmVersion | Script version the FSM positions were written under; a change resets them |
Config schema field types
Declared in synapse.meta{ config = { … } }. Each entry takes key (required) and type, plus optional default, label, description, min, max, options (for enum) and required.
type | Value shape | Engine validation |
|---|---|---|
number | number | Clamped to min/max; coerced from a numeric string |
integer | integer | As number, truncated to an integer |
boolean | boolean | Coerced where unambiguous |
enum | one of options | Falls back to the default when not in options |
output | IOChannel | Must be { deviceId, index }; stored strictly |
outputs | array of IOChannel | Every element must be valid; empty array allowed; any bad element falls back to the default |
button | IOChannel | As output — the type is a frontend picker hint |
tor | IOChannel | Digital input, read with readTOR |
analog | IOChannel | Analog input, read with readAnalog |
| anything else | string | Unrecognised type names are treated as string |
An IOChannel is { deviceId, index }, both integers, with deviceId = functionCode × 64 + instance (0–4095) and index the 0-based channel (≥ 0). Extra keys are dropped.
MQTT topics at a glance
Full payload shapes in MQTT interface.
| Topic | Direction | Retained |
|---|---|---|
synapse/<id> | engine → all | yes |
synapse/<id>/command | frontend → engine | no |
synapse/<id>/feedback | frontend → engine | no |
synapse/<id>/debug | requester → engine | no |
synapse/<id>/debug/dump | engine → requester | no |
synapse/debug | requester → engine | no |
synapse/debug/dump | engine → requester | no |
synapse/validate | requester → engine | no |
synapse/validate/result | engine → requester | no |
app/synapse/info | engine → all | yes |
Topics synapse reads: app/sensor/<name> (cached), sextant/* (cached), device/<fc>/<inst>/{io,state} (cached), system/time, and anything a script subscribes to. Topics synapse writes: device/<fc>/<inst>/command, plus anything a script publishes.
HTTP endpoints
Served by nginx from /etc/nginx/snippets/muxen-synapse-templates.conf.
| URL | Content |
|---|---|
/synapse/templates/ | JSON directory listing of the library |
/synapse/templates/index.json | Template manifest: name, title, description, version, config schema |
/synapse/templates/<name>.lua | Template source, served as text/plain |
/ws/synapse | MQTT-over-WebSocket proxy to 127.0.0.1:1884 |
nginx only ever serves this directory; it never writes. Creating an instance from a template goes through the config daemon, which owns deploy.json.
The WebSocket snippet requires map $http_upgrade $connection_upgrade { default upgrade; '' close; } in the surrounding http block, as for /ws/boat.
Both the site that includes these two snippets and that map block are shipped by the boat's UI interface package (muxen-interface-*), one site file per boat. There the two includes carry a trailing *, which makes them optional: a Brain whose interface expects synapse but does not have muxen-synapse installed still passes nginx -t and starts, and /synapse/templates/ and /ws/synapse simply do not exist. So the URLs above are live only on a boat whose interface site includes them and where this package is installed.
Logging
Every line the daemon or a script emits carries a tag and an sd-daemon priority prefix, so journald records it at the right level without a libsystemd dependency:
<6>[synapse:<id>] messageEngine-scope lines use the tag [synapse] with no id. synapse.log.debug lines are suppressed unless -v or higher is in effect; every other level is always emitted. In the sandbox, print is redirected to synapse.log.info.
muxen-synapse-ctl journal <id> is journalctl -u muxen-synapse --grep '\[synapse:<id>\]' with -o short-iso --no-hostname.
const.FunctionCode
The Lua constants table, generated at build time from libstdmuxen's MUXEN_FUNCTION_* defines in stdmuxen/functions.h. Names match the canonical @muxen/device-id TypeScript enum; the mapping from numeric code to name lives in tools/gen-function-codes.
| Constant | Code | Constant | Code | |
|---|---|---|---|---|
Button | 0 | CurrentLimiter | 19 | |
Bloc8 | 1 | WaterMaker | 20 | |
Hydrogenerator | 2 | AirConditioning | 21 | |
Interconnexion | 3 | SFSPReceptor | 22 | |
PowerGenerator | 4 | SFSPSwitch | 23 | |
Battery | 5 | MagicTrim | 24 | |
PowerConverter | 6 | BatterySwitch | 25 | |
Motor | 7 | CGS | 26 | |
Lighting | 8 | Alternator | 27 | |
Display | 9 | ThermalEngine | 28 | |
SolarPanel | 10 | KeelMotor | 29 | |
WindTurbine | 11 | BatteryConcentrator | 30 | |
NavigationInstruments | 12 | BusExtender | 31 | |
ImocaKeelTeam | 16 | BowThruster | 32 | |
GenericIO | 17 | |||
BlinkKeypad | 18 |
Codes 13–15 are reserved (internal IMOCA keel) and are never emitted. A constant appears only when libstdmuxen's header actually defines that code, so the installed table is the intersection of this mapping and the header.
The table and const itself are read-only proxies: every assignment raises (const.FunctionCode is read-only), existing names included; an unknown name reads as nil; pairs() yields every constant. See Lua API reference.
SFSPRecepter (22) is a deprecated alias of SFSPReceptor, kept in the table so scripts written before @muxen/device-id 9.5.1 renamed the member keep working. Use SFSPReceptor in new scripts. Deprecated aliases are listed in k_deprecated_aliases in tools/gen-function-codes.
