Appearance
Alarms
When something on the boat goes wrong — a bilge float rises, a battery goes flat, an engine overheats — the MUXEN system raises an alarm and it appears on the MUXEN screens. This chapter is about the other screens.
NMEA 2000 has a standard way of carrying alerts, and every modern chartplotter knows how to display one and how to let you silence it. muxen-nmea2000 bridges the two: an alarm raised anywhere in the MUXEN system is put on the bus as a standard NMEA 2000 alert, in seven languages, and appears on the chartplotter. Press silence on the plotter and the request travels back into the MUXEN system.
For the crew, the practical consequence is: you can acknowledge a MUXEN alarm from the chartplotter at the helm, without going to a MUXEN screen.
This daemon does not decide what is an alarm. It never raises one by itself. It is a translator between the MUXEN alarm system and the NMEA 2000 bus, in both directions.
Turning it on
The subsystem is off by default, and the shipped muxen-nmea2000.service turns it on:
ExecStart=/usr/bin/muxen-nmea2000 … --enable-alarmsWithout --enable-alarms the daemon subscribes to no alarm topic and silently ignores everything on them. A hand-run daemon that appears not to react to alarm messages is almost always missing this flag.
The two directions
other muxen-* services chartplotter / MFD
│ ▲
│ nmea/alarm/active │ PGN 126983 Alert
│ nmea/alarm/clear │ PGN 126985 Alert Text
▼ │
muxen-nmea2000 ────────────────────────────► can1
▲ │
│ nmea/alarm/mute/request │ PGN 126984 Alert Response
│ nmea/alarm/state │ (silence / acknowledge)
│ ▼Inbound: raising and clearing
Publish to nmea/alarm/active to raise an alarm:
sh
mosquitto_pub -h 127.0.0.1 -t nmea/alarm/active -m '{
"alarmCode": 4001,
"alarmDescription": "Overcurrent channel 1",
"alarmDescription.FR": "Surintensité canal 1",
"severity": "alarm",
"deviceId": 40001,
"functionCode": 210,
"functionName": "Bloc 4",
"instance": 1,
"creationTime": "2026-08-16T09:00:00Z",
"expireTime": "2026-08-16T10:00:00Z",
"muted": false
}'| Field | Meaning |
|---|---|
alarmCode | the alarm's identifier, unique per device |
alarmDescription | the default (English) text |
alarmDescription.FR, .DE, .ES | translations |
severity | alarm, warning or unknown |
deviceId, instance | which device and which instance raised it |
functionCode, functionName | the device's MUXEN function |
creationTime, expireTime | ISO 8601 timestamps |
muted | whether it is already silenced |
filterBy | an existing mute filter, if any |
An alarm is identified on the bus by the triple alarmCode + deviceId + instance. Republishing the same triple updates the existing alarm rather than creating a second one.
Clear it with the same triple:
sh
mosquitto_pub -h 127.0.0.1 -t nmea/alarm/clear -m \
'{"alarmCode":4001,"deviceId":40001,"instance":1}'Text longer than 256 characters is truncated. At most 64 alarms can be active at once.
Outbound: what goes on the bus
An active alarm is retransmitted continuously as PGN 126983 (Alert), at a rate set by its priority:
| Severity | NMEA 2000 priority | Retransmit |
|---|---|---|
| — | 0 Emergency | 500 ms |
alarm | 1 Alarm | 500 ms |
warning | 2 Warning | 1000 ms |
unknown | 3 Caution | 1000 ms |
| — | 4 Normal | 2000 ms |
The text goes out as PGN 126985 (Alert Text) in seven languages — English, French, German, Spanish, Italian, Dutch and Portuguese — so a plotter set to any of them shows the alarm in that language.
The daemon's alert category is derived from the raising device's functionCode: 0–99 Engine, 100–199 Navigation, 200–299 Electrical, 300–399 Environmental, 400–499 Safety, 500–599 Communication, 600–699 Fuel, 700–799 Anchor, 800 and above General.
At most 8 alerts are transmitted per 500 ms tick. With more than eight simultaneous alarms, transmission rotates through them, so a high-priority alert on a saturated bus is never starved but every alert takes longer to repeat.
Inbound from the bus: silence and acknowledge
When an operator presses silence or acknowledge on a plotter, the plotter sends PGN 126984 (Alert Response). The daemon translates it into an MQTT request and waits for the MUXEN system to answer:
nmea/alarm/mute/request → {"requestId":…, "action":"silence"|"acknowledge",
"duration":300, …}
nmea/alarm/mute/response ← {"requestId":…, "success":true, "filterBy":{…}}Unmuting works the same way over nmea/alarm/unmute/request and nmea/alarm/unmute/response.
The daemon waits 5 seconds for a response, retries up to 3 times at 1 second intervals, and then publishes an error with code timeout on nmea/alarm/error. Up to 32 callbacks may be outstanding. The default mute duration it requests is 300 seconds.
The response must echo the requestId from the request. This is the one place where an integration has to do real work rather than just subscribe.
State changes
Every transition is announced on nmea/alarm/state:
json
{ "alarmCode": 4001, "deviceId": 40001, "instance": 1,
"previousState": "active", "newState": "silenced",
"trigger": "operator_action", "sourceAddress": 12,
"timestamp": "2026-08-16T09:00:05Z" }States are normal, active, silenced, acknowledged, escalated and disabled. Triggers are condition_detected, condition_cleared, operator_action, timeout, external_command and filter_expired.
Errors
nmea/alarm/error carries error, message, field, requestId and timestamp. The codes the daemon actually emits are:
| Code | Cause |
|---|---|
invalid_json | the payload did not parse |
missing_field | a required field was absent; field names it |
alarm_not_found | a clear or a response referenced an unknown alarm |
timeout | no response to a mute or unmute request after 3 retries |
Topic summary
| Topic | Direction | Notes |
|---|---|---|
nmea/alarm/active | in | raise or update an alarm |
nmea/alarm/clear | in | clear an alarm |
nmea/alarm/mute/response | in | answer to a mute request |
nmea/alarm/unmute/response | in | answer to an unmute request |
nmea/alarm/mute/request | out | a plotter asked to silence |
nmea/alarm/unmute/request | out | a mute filter ended |
nmea/alarm/state | out | every state transition |
nmea/alarm/error | out | rejected input, or a callback timeout |
The daemon subscribes to exactly the four inbound topics. Anything published on the outbound ones by something else is ignored.
A ninth topic, nmea/alarm/list, exists in the code as a snapshot of every active alarm, but nothing currently publishes it. Do not build an integration on it. Track alarm state with nmea/alarm/state instead.
Decoding PGN 126983 on the wire
If you are decoding the alert off the CAN bus rather than off MQTT, note that the wire encoding and the MQTT vocabulary are not the same numbering:
Wire Alert State | Value |
|---|---|
| Disabled | 0 |
| Normal | 1 |
| Active | 2 |
| Silenced | 3 |
| Acknowledged | 4 |
| Awaiting acknowledge | 5 |
Wire Alert Type | Value |
|---|---|
| Emergency Alarm | 1 |
| Alarm | 2 |
| Warning | 5 |
| Caution | 8 |
Wire Alert Category is 0 Navigational or 1 Technical. Wire Trigger Condition is 0 Manual, 1 Auto, 2 Test, 3 Disabled. The message is 28 bytes, Fast Packet.
The category-from-functionCode mapping above is an internal classification and is a different field from the wire Alert Type. A decoder that confuses the two will misread every alert.
What is not implemented
Being explicit, because the NMEA 2000 alert family is larger than what this daemon does:
- PGN 126986 (Alert Configuration) is defined but never transmitted or decoded. PGNs 126987 and 126988 are not handled at all.
- Nothing escalates on its own. An alarm reaches the
escalatedstate only if a device on the bus explicitly commands it. There is no unacknowledged-alarm timer. alarmDescription.IT,.NLand.PTare transmitted but never read from MQTT. Only the default text and.FR,.DE,.ESare parsed, so the other three are always empty on the wire.- Nothing about the subsystem is runtime-configurable. Topics, timeouts, retry counts, the concurrent-alarm limit and the language set are compile-time constants.
Diagnostics
sh
mosquitto_sub -h 127.0.0.1 -t 'nmea/alarm/#' -v
journalctl -u muxen-nmea2000 -f | grep -i alarm
candump can1 | grep 19F007 # PGN 126983 leaving the BrainThe journal logs each alarm as it is added, each state change and each expiry:
Alarm added: code=4001, device=40001, instance=1, state=active
Alarm state change: …
Alarm removed: code=4001, device=40001, instance=1
Alarm expired: code=4001The suspend and resume of alert transmission during CAN bus recovery is part of the general recovery behaviour — see Troubleshooting.
