Appearance
Reference
Everything muxen-sextant exposes, in tables. Every entry here is taken from the software as it ships: its own code, its systemd unit and its package.
Command line
muxen-sextant takes no positional arguments. Options are grouped; --help lists the groups and --help-report, --help-mqtt and --help-verbose expand them.
General
| Option | Short | Type | Default | Effect |
|---|---|---|---|---|
--version | -v | flag | — | print muxen-sextant: version <v> and exit 0 |
--data-dir | DIR | see below | directory holding WMM.COF and tides.db |
--data-dir is resolved at startup by trying, in order, /var/lib/muxen-sextant and ./data, taking the first that exists as a directory; if neither does, it falls back to /var/lib/muxen-sextant. That is what lets the binary run straight from a source checkout.
Report options
All reports are off by default. The shipped systemd unit passes --report-all.
| Option | Type | Default | Effect |
|---|---|---|---|
--report-all | flag | off | enables all four report flags below |
--report-astro | flag | off | publish {prefix}/sun and {prefix}/moon |
--report-magnetic | flag | off | publish {prefix}/magnetic |
--report-tides | flag | off | publish {prefix}/tides |
--report-skymap | flag | off | publish {prefix}/skymap |
--wmm-cof-path | PATH | {data-dir}/WMM.COF | World Magnetic Model coefficient file |
--tides-db-path | PATH | {data-dir}/tides.db | tidal harmonics database |
--tide-radius-nm | double | 50.0 | station search radius, nautical miles |
--tide-recompute-min | int | 30 | minimum minutes between tidal recomputations |
--skymap-max-mag | double | 5.0 | faintest star magnitude included |
--recompute-drift-deg | double | 1.25 | position drift, in degrees of latitude or longitude, that triggers a recompute |
--recompute-interval-min | int | 15 | minutes between periodic recomputes |
--report-all is expanded immediately after parsing, so it can be combined with individual flags in any order.
MQTT options
| Option | Type | Default | Effect |
|---|---|---|---|
--no-mqtt | reverse flag | MQTT enabled | disable all publishing and subscribing |
--mqtt-host | HOST | 127.0.0.1 | broker host |
--mqtt-port | int | 1883 | broker port |
--mqtt-username | USER | unset | broker username |
--mqtt-password | PASS | unset | broker password |
--mqtt-client-id | ID | muxen-sextant | MQTT client identifier |
--mqtt-topic-prefix | PREFIX | sextant | prefix for published reports |
--mqtt-nmea-prefix | PREFIX | nmea | prefix for the subscribed input topics |
Verbose options
| Option | Effect |
|---|---|
--verbose-all | enable all of the below |
--verbose-astro | one line per sun and moon publish |
--verbose-magnetic | one line per magnetic publish |
--verbose-tides | tidal computation and station counts |
--verbose-skymap | star, constellation and planet counts per publish |
--verbose-mqtt | connection, reconnection and message tracing |
--verbose-recompute | the reason for every recompute trigger |
Verbose output goes to stderr. The always-on Config:, Main: and per-report startup lines go through the GLib log handler, which timestamps them HH:MM:SS.mmm and sends informational messages to stdout and warnings to stderr.
MQTT interface
Subscribed
Both at QoS 0, subscribed from the connect callback so they are re-subscribed automatically after a reconnection.
| Topic | Fields read |
|---|---|
{nmea-prefix}/navigation | data.position.latitude, data.position.longitude |
{nmea-prefix}/datetime | data.now — presence only; the value is never used in any computation |
data.position.altitude is present in the input and is not read. The magnetic model is evaluated at 0 km above the WGS-84 ellipsoid.
Published
All five are published at QoS 0 with the retain flag set, in the MUXEN envelope { "data": {…}, "metadata": {…} }, serialised compactly (no pretty-printing).
| Topic | Content | expireAfterSec |
|---|---|---|
{prefix}/sun | solar rise/set/transit, three twilights, current position | 3600 |
{prefix}/moon | lunar rise/set/transit, phase, illumination, phase name, age, lit side and angle, days to new/full, track of one pass | 3600 |
{prefix}/magnetic | declination, inclination, total intensity | 3600 |
{prefix}/tides | array of nearby stations with curves | 3600 |
{prefix}/skymap | stars, constellation segments, planets | 3600 |
metadata.rxDate is an ISO-8601 UTC timestamp taken at publish time.
app/sextant/info — daemon info
Retained, QoS 1, on a fixed topic that --mqtt-topic-prefix does not change: every MUXEN daemon publishes the same shape on app/<daemon>/info, so a screen lists them all with one app/+/info subscription.
json
{"name":"muxen-sextant","version":"v2.3.0","hostname":"brain-3",
"features":["sun","moon","magnetic","tides","skymap"],"online":true,
"metadata":{"rxdate":"2026-09-22T07:58:15.659Z","rxTimestamp":1790063895,
"expireAfterSec":3124137600}}| Key | Meaning |
|---|---|
name | muxen-sextant |
version | build version (git describe); for display only, never branch on it |
hostname | host the daemon runs on |
online | true on every (re)connect; false on a clean shutdown, and as the MQTT last will when the connection is lost |
features | the report topics this instance publishes (below) |
metadata | rxdate (lowercase, unlike the reports' rxDate), rxTimestamp, and expireAfterSec ≈ 99 years: liveness is online, not age |
A feature is listed only when its report is enabled and started: a tides report with no readable tide database, for instance, leaves tides out. Clients ignore features they do not know.
| Feature | Published topic |
|---|---|
sun | {prefix}/sun (astro report) |
moon | {prefix}/moon (astro report) |
magnetic | {prefix}/magnetic |
tides | {prefix}/tides |
skymap | {prefix}/skymap |
{prefix}/sun — data
| Key | Type | Optional | Unit |
|---|---|---|---|
rise, set, transit | string | yes | ISO-8601 UTC |
riseTimestamp, setTimestamp, transitTimestamp | int64 | yes | Unix epoch seconds |
riseAzimuth, setAzimuth | double | yes | degrees, 0 = North |
transitAltitude | double | yes | degrees |
civilTwilightStart, civilTwilightEnd | string | yes | ISO-8601 UTC |
nauticalTwilightStart, nauticalTwilightEnd | string | yes | ISO-8601 UTC |
astronomicalTwilightStart, astronomicalTwilightEnd | string | yes | ISO-8601 UTC |
…Timestamp for each twilight bound | int64 | yes | Unix epoch seconds |
altitude | double | no | degrees |
azimuth | double | no | degrees, 0 = North |
A time string and its …Timestamp sibling are always present together or absent together. Each twilight bound is the next time the sun passes 6°, 12° or 18° below the horizon, upwards for a start and downwards for an end; it is absent when that does not happen within 48 hours.
{prefix}/moon — data
| Key | Type | Optional | Unit |
|---|---|---|---|
rise, set, transit (+ …Timestamp) | string / int64 | yes | ISO-8601 UTC / epoch; the next moonrise, moonset and culmination |
riseAzimuth, setAzimuth | double | yes | degrees, 0 = North, at rise and at set |
transitAltitude | double | yes | degrees, at transit |
altitude, azimuth | double | no | degrees |
phase | double | no | 0.0–1.0, synodic cycle position |
illumination | double | no | 0.0–1.0, lit fraction |
phaseName | string | no | new, waxing-crescent, first-quarter, waxing-gibbous, full, waning-gibbous, last-quarter or waning-crescent |
waxing | boolean | no | true from new moon to full moon (phase < 0.5) |
age | double | no | days since the last new moon |
litSide | string | yes | right or left; absent when the latitude is exactly 0 |
litAngle | double | no | degrees, 0 (included) to 360 (excluded), clockwise from the vertical: 0 = lit side straight up, 90 = on the right |
daysToNewMoon, daysToFullMoon | double | yes | days, half-day resolution |
track | array | yes | the moon's path for one pass above the horizon; absent when there is no pass |
track[].time, track[].timestamp | string / int64 | no | ISO-8601 UTC / Unix epoch seconds |
track[].altitude, track[].azimuth | double | no | degrees to 0.01°, azimuth 0 = North |
rise, set and transit are the next moonrise, the next moonset and the next culmination above the horizon after the report: never a past one, each a whole second that stays the same from report to report while the event is ahead, and each absent, with its bearing or altitude, when the event does not come within 48 hours.
A principal phase (new, first-quarter, full, last-quarter) is named within 20° of elongation of its exact position, that is within 20/360 of its position in the cycle (0.0, 0.25, 0.5, 0.75), lower bound included: about 3.3 days each, and about 4.1 days for each crescent or gibbous name between them. litSide is right for a waxing moon north of the equator and for a waning moon south of it, left otherwise: the hemisphere's convention. litAngle is the measured direction of the lit limb as the observer sees the moon. track is the pass in progress if the moon is up, the next one if it is down: points in time order, at most 20 minutes apart and 77 in all, from that pass's moonrise to its moonset, its culmination among them, each at the instant rise, transit or set gives when it names the same event. It is absent when the moon stays up for more than 25 hours or does not rise within 48. The rules, and how the fields relate, are set out in Sun and moon.
{prefix}/magnetic — data
| Key | Type | Unit |
|---|---|---|
declination | double | degrees, positive = east |
inclination | double | degrees |
totalIntensity | double | nanotesla |
All three are always present. The whole message is omitted when the model cannot be evaluated.
{prefix}/tides — data.stations[]
| Key | Type | Optional | Unit |
|---|---|---|---|
name | string | no | — |
country | string | yes | ISO alpha-3 |
latitude, longitude | double | no | degrees |
distanceNm | double | no | nautical miles |
height | double | no | metres about local mean water |
nextHighTide | string | yes | ISO-8601 UTC |
nextHighTideHeight | double | yes | metres |
nextLowTide | string | yes | ISO-8601 UTC |
nextLowTideHeight | double | yes | metres |
tidalCoefficient | int | yes | rounded; clamped to 0…200 |
curve | array | no | 216 objects { "t": ISO-8601 UTC, "h": metres } |
Stations are sorted by distanceNm, nearest first. When no station is within range, the message is not published at all.
There are no …Timestamp siblings in this report.
{prefix}/skymap — data
| Key | Type | Notes |
|---|---|---|
stars[] | array | id (int), altitude, azimuth (1 decimal), magnitude (2 decimals), optional name |
constellations[] | array | name, segments = array of [id, id] pairs referring to stars[] of the same message |
| — | — | id is an internal 1…1663 index into the built-in catalogue, not a Hipparcos or HD number, and is stable only for a given build |
planets[] | array | name, altitude, azimuth, magnitude |
Stars are included when brighter than --skymap-max-mag and above −5°. Planets are included when above 0°, with no magnitude filter.
Recompute triggers
| Trigger | Condition |
|---|---|
| First fix | no position recorded yet |
| Position drift | ` |
| Interval | --recompute-interval-min elapsed since the last recompute |
All enabled reports are fed from the same trigger. The tides report applies an additional internal floor of --tide-recompute-min and skips triggers that arrive sooner.
Files
| Path | Owner | Content |
|---|---|---|
/usr/bin/muxen-sextant | muxen-sextant | the daemon |
/usr/lib/systemd/system/muxen-sextant.service | muxen-sextant | the systemd unit |
/etc/nginx/snippets/muxen-ws-sextant.conf | muxen-sextant | location = /ws/sextant proxying to http://127.0.0.1:1884/ |
/var/lib/muxen-sextant/WMM.COF | muxen-sextant-database | WMM-2025 coefficients, epoch 2025.0 |
/var/lib/muxen-sextant/tides.db | muxen-sextant-database | SQLite tidal harmonics, 8117 stations |
The daemon opens tides.db read-only and never writes to disk.
The nginx snippet does nothing until a server block includes it, and that block is not in this package: it is shipped by the boat's UI interface package (muxen-interface-*), one site file per boat, which is also where the map $http_upgrade $connection_upgrade block the proxy needs is defined. Interfaces that want /ws/sextant include the snippet with a trailing * — include /etc/nginx/snippets/muxen-ws-sextant.conf*; — which makes the include optional, so a Brain without muxen-sextant installed still starts nginx and simply has no /ws/sextant. Not every interface asks for it, so the endpoint exists only on the boats whose site file has that line.
systemd unit
| Directive | Value |
|---|---|
Description | MUXEN Sextant Environment Daemon |
After | mosquitto.service |
PartOf | muxen.target |
WantedBy | muxen.target |
Type | simple |
ExecStart | /usr/bin/muxen-sextant --report-all |
Restart | always |
RestartSec | 10 |
TimeoutStopSec | 5 |
StartLimitIntervalSec / StartLimitBurst | 300 / 5 |
User / Group | muxen / muxen |
StateDirectory | muxen-sextant, mode 0750 |
RuntimeDirectory | muxen-sextant, mode 0755 |
Hardening: NoNewPrivileges, ProtectSystem=strict, ProtectHome, PrivateTmp, PrivateDevices, ProtectKernelTunables, ProtectKernelModules, ProtectKernelLogs, ProtectControlGroups, ProtectClock, ProtectHostname, ProtectProc=invisible, ProcSubset=pid, RestrictRealtime, RestrictSUIDSGID, MemoryDenyWriteExecute, LockPersonality, RestrictNamespaces, SystemCallArchitectures=native, SystemCallFilter=@system-service, RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6, and an empty CapabilityBoundingSet and AmbientCapabilities.
ProtectClock=yes is worth noting: the daemon can read the clock but cannot set it, which is correct — it is a consumer of time, not a source.
Signals and exit status
SIGINT and SIGTERM are handled through the GLib main loop: the loop quits, every report and the MQTT client are freed, and the process exits 0. TimeoutStopSec=5 bounds that.
| Status | Meaning |
|---|---|
| 0 | normal shutdown on SIGINT/SIGTERM, or --version |
| 1 | command-line option parsing failed — the message is Config: Option parsing failed: … |
There is no other non-zero exit. A missing coefficient file, a missing tide database, an unreachable broker and an expired magnetic model are all logged and survived; the daemon keeps running and publishes whatever it still can.
MQTT client behaviour
| Property | Value |
|---|---|
| Client ID | muxen-sextant (--mqtt-client-id) |
| Keepalive | 60 s |
| Clean session | yes |
| Connect | asynchronous, integrated into the GLib main loop |
| Reconnect | every 5000 ms until it succeeds |
| Housekeeping timer | 100 ms |
| Publish | QoS 0, retain set (daemon info: QoS 1, retain set) |
| Last will | app/sextant/info with online: false, retained, QoS 1, re-registered before every connect attempt |
| Clean shutdown | publishes the offline info and waits up to 1 s for its PUBACK before disconnecting |
| Subscribe | QoS 0, re-issued on every successful connect |
The daemon does not exit when the broker is unreachable at startup; it retries indefinitely and begins publishing once connected.
Packages
| Package | Arch | Depends |
|---|---|---|
muxen-sextant | any | ${shlibs:Depends}, ${misc:Depends}, muxen-systemd, muxen-sextant-database |
muxen-sextant-database | all | ${misc:Depends} |
muxen-sextant activates the muxen-restart-target dpkg trigger, and nginx-reload, so a running nginx reloads at the end of the transaction and serves a changed muxen-ws-sextant.conf at once.
Build dependencies: debhelper-compat (= 13), meson, ninja-build, pkg-config, libglib2.0-dev, libmosquitto-dev, libjson-c-dev, libnova-dev, libsqlite3-dev.
Third-party components
| Component | Licence | Role |
|---|---|---|
| libnova | LGPL-2.0 | linked; solar, lunar, planetary and coordinate computations |
| NOAA WMM reference library | public domain | vendored in src/vendor/wmm/, built as a static library |
| json-c, libmosquitto, GLib, SQLite | system packages | JSON, MQTT, main loop, database |
libnova is a system library this daemon links against; its API is not part of the MUXEN interface and nothing in this manual documents it.
Runtime data volumes
| Quantity | Value |
|---|---|
Tide stations in tides.db | 8117 |
| Harmonic constants | ~205 000 |
| Constituents per station | 2 to 34, 25 on average |
| Distinct constituents in the database | 34 |
| Stations with a country code | 7988 of 8117 |
| Stations without a mean spring range | 2 |
| Curve points per station | 216, at 10-minute spacing, −12 h to +24 h |
| Built-in stars | 1663 |
| Built-in constellation line segments | 695 |
| Built-in constellations | 88 |
| Planets computed | 7 |
