Susi Server

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

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).

KeyLabelTypeDefaultOptionsDescription / Notes
declinationDegMagnetic declination (deg, east positive)number0.0
inclinationDegMagnetic inclination / dip (deg, down positive)number60.0
fieldMagnitudeUtExpected field magnitude (uT)number50.0
magnitudeToleranceField magnitude tolerance (fraction)number0.15
inclinationToleranceDegInclination tolerance (deg)number8.0
rlsForgettingCalibration forgetting factornumber0.9999
enableSoftIronSoft-iron scales (diagonal)booleanfalse
softIronScaleSigmaSoft-iron scale prior sigmanumber2.0
minSampleSpacingUtMin field-space sample spacing (uT)number2.0
magNoiseUtMagnetometer noise stddev (uT)number0.5
cogMinSpeedMpsCOG truth: min speed (m/s)number2.0
cogMaxYawRateRadSCOG truth: max yaw rate (rad/s)number0.02
cogMinStraightSCOG truth: min straight duration (s)number5.0
persistCalibrationPersist calibrationbooleantrue
useWmmWorld Magnetic Model reference fieldbooleantrue
useLocalFieldReferenceLearn local field as gate referencebooleantrue
localFieldTauSLocal field time constant (s)number120.0
enableDeviationModelDeviation card (heading-dependent correction)booleanfalse
useReceiverHeadingUse receiver true heading (HDT)booleanfalse
kfAccCovarianceKalman: accelerometer covariancenumber0.1
kfMagCovarianceKalman: magnetometer covariancenumber1000.0
kfCogCovarianceKalman: COG yaw covariancenumber10.0
kfReceiverCovarianceKalman: receiver heading covariancenumber1.0
compassMaxRateDegSCompass: max rate for mag correction (deg/s)number45.0
compassInnovationGateDegCompass: mag innovation gate (deg)number30.0
compassInnovationReleaseSCompass: innovation gate release (s)number10.0
gyroUnitInput gyro unitselectdegSdegS, radS
accelUnitInput accel unitselectgg, mps2
enableMagnitudeGateField magnitude gatebooleantrue
enableInclinationGateField inclination gatebooleantrue
enableCogGateCOG straight-line gatebooleantrue

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

FieldPurpose
dataEndpointEndpoint this node binds to publish its output. Omit to let the runtime generate one.
inputEndpointsList of upstream endpoints this node connects to (outEndpoint / dataEndpoint values from upstream nodes).
inputDataFilterOptional whitelist of data types. Messages whose type isn’t in the list are dropped at the subscriber.
settingsAll user-configurable fields — see the Properties table above.

Connections can also be declared at the top-level connections array when settings.explicitConnections is true, as an alternative to filling inputEndpoints on each node.

Loading documentation…