Signal K server plugin for centralized alert management following maritime and process industry standards.
Signal K has no built-in alert management. Maritime safety standards (IMO MSC.302(87), IEC 62682) require features that simply don't exist in the server: alert lifecycle tracking, operator acknowledgment, persistence across restarts, silencing, escalation, and audit trails.
signalk-alert-manager provides a standalone alert management system based on these standards. Alerts can be raised through multiple paths — a REST API, a plugin API for other Signal K plugins, incoming alerts.* deltas from devices, or automatically from existing notifications.* deltas — and all alerts receive the same lifecycle management regardless of how they entered the system.
Signal K's notifications.* paths provide a simple signaling layer — a value at a path with a state and message. They were not designed for alert management and lack lifecycle, acknowledgment, persistence, and history. The alert manager is a separate system that can ingest notifications as one alert source among others, not a replacement or extension of the notification mechanism.
The comparison below summarizes the differences:
| Capability | Signal K Notifications | Alert Manager |
|---|---|---|
| Data model | Key-value at notifications.* paths |
Alert objects with unique IDs, full metadata |
| States | normal, nominal, alert, warn, alarm, emergency |
IEC 62682: unacknowledged, acknowledged, return-to-normal |
| Lifecycle | None — value replaced on update | Raise → acknowledge → clear with full tracking |
| Acknowledgment | Not supported | Per-alert, records who and when |
| Silencing | Not supported | Time-limited, configurable per priority |
| Escalation | Not supported | Automatic priority promotion on timeout |
| Persistence | In-memory only (lost on restart) | SQLite — survives restarts |
| History | None | Full audit trail with query API |
| Source tracking | $source field |
Source liveness monitoring, stale detection |
| Priority model | Implicit in state string | Four explicit levels (IMO model) with defined behaviors |
| Creation | Zone triggers or manual deltas | REST API, plugin API, alerts.* deltas, and notification ingestion |
Four priority levels follow the IMO model, from most to least urgent:
| Priority | When to Use | Audible | Requires Acknowledgment |
|---|---|---|---|
| Emergency | Immediate danger to life or vessel | Rapid 5-pulse burst, repeating | Yes |
| Alarm | Conditions requiring immediate attention | 3-pulse triplet, repeating | Yes |
| Warning | Conditions requiring precautionary attention | 2-pulse chime, repeating | Yes |
| Caution | Noteworthy conditions, not immediately hazardous | None | No |
All audible patterns repeat until the alert is acknowledged or silenced. Each priority has a distinct frequency (880/660/440 Hz) and pulse pattern inspired by IEC 60601-1-8.
Alerts follow the IEC 62682 state model:
condition condition clears
NORMAL ──────triggers──────► UNACKNOWLEDGED ──────────────────► RTN-UNACKNOWLEDGED
│ │
operator acks operator acks
│ │
▼ condition clears ▼
ACKNOWLEDGED ──────────────────────► NORMAL
- Unacknowledged: Alert is active, operator has not yet responded. Visual indicators flash, audible alerts sound per priority.
- Acknowledged: Operator confirmed awareness. Visual indicators show steady, audible alerts stop.
- Return-to-normal unacknowledged: The triggering condition cleared before the operator acknowledged. Audible alert continues until acknowledged or silenced.
Caution-priority alerts skip acknowledgment — they clear automatically when the condition resolves.
Some alerts represent one-shot events (e.g., waypoint arrival, anchor drag detected) where the triggering condition is momentary. Latching alerts remain active after the condition clears and must be explicitly acknowledged to dismiss.
Silencing suppresses audible indicators without acknowledging the alert. It is time-limited:
- Non-emergency alerts: configurable, default 120 seconds
- Emergencies: shorter limit, default 30 seconds
- A global "silence all" action affects all unacknowledged alerts (including return-to-normal unacknowledged)
Unacknowledged warnings automatically escalate to alarm priority after a configurable timeout (default: 5 minutes). This ensures ignored warnings eventually demand attention.
Each alert tracks its source. If a source stops sending updates, the alert is marked stale after a configurable timeout (default: 60 seconds). Stale alerts remain visible and actionable but are visually distinguished in the UI.
Who creates alerts? Alerts enter the system through four ingress paths:
- Plugin API — Signal K plugins raise alerts programmatically via
app.alertManager.raiseAlert(), providing apathto identify the data point and a$sourceto identify themselves. - REST API — Authenticated HTTP clients raise alerts via
POST /alerts. The caller must provide apath;$sourceis optional and defaults to'rest-api'. - Alert deltas — Devices and plugins can raise alerts by publishing Signal K deltas with
alerts.*paths. See Alert Delta Ingestion below. - Notification ingestion — The plugin intercepts incoming
notifications.*deltas and transforms them into managed alerts. This is how zone-based alarms and other plugins that use the existing notification mechanism enter the alert system. The alert'spathis derived from the notification path with thenotifications.prefix stripped (e.g.,notifications.propulsion.main.coolantTemperature→ pathpropulsion.main.coolantTemperature).
Each alert is identified by its path (with optional context for multi-vessel deployments). When the same path is raised again, the existing alert is updated (message refreshed, priority escalated if higher) rather than creating a duplicate.
Who can acknowledge, silence, and clear? Any authenticated user can perform any operation on any alert, regardless of who raised it. There is no per-alert ownership or role-based restriction. This is intentional: in a bridge context, any watchkeeper must be able to respond to any alert immediately. The operator's identity is recorded for audit purposes (in acknowledgedBy and in the history log) but is not used for access control.
Who manages alert state? The AlertManager is the single source of truth. All state transitions — whether triggered by the REST API, the plugin API, or the notification transformer — pass through the AlertManager, which enforces the IEC 62682 state machine rules, manages escalation timers, and persists changes to the SQLite store. No external actor can modify alert state directly.
Authentication is handled entirely by the Signal K server. All REST API routes inherit the server's admin authentication middleware; unauthenticated requests are rejected before reaching the plugin. The plugin API has no authentication layer — any plugin running in the server process can call app.alertManager.
- Node.js 22+ (required for built-in
node:sqlite) - Signal K server running on Node.js 22+ with the
--experimental-sqliteflag @signalk/server-apiv2.10.0+ (peer dependency)
Install from the Signal K Appstore (Server → Appstore in the Admin UI), then enable the plugin under Server → Plugin Config.
cd ~/.signalk
npm install signalk-alert-managerOn HaLOS, Signal K runs in a Docker container:
sudo docker exec signalk-server npm install signalk-alert-manager --prefix /home/node/.signalk
sudo systemctl restart marine-signalk-server-container.serviceAll settings are optional — defaults are applied automatically.
| Setting | Default | Description |
|---|---|---|
| Audio: Minimum Audible Priority | warning |
Lowest priority that triggers browser audio. Set to off to disable all audio. |
| Silencing: Default Duration | 120s | Maximum silence duration for non-emergency alerts |
| Silencing: Emergency Duration | 30s | Maximum silence duration for emergencies |
| Escalation: Enabled | true |
Auto-escalate unacknowledged warnings to alarms |
| Escalation: Timeout | 300s | Seconds before warning escalates to alarm |
| Source Timeout: Stale After | 60s | Mark alert stale if source stops updating |
| History: Retention | 90 days | How long to keep alert history |
| Dev: Enable Simulation | false |
Show simulation controls in the UI toolbar |
Configuration is accessible through the Signal K Admin UI plugin settings page or by editing ~/.signalk/plugin-config-data/signalk-alert-manager.json.
All endpoints are under /plugins/signalk-alert-manager/. Authentication is handled by Signal K server middleware.
POST /plugins/signalk-alert-manager/alerts
curl -X POST http://localhost:3000/plugins/signalk-alert-manager/alerts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"path": "propulsion.main.coolantTemperature",
"priority": "alarm",
"message": "Engine coolant temperature high",
"category": "engine",
"latching": false,
"data": { "value": 95, "threshold": 90 }
}'Returns 201 with the created alert object.
GET /plugins/signalk-alert-manager/alerts
GET /plugins/signalk-alert-manager/alerts?state=unacknowledged
GET /plugins/signalk-alert-manager/alerts?priority=alarm,emergency
GET /plugins/signalk-alert-manager/alerts?category=engine
GET /plugins/signalk-alert-manager/alerts?stale=true
GET /plugins/signalk-alert-manager/alerts/{id}
POST /plugins/signalk-alert-manager/alerts/{id}/acknowledge
The authenticated user's identity is recorded in acknowledgedBy.
POST /plugins/signalk-alert-manager/alerts/{id}/silence
Optional body: { "duration": 30 } (seconds). If omitted, uses the configured maximum for the alert's priority.
POST /plugins/signalk-alert-manager/alerts/silence-all
PUT /plugins/signalk-alert-manager/alerts/{id}/condition
Content-Type: application/json
{ "active": false }
Used by sources to signal that the triggering condition has resolved.
GET /plugins/signalk-alert-manager/alerts/history
GET /plugins/signalk-alert-manager/alerts/history?from=2026-01-01T00:00:00Z&to=2026-01-31T23:59:59Z
GET /plugins/signalk-alert-manager/alerts/history?alertId={id}
GET /plugins/signalk-alert-manager/alerts/history?eventType=raise,acknowledge
GET /plugins/signalk-alert-manager/alerts/history?limit=50&offset=100
Event types: raise, acknowledge, silence, unsilence, clear, escalate.
GET /plugins/signalk-alert-manager/config/ui
Returns browser-relevant settings (audio threshold, simulation mode).
Other Signal K plugins can interact with alerts programmatically via app.alertManager:
// Raise an alert
const alert = await app.alertManager.raiseAlert({
$source: 'my-plugin',
priority: 'warning',
message: 'Anchor watch: vessel outside radius',
category: 'navigation',
latching: true
})
// Acknowledge
await app.alertManager.acknowledgeAlert(alert.id, 'operator-1')
// Clear condition
await app.alertManager.clearCondition(alert.id)
// Silence for 60 seconds (plugin API takes milliseconds; REST API takes seconds)
await app.alertManager.silenceAlert(alert.id, 60000)
// Silence all audible alerts
await app.alertManager.silenceAll()
// Query alerts
const emergencies = app.alertManager.getAlerts({ priority: 'emergency' })
const unacked = app.alertManager.getAlerts({ state: 'unacknowledged' })
// Register a reusable alert type (reserved for future use — definitions
// are stored but not yet consumed by raiseAlert)
app.alertManager.registerAlertType({
alertType: 'engine.coolant.high',
defaultPriority: 'alarm',
latching: false,
message: 'Engine coolant temperature high',
category: 'engine'
})TypeScript types are exported from the package for use in plugin development:
import type {
Alert,
AlertPriority,
AlertState,
AlertManagerAPI,
RaiseAlertRequest,
AlertFilter,
HistoryEntry
} from 'signalk-alert-manager'The plugin intercepts incoming notifications.* deltas and transforms them into managed alerts, providing a bridge for zone-based alarms and other plugins that use the existing notification mechanism. Signal K notification states map to alert priorities:
| SK Notification State | Alert Priority |
|---|---|
emergency |
Emergency |
alarm |
Alarm |
warn |
Warning |
alert |
Caution |
normal, nominal |
Clears the condition |
Notifications continue to flow through Signal K normally — the plugin subscribes non-destructively via registerDeltaInputHandler.
Devices and plugins can raise alerts by publishing Signal K deltas with alerts.* paths. The portion after alerts. becomes the alert's path (e.g., delta path alerts.propulsion.main.coolantTemperature → alert path propulsion.main.coolantTemperature).
Raise an alert:
{
"updates": [{
"values": [{
"path": "alerts.propulsion.main.coolantTemperature",
"value": {
"priority": "alarm",
"message": "Engine coolant temperature high",
"category": "engine",
"data": { "value": 95, "threshold": 90 },
"latching": false
}
}]
}]
}| Field | Required | Description |
|---|---|---|
priority |
Yes | emergency, alarm, warning, or caution |
message |
Yes | Human-readable alert message |
category |
No | Grouping label (e.g., "engine", "navigation") |
data |
No | Arbitrary context object (non-array) |
latching |
No | Whether the alert latches (stays active after condition clears) |
Lifecycle fields (id, state, silenced, timestamps, etc.) are ignored — the AlertManager is authoritative for those.
Deltas with missing or invalid priority (not one of the four levels) or missing message are silently ignored. Check server debug logs (signalk-alert-manager) for diagnostics.
Clear an alert condition (two equivalent forms):
{
"updates": [{
"values": [{
"path": "alerts.propulsion.main.coolantTemperature",
"value": null
}]
}]
}{
"updates": [{
"values": [{
"path": "alerts.propulsion.main.coolantTemperature",
"value": { "state": "normal" }
}]
}]
}Update behavior: If a delta arrives for a path that already has an active alert with the same priority and message, it acts as a heartbeat — refreshing source liveness for all alerts from that $source, not just the one at the delta path. If priority or message changed, the alert is re-raised with the new values (returning it to unacknowledged state if it had been acknowledged).
Deltas published by the alert manager's own delta publisher (source label alert-manager) are ignored to prevent feedback loops.
Alert state changes are published as Signal K deltas on alerts.* paths, keyed by the alert's origin Signal K path. Each delta contains one value: the full alert object at the corresponding path.
Delta structure:
{
"context": "vessels.self",
"updates": [{
"source": { "label": "alert-manager" },
"timestamp": "2026-01-13T10:30:00.000Z",
"values": [{
"path": "alerts.propulsion.main.coolantTemperature",
"value": {
"id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"path": "propulsion.main.coolantTemperature",
"$source": "n2k-on-ve.can-bus.115",
"priority": "alarm",
"state": "unacknowledged",
"condition": true,
"latching": true,
"silenced": false,
"message": "Coolant temperature high",
"raisedAt": "2026-01-13T10:30:00.000Z",
"sourceOnline": true,
"lastSourceUpdate": "2026-01-13T10:30:00.000Z",
"stale": false
}
}]
}]
}Alert lifecycle as deltas — a single alert path receives updates as its state changes:
| Event | state |
Notes |
|---|---|---|
| Raised | unacknowledged |
New alert or re-raised |
| Acknowledged | acknowledged |
Operator acknowledged |
| Condition cleared (latching) | rtn-unacknowledged |
Condition resolved, awaiting ack |
| Silenced | unacknowledged |
silenced: true, silencedUntil set |
| Escalated | unacknowledged |
priority upgraded (e.g., warning → alarm) |
| Cleared | normal |
Final value — alert fully resolved |
When an alert clears, the full alert object is published with state: 'normal'. This is the terminal value at the path — consumers should treat normal as "no active alert for this path".
WebSocket subscription:
{
"context": "vessels.self",
"subscribe": [{ "path": "alerts.*", "minPeriod": 0 }]
}The plugin provides a web interface accessible at the plugin's webapp URL in Signal K. Built with Lit web components for framework-agnostic reuse.
Alert List — Full alert management view with priority-sorted list, acknowledge/silence controls, and a toolbar with global silence and optional simulation controls.
Alert Detail — Expanded view of a single alert showing full metadata, history timeline, and action controls. Accessible by selecting an alert in the list.
Alert Banner — Compact embeddable component showing the highest-priority unacknowledged alert, designed for use in other applications (chart plotters, dashboards). Expands to show all active alerts.
Audio — Browser-based audio alerts with different tones per priority level, respecting the configured minimum audible priority and silence state.
npm run build # Compile TypeScript (plugin backend)
npm run build:ui # Build UI with Vite (output to public/)
npm run dev:ui # Vite dev server for UI development
npm test # Run all Vitest tests
npm run test:ui # Run UI component tests
npm run test:e2e # Run end-to-end tests
npm run lint # ESLint check
npm run format # Prettier format
npm run ci # Full CI check (typecheck + lint + format + test)- IMO MSC.302(87) — Performance Standards for Bridge Alert Management
- IMO A.1021(26) — Code on Alerts and Indicators
- IEC 62682:2023 — Management of alarm systems for the process industries
- OpenBridge Design System — Maritime UI design guidelines
- Signal K Specification — Signal K data model and notification schema
- signalk-server#1857 — Discussion on alert data model and lifecycle
Apache License 2.0 — see LICENSE.