Files
Paul G 36a07dacb9 Extract shared signal-based D-Bus readers into lib/signal_reader.py
- Added lib/signal_reader.py with SignalGpsReader, SignalMeteoReader, and
  SignalDepthReader that use PropertiesChanged signal subscriptions instead
  of polling via GetValue(), reducing D-Bus overhead at steady state.
- Each reader discovers its service dynamically, seeds its cache with a
  one-shot GetValue, then relies on signals for all subsequent updates.
- Refactored dbus-tides, dbus-windy-station, dbus-no-foreign-land,
  dbus-lightning, and dbus-meteoblue-forecast to import from the shared
  library, removing ~600 lines of duplicated _unwrap() helpers and
  per-service GPS/meteo/depth reader classes.
- Updated install.sh for all five services to deploy signal_reader.py
  to /data/lib/ on the target device.
- Updated build-package.sh for all five services to bundle
  signal_reader.py into the .tar.gz package.
- Updated README.md with the new lib/ entry in the project table and
  documented the shared D-Bus readers pattern.
- Bumped version numbers in affected services (e.g. nfl_tracking 2.0.1).

Made-with: Cursor
2026-03-27 01:03:16 +00:00
..

dbus-tides

A Venus OS service that provides real-time tide predictions by combining onboard depth-sensor observations with harmonic tidal models. It publishes tide data on D-Bus (and therefore MQTT) so that marine displays, MFDs, and other Venus OS consumers can show current and upcoming tide information.

Version: 1.0.19

How It Works

Overview

The service continuously reads the vessel's depth sounder, detects observed high and low tide events, and blends them with a harmonic tidal model to produce an accurate, location-aware tide prediction curve. The result is published as a set of D-Bus paths under com.victronenergy.tides.

Depth Sensor (D-Bus)
       │
       ▼
 DepthRecorder          Collect, average, persist 5-min depth samples
       │
       ▼
 TideDetector           Detect observed high/low tide turning points
       │
       ▼
 TidePredictor          Harmonic model prediction (NOAA stations or grid)
       │
       ▼
 TideMerger             Calibrate predictions to depth under keel
       │
       ▼
 TideAdapter            Local adaptation (timing offset, amplitude scaling)
       │
       ▼
 D-Bus / MQTT           Publish predictions, curves, and event slots

Depth Collection (depth_recorder.py)

Raw depth readings are sampled every 10 seconds. Readings are averaged over 5-minute windows to suppress wave and wake noise, then stored in a SQLite database (/data/dbus-tides/depth_history.db) along with GPS coordinates. Up to 96 hours of history is retained.

When retrieving history for calibration or detection, records are filtered by position so that only data from the current anchorage (within the stationary radius) is used.

Tide Detection (tide_detector.py)

The smoothed depth history is processed to identify turning points (high and low tides). A two-stage approach is used:

  1. Median filter (kernel size 5) removes impulse noise from anchor swing.
  2. Triangular-weighted moving average (60-minute window) produces a smooth trend curve.
  3. Slope reversal detection identifies candidate extrema.
  4. Amplitude gating requires at least 0.4 m of depth change between consecutive highs and lows (MIN_TIDE_AMPLITUDE).
  5. Confirmation requires a 0.2 m excursion from the candidate before the event is reported, preventing premature triggering on flat tides.

Harmonic Prediction (tide_harmonics.py, tide_predictor.py)

Tide heights are computed using the standard IHO/Schureman harmonic method with 37 tidal constituents:

h(t) = Z₀ + Σ fᵢ · Aᵢ · cos(Vᵢ(t) + uᵢ  Gᵢ)

Where:

  • Z₀ — mean water level
  • Aᵢ, Gᵢ — amplitude and phase lag for each constituent
  • fᵢ, uᵢ — nodal amplitude and phase corrections (18.6-year lunar cycle)
  • Vᵢ(t) — astronomical argument computed from Doodson numbers

Constituent list (37)

Category Constituents
Semi-diurnal M2, S2, N2, K2, 2N2, MU2, NU2, L2, T2, R2, LAM2
Diurnal K1, O1, P1, Q1, J1, OO1, M1, 2Q1, RHO1
Long-period MF, MM, SSA, MSM, MS
Shallow-water M3, M4, MS4, M6, M8, MN4, S4, S6, 2SM2, MKS2
Terdiurnal MK3, 2MK3
Other S1, SA

The engine is pure Python with no external dependencies at runtime.

Extrema detection

Predicted tide events (highs and lows) are extracted from the harmonic curve using a multi-pass algorithm:

  1. Identify all local maxima and minima.
  2. Merge consecutive same-type extrema to enforce strict alternation.
  3. Iteratively remove the adjacent pair with the smallest height range until all remaining pairs exceed the minimum amplitude threshold.

This avoids the cascading-rejection problem that single-pass filtering causes when diurnal inequality produces small-range tidal cycles (common in the Caribbean, Gulf of Mexico, and other mixed-tide regions).

Station Selection (tide_predictor.py)

Constituent data can come from three sources, tried in priority order:

  1. Manual override — user specifies a NOAA station ID via /Settings/StationOverride.
  2. Best-fit scoring — if the vessel has accumulated at least 12 hours of depth history, nearby NOAA stations are hindcast and correlated against detrended observations. The station with the highest Pearson correlation (minimum 0.3) wins.
  3. Nearest station — falls back to the closest NOAA station within 50 nm.
  4. Coastal grid — if no nearby station exists, constituents are bilinearly interpolated from a pre-built 0.25° coastal grid (derived from the GOT4.10c global ocean tide model).

Up to 5 nearby NOAA stations are reported on /Tide/NearbyStations for the UI to offer as manual selections.

Subordinate stations

NOAA subordinate stations (type S) do not have their own harmonic constituents. Instead, predictions are derived from a reference harmonic station by:

  1. Predicting the reference station's full curve.
  2. Applying time offsets (time_high, time_low) interpolated between reference extrema.
  3. Applying height correction factors (height_high, height_low) as either ratios or additive offsets, interpolated between reference extrema.

Chart Depth Calibration (tide_merger.py)

Harmonic models predict heights relative to a tidal datum (typically mean sea level). The depth sounder reports depth under the keel. The chart depth offset bridges the two:

chart_depth = mean(observed_depth)  mean(predicted_tide_height)

This offset is computed from recent observations and converts model predictions into expected depth under the keel. Once calibrated, the predicted curve represents what the sounder should read at each point in time.

Position-Aware Recalibration

When the vessel moves, the service responds at three distance thresholds:

Threshold Distance Action
CHART_DEPTH_RECAL_METERS 500 ft (152 m) Recalibrate chart depth using the last 30 minutes of observations at the new position
STATIONARY_RADIUS_METERS 5 nm (9.3 km) Depth history is filtered to the current anchorage
MODEL_RERUN_RADIUS_METERS 20 nm (37 km) Re-interpolate tidal constituents and re-run the harmonic model

On service restart, if no prior calibration position is known, calibration uses only recent observations (last 30 minutes) to avoid contamination from depth records at a previous anchorage.

A speed gate (2 knots) suppresses depth readings while underway, since vessel motion invalidates sounder accuracy.

Local Adaptation (tide_adapter.py)

Even with the best available constituents, local geography (bays, inlets, shallow banks) causes tides to arrive earlier or later and with different amplitude compared to the open-ocean model. The adapter estimates two correction factors from observed tide events matched against predictions:

  • Δt — timing offset in seconds (positive = local tide arrives later)
  • k — amplitude scale factor (1.0 = no scaling)

The corrected local prediction is:

local_height(t) = k · predicted_height(t  Δt)

Adaptation progresses through stages:

  • Status 0 — no observed events matched yet
  • Status 1 — timing correction only (at least 2 matched events)
  • Status 2 — timing + amplitude correction (at least 1 high/low range pair)

Residual correction

After applying timing and amplitude adjustments, a smoothed residual (observed minus adapted model) is computed over a 1.5-hour window and added to the prediction. Beyond the last observation, the residual correction decays exponentially with a 6-hour half-life.

Dual-Curve Output

The service publishes two prediction curves:

  • Station curve — raw harmonic model shifted to depth under keel
  • Local curve — station curve with timing, amplitude, and residual corrections applied

The UI can display either or both. The "best" curve (local when adaptation is active, otherwise station) is published on /Tide/Predictions.

D-Bus / MQTT Topics

All paths are published under service name com.victronenergy.tides. On MQTT, paths map to topics prefixed with N/<portal_id>/tides/.

Service Management

Path Type Description
/Mgmt/ProcessName string dbus-tides
/Mgmt/ProcessVersion string Service version
/Mgmt/Connection string local
/DeviceInstance int 0
/ProductId int 0xA162
/ProductName string Tide Prediction
/FirmwareVersion string Service version
/Connected int 1

Status

Path Type Description
/Status int 0 Idle, 1 Calibrating, 2 Ready, 3 Error
/ErrorMessage string Error description (empty when status is not 3)
/IsStationary int 1 if vessel is within the stationary radius

Depth

Path Type Description
/Depth/Current float Latest smoothed depth in meters
/Depth/History string JSON array of {"ts": <unix>, "depth": <m>} (last 24 h)
/Depth/FullHistory string JSON array of all observed depth points

Predictions

Path Type Description
/Tide/Predictions string JSON array — best available prediction curve
/Tide/Station/Predictions string JSON array — raw station model curve
/Tide/Local/Predictions string JSON array — locally adapted curve

Local Adaptation

Path Type Description
/Tide/Local/TimeOffset float Timing offset Δt in seconds
/Tide/Local/AmpScale float Amplitude scale factor k
/Tide/Local/MatchCount int Number of matched observed/predicted events
/Tide/Local/Status int 0 no data, 1 time only, 2 time + amplitude

Tide Event Slots

Eight event slots provide the next and previous two highs and two lows. Each slot has four sub-paths:

Slot Sub-paths
NextHigh1, NextHigh2 /Time, /Depth, /ObsTime, /ObsDepth
NextLow1, NextLow2 /Time, /Depth, /ObsTime, /ObsDepth
PrevHigh1, PrevHigh2 /Time, /Depth, /ObsTime, /ObsDepth
PrevLow1, PrevLow2 /Time, /Depth, /ObsTime, /ObsDepth

Full path example: /Tide/NextHigh1/Time (Unix timestamp), /Tide/NextHigh1/Depth (meters).

ObsTime and ObsDepth are populated when a detected observed event has been matched to this predicted slot; otherwise they are null.

Model Metadata

Path Type Description
/Tide/LastModelRun int Unix timestamp of the last prediction run
/Tide/DataSource string Source identifier, e.g. noaa:8722670, grid
/Tide/ChartDepth float Calibrated chart depth offset in meters
/Tide/DatumOffset float MSL above MLLW in meters
/Tide/ModelLocation/Lat float Latitude used for model
/Tide/ModelLocation/Lon float Longitude used for model

Station Info

Path Type Description
/Tide/Station/Id string NOAA station ID
/Tide/Station/Name string Station name
/Tide/Station/Distance float Distance in km
/Tide/Station/Lat float Station latitude
/Tide/Station/Lon float Station longitude
/Tide/Station/Type string R (reference/harmonic) or S (subordinate)
/Tide/Station/RefId string Reference station ID (subordinate stations only)
/Tide/NearbyStations string JSON array of nearby stations for UI selection

Settings (writable)

Path Type Default Description
/Settings/Enabled int 1 0 disables the service
/Settings/MinTideAmplitude float 0.3 Minimum amplitude for observed tide detection
/Settings/Units int 0 Display unit preference
/Settings/DatumOffset float -1.0 Manual datum offset in meters (-1 = automatic)
/Settings/StationOverride string "" Manual NOAA station ID (empty = automatic selection)

Installation

The service is designed for Venus OS (Victron Energy). It installs to /data/dbus-tides/ and registers itself with the daemontools service supervisor.

# Install
chmod +x install.sh
./install.sh

# Uninstall (preserves data in /data/dbus-tides/)
./uninstall.sh

The install script:

  • Locates velib_python and creates a symlink
  • Links the service directory into /service/ (or /opt/victronenergy/service/)
  • Creates /var/log/dbus-tides/ for multilog output
  • Adds an rc.local entry so the service persists across firmware updates

Building

./build-package.sh                        # dbus-tides-1.0.19.tar.gz
./build-package.sh --version 1.1.0        # override version
./build-package.sh --output /tmp/release  # custom output dir

Produces a .tar.gz package and a .sha256 checksum file.

Development Tools

Tools in the tools/ directory require the dependencies listed in tools/requirements-dev.txt (pyTMD, numpy, scipy, pyproj, h5netcdf, xarray).

Tool Purpose
build_noaa_stations.py Fetch NOAA harmonic and subordinate station data from the CO-OPS API
build_coastal_grid.py Build a 0.25° coastal constituent grid from pyTMD / GOT4.10c
predict_tides.py Standalone tide prediction for a given lat/lon
extract_constituents.py Extract constituents for a single position from pyTMD

Configuration

All tunable parameters are in config.py. Key settings:

Parameter Default Description
DEPTH_SAMPLE_INTERVAL 10 s Depth sensor polling rate
DEPTH_AVG_WINDOW 300 s Averaging window for raw readings
DEPTH_HISTORY_HOURS 96 h History retention
MIN_TIDE_AMPLITUDE 0.4 m Minimum observed tide range
SPEED_THRESHOLD_MS 1.03 m/s Speed gate (≈ 2 knots)
STATIONARY_RADIUS_METERS 9260 m Anchorage radius (5 nm)
CHART_DEPTH_RECAL_METERS 152 m Movement to trigger recalibration
MODEL_RERUN_RADIUS_METERS 37040 m Movement to re-run model (20 nm)
PREDICTION_HOURS 48 h Prediction horizon
PREDICTION_INTERVAL 900 s Prediction time step (15 min)
NOAA_STATION_MAX_DISTANCE 92600 m Max station distance (50 nm)
BESTFIT_MIN_HISTORY_HOURS 12 h History required for best-fit scoring
ADAPTATION_MAX_MATCH_WINDOW 10800 s Max time difference for event matching
RESIDUAL_DECAY_HOURS 6 h Residual correction decay half-life

License

See the project repository for license details.