signalkutilities
v3.1.0
Published
Utilities for Signal K plugin development: message handling, polar vector maths, statistical smoothing, and web reporting
Maintainers
Readme
signalkutilities
Utilities for Signal K plugin development. Covers subscription and value tracking for individual paths, polar vector maths (add, rotate, scale), statistical smoothing with three smoother types, correct wraparound-safe angle smoothing, and a web-facing Reporter that aggregates state across all tracked objects.
The current API also includes explicit lifecycle events (onDelta, onIdle, onStale) and static clear() helpers so plugins can manage idle/stale subscriptions and publish null values cleanly when outputs are disabled or the plugin stops.
Installation
npm install signalkutilitiesRequires Node.js ≥ 18.
ID convention
Every tracked object carries a camelCase id. Smoothers derive their id automatically by appending .smoothed. Polar sub-handlers use .magnitude and .angle suffixes internally, but these are not normally exposed directly.
"apparentWind" ← Polar id
"apparentWind.magnitude" ← internal handler id
"apparentWind.angle" ← internal handler id
"apparentWind.smoothed" ← PolarSmoother id
"heading" ← SmoothedAngle id
"heading.smoothed" ← SmoothedAngle smoother id (PolarSmoother base)meta / state / report() pattern
Every class exposes three standard accessors:
| Accessor | When to use | Contents |
|---|---|---|
| meta | Read once on startup | Static info: id, path, displayName, displayUnits, smoother type, etc. |
| state | Poll at any frequency | Dynamic info: id, stale, frequency |
| report() | Snapshot for logging/API | Combined: id, current value(s), variance, path, state |
Reporter aggregates these across all objects and returns results keyed by id.
MessageHandler
Subscribes to a single Signal K path and tracks the latest value.
const { MessageHandler } = require('signalkutilities');
const handler = new MessageHandler(app, pluginId, 'boatSpeed');
handler.configure('navigation.speedOverGround');
handler.idlePeriod = 10000;
handler.stalePeriod = 15000;
handler.onDelta = () => {};
handler.subscribe();
// later
handler.value; // latest numeric value
handler.stale; // true after stalePeriod elapses without a fresh delta
handler.frequency; // Hz
handler.meta; // { id, path, displayName, displayUnits, ... }
handler.state; // { id, isStale, frequency, ... }
handler.report(); // { id, value, path, state }
handler.terminate(); // unsubscribeMessageSmoother
Wraps a MessageHandler and applies statistical smoothing to scalar values.
const { MessageHandler, MessageSmoother, ExponentialSmoother } = require('signalkutilities');
const handler = new MessageHandler(app, pluginId, 'boatSpeed');
handler.configure('navigation.speedOverGround');
const smoother = new MessageSmoother(handler, ExponentialSmoother, { timeConstant: 2 });
smoother.onDelta = () => {};
smoother.subscribe();
smoother.value; // smoothed value
smoother.standardError; // sqrt of variance
smoother.handler; // underlying MessageHandler
smoother.meta; // includes smoother type and options
smoother.state; // { id, isStale, frequency, ... }
smoother.report(); // { id, value, variance, path, state }Or use the factory:
const { createSmoothedHandler } = require('signalkutilities');
const smoother = createSmoothedHandler({
id: 'boatSpeed',
path: 'navigation.speedOverGround',
app, pluginId,
subscribe: true,
SmootherClass: ExponentialSmoother,
smootherOptions: { timeConstant: 2 },
onDelta: () => {}
});Polar + PolarSmoother
Combines two SK paths into a cartesian vector (x, y). Supports add, subtract, rotate, scale. Smoothing is applied in cartesian space so magnitude and angle are both smoothed correctly.
const { createSmoothedPolar } = require('signalkutilities');
const wind = createSmoothedPolar({
id: 'apparentWind',
pathMagnitude: 'environment.wind.speedApparent',
pathAngle: 'environment.wind.angleApparent',
app, pluginId,
angleRange: '-piToPi',
SmootherClass: ExponentialSmoother,
smootherOptions: { timeConstant: 3 },
meta: { displayName: 'Apparent Wind' }
});
// PolarSmoother values
wind.magnitude; // smoothed speed
wind.angle; // smoothed angle (radians)
wind.trace; // xVariance + yVariance
wind.meta; // { id, angleRange, magnitude: {...}, angle: {...}, smoother: {...} }
wind.state; // { id, isStale, magnitude: {...}, angle: {...}, ... }
wind.report(); // { id, pathMagnitude, pathAngle, x, y, magnitude, angle, trace, state }
// Static send — writes smoothed values back to SK
PolarSmoother.send(app, pluginId, [wind]);Event configuration is part of the subscription contract. Configure path, subscribe options,
onDelta, onIdle, onStale, idlePeriod, and stalePeriod before subscribe().
To change any of those while the plugin is running, do an explicit unsubscribe(), apply the
change, then subscribe() again.
By default, idlePeriod is 60 seconds and stalePeriod is 4 seconds.
For unsmoothed use:
const { Polar } = require('signalkutilities');
const polar = new Polar(app, pluginId, 'apparentWind');
polar.configureMagnitude('environment.wind.speedApparent');
polar.configureAngle('environment.wind.angleApparent');
polar.subscribe();
// polar.add(), polar.rotate(), polar.scale(), etc.SmoothedAngle
Wraps a single angular SK path and applies vector-based smoothing that correctly handles wraparound (heading crossing 0/2π, etc.). Mirrors the MessageSmoother interface.
const { SmoothedAngle } = require('signalkutilities');
const heading = new SmoothedAngle(app, pluginId, 'heading', 'navigation.headingTrue', {
angleRange: '0to2pi',
SmootherClass: ExponentialSmoother,
smootherOptions: { timeConstant: 2 },
meta: { displayName: 'True Heading' }
});
heading.value; // smoothed angle (radians)
heading.standardError; // angular uncertainty (radians)
heading.frequency; // Hz
heading.path; // 'navigation.headingTrue'
heading.meta; // { id, path, displayUnits, angleRange, smoother: {...}, ... }
heading.state; // { id, isStale, frequency, ... }
heading.report(); // { id, value, variance, path, state }Smoothers
Three smoother classes are available. All implement the same interface: add(value, variance?), estimate, variance, reset(). Options are updated via the options property setter (smoother.options = opts).
| Class | Options | Use case |
|---|---|---|
| ExponentialSmoother | timeConstant (seconds) | General purpose, low lag |
| MovingAverageSmoother | timeSpan (seconds) | Simple rolling average |
| KalmanSmoother | processVariance, measurementVariance, steadyState | Optimal when noise is known |
const { ExponentialSmoother, KalmanSmoother, MovingAverageSmoother } = require('signalkutilities');Reporter
Aggregates meta, state, and report() across collections of objects and returns results keyed by id. Designed for Express/webapp endpoints.
const { Reporter } = require('signalkutilities');
const reporter = new Reporter();
reporter.addDelta(boatSpeed); // MessageHandler or MessageSmoother
reporter.addPolar(apparentWind); // Polar or PolarSmoother or SmoothedAngle
reporter.addTable(customTable); // any object exposing id/meta/state/report
// In your webapp GET handler:
res.json(reporter.state()); // { deltas: { boatSpeed: {...}, ... }, polars: { ... } }
res.json(reporter.meta()); // same structure, static fields
res.json(reporter.report()); // combined snapshotTable2D / SI
Table2D— generic 2-D interpolation table.SI— unit conversion helpers.
