Marine Heading
Node ID: marineHeading · Role: Filter · Realtime config: no
Description
Marine attitude and heading from the firmware-ported quaternion Kalman filter: gyro prediction, gravity-referenced tilt, yaw referenced by the continuously hard-iron-calibrated magnetometer (field-validity, tilt-leak, rate and innovation gated), crab-gated GNSS course over ground, or the receiver’s true heading (HDT). Position is raw GNSS passthrough - no position fusion. Outputs FusedPose (raw-GNSS ENU position + true-heading attitude) and GlobalFusedPose (WGS84, for map sinks).
Algorithm notes
What the node estimates
The node runs an indirect quaternion Kalman filter over attitude only. The state is a unit quaternion mapping the sensor body frame into a local ENU world frame (x east, y north, z up), together with its covariance. Roll and pitch are referenced to gravity; heading is referenced to true north, with magnetic declination folded into the magnetic reference direction so the reported yaw is already true heading. Heading is read out as the rotation-about-vertical component of the attitude, so it stays continuous even at extreme pitch.
Position is not estimated. GNSS fixes are passed through untouched, anchored to a local ENU origin at the first valid fix and republished in WGS84 for map consumers. This means the position output can never diverge, but it also inherits GNSS noise and outages directly.
How the inputs are used
Each inertial sample drives the prediction step (angular rate integration) and a gravity correction whose weight adapts to how far the measured specific force deviates from 1 g and to the current turn rate; samples far outside 1 g are skipped entirely. Magnetometer samples, when present, are corrected for the estimated hard iron (and optional per-axis soft-iron scale), then checked against a reference field for magnitude and dip. Passing samples feed the continuous calibration; failing samples freeze it. A separate learner builds an average of the installation’s own field magnitude and dip from accepted samples, and once established the validity gates re-center on it, which is what allows a permanently distorted but stable installation to calibrate at all.
GNSS contributes in two ways: a course derived from successive fixes over a minimum baseline, gated for speed, straightness and duration so that crab under wind and current does not enter as heading; and, optionally, the receiver’s true heading sentence, which is the strongest reference available. No output is produced until some absolute yaw reference has arrived. The first one snaps the heading directly, and during a short acquisition window trusted magnetic headings keep snapping it while the tilt estimate settles. After that all references act through normal filtering. Hard iron typically converges within a few minutes of varied motion; the learned installation field needs roughly a few seconds of accepted samples. Both are persisted per sensor and reloaded with deliberately loosened confidence.
Parameter groups
Reference field settings (declination, dip, magnitude and their tolerances, the world-model option, and the learned local reference with its time constant) decide what counts as a valid field. Loosening tolerances or disabling individual gates lets more samples into the calibration at the cost of accepting disturbances. Calibration settings (forgetting factor, minimum sample spacing, assumed noise, soft-iron estimation, persistence, deviation card) control how fast and how far the magnetometer model adapts. Reference weighting settings (the accelerometer, magnetometer, course and receiver covariances) set the relative pull of each correction; a larger number means less pull. Trust gating settings (maximum turn rate for magnetic corrections, innovation gate, and its timed release) control how the compass behaves under disturbance. Finally, the input unit selectors must match the actual wire units.
Tuning and failure modes
Start by confirming units: an incorrect gyro unit shows up as heading that drifts or turns at the wrong rate. Then watch the reported field magnitude, gate status and heading sigma while manoeuvring. Yaw-only motion leaves the vertical calibration terms unobservable; they stay near their prior, and the residual leaks into heading when the vessel pitches or heels, worst on easterly and westerly headings. Exercising roll and pitch fixes this. A steep local dip or a shorted vertical field leaves a weak horizontal component, which inflates heading noise for a given calibration error.
Known failure modes: a disturbance that preserves both magnitude and dip is invisible to the field gates and is caught only by the innovation gate, which will release after the configured time if the disagreement persists; a strong, permanently stable disturbance will eventually be adopted as the local reference, so verify heading against a known bearing after installation changes; training the deviation card while crabbing consistently aliases the crab angle into the correction; and if no magnetometer, course or receiver heading ever qualifies, the node stays silent rather than emitting an unreferenced heading.
Inputs / Outputs
- Inputs:
Imu,Gnss - Outputs:
FusedPose,GlobalFusedPose
Config aliases
MarineHeadingFilter, marineHeadingFilter, marineHeading
Properties
The following fields are exposed in the node’s Properties panel in the UI. Types map to UI widgets (e.g. string → text input, number → numeric, boolean → toggle, select → dropdown, json → JSON editor).
| Key | Label | Type | Default | Options | Description / Notes |
|---|---|---|---|---|---|
declinationDeg | Magnetic declination (deg, east positive) | number | 0.0 | — | — |
inclinationDeg | Magnetic inclination / dip (deg, down positive) | number | 60.0 | — | — |
fieldMagnitudeUt | Expected field magnitude (uT) | number | 50.0 | — | — |
magnitudeTolerance | Field magnitude tolerance (fraction) | number | 0.15 | — | — |
inclinationToleranceDeg | Inclination tolerance (deg) | number | 8.0 | — | — |
rlsForgetting | Calibration forgetting factor | number | 0.9999 | — | — |
enableSoftIron | Soft-iron scales (diagonal) | boolean | false | — | — |
softIronScaleSigma | Soft-iron scale prior sigma | number | 2.0 | — | — |
minSampleSpacingUt | Min field-space sample spacing (uT) | number | 2.0 | — | — |
magNoiseUt | Magnetometer noise stddev (uT) | number | 0.5 | — | — |
cogMinSpeedMps | COG truth: min speed (m/s) | number | 2.0 | — | — |
cogMaxYawRateRadS | COG truth: max yaw rate (rad/s) | number | 0.02 | — | — |
cogMinStraightS | COG truth: min straight duration (s) | number | 5.0 | — | — |
persistCalibration | Persist calibration | boolean | true | — | — |
useWmm | World Magnetic Model reference field | boolean | true | — | — |
useLocalFieldReference | Learn local field as gate reference | boolean | true | — | — |
localFieldTauS | Local field time constant (s) | number | 120.0 | — | — |
enableDeviationModel | Deviation card (heading-dependent correction) | boolean | false | — | — |
useReceiverHeading | Use receiver true heading (HDT) | boolean | false | — | — |
kfAccCovariance | Kalman: accelerometer covariance | number | 0.1 | — | — |
kfMagCovariance | Kalman: magnetometer covariance | number | 1000.0 | — | — |
kfCogCovariance | Kalman: COG yaw covariance | number | 10.0 | — | — |
kfReceiverCovariance | Kalman: receiver heading covariance | number | 1.0 | — | — |
compassMaxRateDegS | Compass: max rate for mag correction (deg/s) | number | 45.0 | — | — |
compassInnovationGateDeg | Compass: mag innovation gate (deg) | number | 30.0 | — | — |
compassInnovationReleaseS | Compass: innovation gate release (s) | number | 10.0 | — | — |
gyroUnit | Input gyro unit | select | degS | degS, radS | — |
accelUnit | Input accel unit | select | g | g, mps2 | — |
enableMagnitudeGate | Field magnitude gate | boolean | true | — | — |
enableInclinationGate | Field inclination gate | boolean | true | — | — |
enableCogGate | COG straight-line gate | boolean | true | — | — |
Example node definition
A full node definition in config.json looks like the block below. Paste it under the sinks key of your config, keyed by the node’s instance name (any identifier; it doesn’t have to match the node ID).
{
"sinks": {
"marineHeading": {
"dataEndpoint": "inproc://marineHeading_data",
"inputEndpoints": [
"inproc://imu_data",
"inproc://gnss_data"
],
"inputDataFilter": [
"Imu",
"Gnss"
],
"settings": {
"accelUnit": "g",
"cogMaxYawRateRadS": 0.02,
"cogMinSpeedMps": 2.0,
"cogMinStraightS": 5.0,
"compassInnovationGateDeg": 30.0,
"compassInnovationReleaseS": 10.0,
"compassMaxRateDegS": 45.0,
"declinationDeg": 0.0,
"enableCogGate": true,
"enableDeviationModel": false,
"enableInclinationGate": true,
"enableMagnitudeGate": true,
"enableSoftIron": false,
"fieldMagnitudeUt": 50.0,
"gyroUnit": "degS",
"inclinationDeg": 60.0,
"inclinationToleranceDeg": 8.0,
"kfAccCovariance": 0.1,
"kfCogCovariance": 10.0,
"kfMagCovariance": 1000.0,
"kfReceiverCovariance": 1.0,
"localFieldTauS": 120.0,
"magNoiseUt": 0.5,
"magnitudeTolerance": 0.15,
"minSampleSpacingUt": 2.0,
"persistCalibration": true,
"rlsForgetting": 0.9999,
"softIronScaleSigma": 2.0,
"useLocalFieldReference": true,
"useReceiverHeading": false,
"useWmm": true
}
}
}
}
Field reference
| Field | Purpose |
|---|---|
dataEndpoint | Endpoint this node binds to publish its output. Omit to let the runtime generate one. |
inputEndpoints | List of upstream endpoints this node connects to (outEndpoint / dataEndpoint values from upstream nodes). |
inputDataFilter | Optional whitelist of data types. Messages whose type isn’t in the list are dropped at the subscriber. |
settings | All user-configurable fields — see the Properties table above. |
Connections can also be declared at the top-level
connectionsarray whensettings.explicitConnectionsistrue, as an alternative to fillinginputEndpointson each node.