npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

signalk-dmi

v0.6.0

Published

Signal K plugin that ingests DMI (Danish Meteorological Institute) free open data — HARMONIE forecasts, met observations, WAM waves, DKSS ocean (sea level, current, water temperature) — into Signal K paths and the v2 Weather API. Authoritative Danish met/

Readme

signalk-dmi

A Signal K server plugin that ingests DMI (Danish Meteorological Institute) free open data — authoritative Danish met/ocean data — into Signal K, richer than the generic OpenWeather provider for Danish, Baltic and North Sea waters.

It uses DMI's Open Data API (host opendataapi.dmi.dk, no API key required, licensed CC BY 4.0).

Screenshots

Plugin configuration — with in-UI InfluxDB history guidance:

signalk-dmi plugin configuration

Live DMI data flowing into Signal K environment.* paths:

signalk-dmi live data in the Signal K Data Browser

Requirements

  • Signal K server — the Node.js signalk-server. The environment.* deltas work on any recent version; the v2 Weather API provider feature needs a v2-capable server (Signal K server ≥ 2.x).
  • Node.js 18 or newer (whatever your Signal K server runs on).
  • Platforms — anything that runs Signal K server: Linux (incl. Raspberry Pi ARM), macOS, Windows, and Docker. No native/compiled dependencies (this plugin has zero runtime dependencies), so no build tools are needed.
  • Outbound internet to https://opendataapi.dmi.dk.
  • No DMI API key, no account, no registration. DMI's Open Data API is free and key-free (DMI Open Data — Authentication).
  • A vessel position — either live navigation.position (GPS) or a configured fixed latitude/longitude (dock/shore install or testing).
  • Coverage — Danish, North Sea and Baltic waters (DMI's model domains). Outside those areas the plugin simply publishes nothing.

No database is required (see History and trends below). Data licence: DMI data is CC BY 4.0 (attribution required — see Attribution / licence). Not for navigation — informational only (see the Disclaimer).

Install steps for a fresh server are in INSTALL.md.

History and trends (optional)

The DMI paths publish live into Signal K with no extra setup — no database, no InfluxDB, nothing else to install. This plugin does not require InfluxDB.

If you want historical data or trend graphs (sparklines, forecast-vs-actual), install the separate signalk-to-influxdb2 plugin, point it at an InfluxDB 2.x bucket, and allow-list the DMI paths in its filteringRules (keep the deny-all .* last):

"filteringRules": [
  { "allow": true,  "path": "environment\\.outside\\." },
  { "allow": true,  "path": "environment\\.water\\." },
  { "allow": true,  "path": "environment\\.tide\\." },
  { "allow": true,  "path": "environment\\.current\\." },
  { "allow": false, "path": ".*" }
]

These are exactly the environment.* groups signalk-dmi publishes; the plugin shows this same list (and detects whether it's already applied) in its own config UI — see History & trends status there. Then verify with the Signal K v2 History API (/signalk/v2/api/history/values?paths=environment.water.temperature&from=…&to=…). Full step-by-step in INSTALL.md → history & trends. This is entirely optional and a feature of that plugin, not a requirement of signalk-dmi.

What it publishes

The plugin feeds Signal K on two surfaces:

1. Signal K v2 Weather API provider

Registers DMI as a weather provider under /signalk/v2/api/weather:

  • Forecasts (/forecasts/point, /forecasts/daily) from HARMONIE (harmonie_dini_sf), merged with WAM waves and DKSS ocean into each forecast step's water.* block.
  • Observations (/observations) from the nearest metObs land/coastal station, plus nearest oceanObs sea level / water temperature.
  • Warnings (/warnings) — official DMI weather/marine warnings sourced from the EUMETNET/MeteoAlarm CAP feed (the DMI Open Data API has no warnings collection). Also raised as notifications.environment.warnings.dmi.*.

2. environment.* deltas

Current conditions + marine, so any consumer reading environment.outside.wind.* (a drop-in for the OpenWeather provider) gets live data:

| Path | Source | Unit | |---|---|---| | environment.outside.temperature | metObs | K | | environment.outside.dewPointTemperature | metObs | K | | environment.outside.pressure | metObs | Pa | | environment.outside.humidity | metObs | ratio | | environment.outside.wind.speed | metObs | m/s | | environment.outside.wind.gust | metObs | m/s | | environment.outside.wind.direction | metObs | rad | | environment.outside.horizontalVisibility | metObs | m | | environment.outside.cloudCover | metObs | ratio | | environment.outside.precipitationRate | metObs | m/s | | environment.water.temperature | oceanObs → DKSS | K | | environment.water.level / environment.tide.heightNow | oceanObs → DKSS | m | | environment.water.waves.significantHeight / .period / .direction | WAM | m / s / rad | | environment.water.swell.height / .period / .direction | WAM | m / s / rad | | environment.current.drift / environment.current.setTrue | DKSS → Open-Meteo | m/s / rad | | environment.water.waves.nearbyDistance / .nearbyBearing | WAM (surrounding-area marker) | m / rad | | environment.current.nearbyDistance / environment.current.nearbyBearing | DKSS (surrounding-area marker) | m / rad | | environment.water.waves.source / environment.current.source / environment.water.temperature.source | provenance marker | string |

All values are converted to Signal K SI units (K, Pa, m/s, rad, ratio); ocean current is derived from the model's u/v components (set = direction flowing toward).

Marine surrounding-area sourcing (grid gaps)

DMI's marine models (WAM waves/swell, DKSS ocean/current) are gridded, and the boat's exact position is frequently a gap — a coastal or land cell, an interior hole, or a spot just outside the model domain — so a point query returns nothing even though the water a mile away is fully modelled. To avoid empty wave/swell/current fields in that common case, the fetch layer samples the surrounding area:

  1. query the exact point first (preferred whenever it has data);
  2. if a WAM or DKSS collection has no usable numeric data at the point, probe a small capped set of nearby offset points (2 rings, ≤ 8 points — the cardinals at ~0.05° / ~3 nm and the diagonals at ~0.10° / ~6 nm, all within the ~15 nm reach), nearest first, spaced by a short delay, and use the first grid point with numeric data as-is (no interpolation, no fabrication) — stopping immediately on that hit;
  3. only report empty when the whole capped area has no data.

The probe is deliberately gentle on DMI, which HTTP-429 rate-limits the marine EDR:

  • A 429 is backoff, not data. If any call returns 429, the plugin stops probing for that poll and relies on the next one — a 429 is never mistaken for an empty cell that would fuel more probing (the failure mode that made an earlier build amplify the 429).
  • Cooldown (bounded). When a collection's capped probe comes back with nothing — genuinely empty or 429-aborted — the plugin remembers that for ~2 poll cycles and, meanwhile, only tries the single exact point (no ring), so it does not re-run the whole probe every 30-minute poll and re-trigger the limit. A numeric hit clears the cooldown at once; otherwise it expires and the area is probed again. The cooldown is capped at 2 h and, crucially, a bare 429 on the single exact-point call does not open or extend it — only a real ring probe does — so a sustained 429 storm can never keep re-extending the window and wedge the ring dark (see Self-recovery below).

Null-masked cells (why this keys on numeric presence, not row count). A masked DMI grid cell — land, out of the wet domain, or otherwise no-data — does not return an empty response; it returns a full-length time series whose parameter values are all null (common at coastal/inner-bay points). "Has data" is therefore judged by the presence of a numeric value for a requested parameter, never by the row count, or a null-masked cell would be mistaken for a valid reading and the ring would never fire. A cell with a real number in any requested parameter (e.g. DKSS water-temperature present while current-u/v are null) counts as present, so working ocean temp/level/tide are never ringed away.

Current gets its own focused probe. Because a mixed DKSS cell (temp/level present, current null) counts as "has data" above, the surrounding-area ring would not fire for the missing current vector — so environment.current.drift/setTrue stayed absent wherever the boat's exact cell is masked for current but modelled for temp/level (the common coastal case). When the primary DKSS result yields no current, the plugin now runs a focused current probe that rings specifically for the current vector — requiring both current-u and current-v — on its own cooldown key, and publishes the nearest wet current cell with its own environment.current.nearbyDistance/nearbyBearing marker. It never disturbs the temp/level path.

When a value comes from an offset point, the plugin publishes a small honest-display marker — …nearbyDistance (m) and …nearbyBearing (rad) — so a display can label the reading "nearby ~X nm" rather than presenting it as measured at the boat. The marker is cleared (null) as soon as the exact point returns data again. This is a fetch-layer resilience feature only: the requested parameters, the SI mapping, and the marine value paths are unchanged.

Reliable marine fallback (Open-Meteo) — waves never vanish

Even with the surrounding-area probe, DMI can leave marine data empty: the boat sits in a wide coastal grid gap, the WAM/DKSS domain doesn't reach, or a 429 storm blocks the fetch. Waves and swell then disappear for hours or days. To stop that, an optional fallback fills any empty marine path from the Open-Meteo Marine API — free, no API key, generous rate limits, global; source models GWAM / EWAM / MFWAM.

  • DMI stays primary. Open-Meteo is only queried when DMI's exact point and its surrounding-area ring already came up empty for a group (waves/swell, current, or SST). Where DMI has data, it is used unchanged — authoritative for Danish waters.
  • Honest provenance. Each filled group publishes a …source string: environment.water.waves.source, environment.current.source, environment.water.temperature.source = DMI-WAM / DMI-DKSS / DMI when DMI supplied it, Open-Meteo when the fallback filled it, or DMI-WAM + Open-Meteo for a mixed wave block. You always know which source each value came from.
  • Same paths, same units. Open-Meteo values land on the identical environment.water.waves.* / .swell.* / environment.current.* / environment.water.temperature paths, converted to SI (km/h→m/s, °→rad, °C→K).
  • Robust + non-blocking. Per-request AbortController timeout, fully error-caught (never crashes the plugin), and integrated into the same self-recovery poll loop.
  • Toggle: Reliable marine fallback (Open-Meteo) (openMeteoFallback, default ON).

Self-recovery (the poll loop never wedges)

DMI's marine EDR is aggressively 429 rate-limited, and a sustained busy period must never leave the plugin permanently dark. The poll loop is self-healing:

  • Guaranteed re-arm. Each poll self-schedules the next one in a finally, so a thrown error, a rejected promise, a 429 storm, or a hung fetch can never stop future polls. (A slow poll can't overlap the next either — the next is armed only after the current completes.) Position not in the model yet at boot re-arms in 30 s rather than a full interval.
  • Bounded backoff. The ring-probe cooldown is capped at 2 h and is not re-extended by bare exact-point 429s, so the surrounding-area probe is always retried within the cap.
  • No frozen-forever data. A last-good value keeps re-publishing across a brief source outage, but past ~4 poll cycles (~2 h at the default) a still-silent source is dropped and the path goes absent — an honest "no data" instead of a stale value wearing a fresh timestamp. Earlier builds could re-publish frozen marine values indefinitely, so a dead feed looked alive until a manual Signal K restart; that can no longer happen.

Install

From the Signal K Appstore (recommended)

  1. Open your Signal K server admin UI → Appstore → Available.
  2. Search for signalk-dmi (category Weather) and click Install.
  3. Restart the server when prompted.

The Appstore lists this plugin because it is published to npm with the signalk-node-server-plugin keyword; installs and updates are handled for you from the admin UI.

From npm (manual)

npm install signalk-dmi

run in your Signal K config directory (~/.signalk), then restart the server.

From a local tarball (development / offline)

Install the registered-tarball way so it shows in the admin UI and survives node_modules rebuilds — see INSTALL.md for the full standard. In short: npm pack → copy the .tgz into the server config dir → npm install ./signalk-dmi-<version>.tgz there → restart. Do not hand-copy files into node_modules.

Enable

Enable DMI (Danish Meteorological Institute) free data in Server → Plugin Config, and (optionally) select it as the Weather API provider.

Configuration

Every field has in-UI help text (its description). Full reference:

| Field (title) | Key | Type | Default | What it does | |---|---|---|---|---| | Position source | positionSource | enum: Vessel GPS / Fixed | vessel | Where DMI is queried. Vessel GPS follows navigation.position (use underway); Fixed uses the lat/lon below (dock/shore install). | | Fixed latitude | fixedLatitude | number (deg) | — | Decimal degrees WGS84, N+. Used only when Position source = Fixed. | | Fixed longitude | fixedLongitude | number (deg) | — | Decimal degrees WGS84, E+. Used only when Position source = Fixed. | | Poll interval | pollIntervalMinutes | number (min, ≥5) | 30 | Refresh cadence for observations + marine deltas. Lower = fresher, more API calls. Does not affect the on-demand forecast. | | Forecast window | forecastHours | number (h, 1–120) | 48 | Look-ahead horizon of the v2 Weather API forecast. Length only, not refresh rate. | | Register as Weather API provider | weatherProvider | boolean | true | ON = serve /signalk/v2/api/weather. Set OFF to run additively beside OpenWeather (deltas still publish). | | Publish current conditions | observations | boolean | true | Nearest-station metObs → environment.outside.* (temp, pressure, humidity, dew point, wind/gust/dir). | | Publish extended conditions | tier1 | boolean | true | Adds environment.outside.horizontalVisibility, cloudCover, precipitationRate (HARMONIE surface). | | Fetch weather warnings | warnings | boolean | true | Official warnings from the EUMETNET/MeteoAlarm CAP feed → Weather API getWarnings. | | Raise warnings as notifications | warningNotifications | boolean | true | Active warnings → notifications.environment.warnings.dmi.*. No effect unless Fetch warnings is ON. | | Warning countries | warningCountries | multi-select enum (38 MeteoAlarm countries) | [denmark] | Which national MeteoAlarm feeds to pull. Add neighbours when crossing borders. Only used when Fetch warnings is ON. | | Publish wave data | waves | boolean | true | WAM model → environment.water.waves.* + .swell.* (height, period, direction). | | Publish ocean data | ocean | boolean | true | DKSS model → environment.water.temperature, water.level, tide.heightNow, current.drift/setTrue. | | Reliable marine fallback (Open-Meteo) | openMeteoFallback | boolean | true | Fill waves/swell/current/SST from the free Open-Meteo Marine API when DMI has a grid gap or 429s, so marine data never vanishes. Each group tagged with a …source marker. | | HARMONIE forecast model | harmonieCollection | enum | harmonie_dini_sf | Surface (SF) model for atmospheric data. DINI = DK/NL/IE/IS (keep for Danish waters); IG = Iceland/Greenland. | | WAM wave model | wamCollection | enum | wam_dw | Wave domain: Danish Waters (best near DK), North Sea & Baltic (wider), North Atlantic (offshore). | | DKSS ocean model | dkssCollection | enum | dkss_nsbs | Ocean domain: North Sea–Baltic (widest), Inner Danish Waters, Wadden Sea, Limfjord, Little Belt, Roskilde/Isefjord. Smallest domain covering your position = best resolution. |

MeteoAlarm country values (warningCountries, lowercase feed slugs): austria, belgium, bosnia-herzegovina, bulgaria, croatia, cyprus, czechia, denmark, estonia, finland, france, germany, greece, hungary, iceland, ireland, israel, italy, latvia, lithuania, luxembourg, malta, moldova, montenegro, netherlands, norway, poland, portugal, republic-of-north-macedonia, romania, serbia, slovakia, slovenia, spain, sweden, switzerland, ukraine, united-kingdom.

Collection ids are DMI Open Data forecastedr collection identifiers (source of truth: https://opendataapi.dmi.dk/v1/forecastedr/collections). Only the values in the enums above are valid for this plugin.

Coverage

  • HARMONIE DINI: Denmark + surrounding NE Europe.
  • WAM wam_dw: Danish waters (7–16°E, 53–60°N); wam_nsb / wam_natlant extend to North Sea/Baltic / North Atlantic.
  • DKSS dkss_nsbs: entire North Sea + Baltic (−4.1–30.3°E, 48.5–65.9°N).

Outside these boxes DMI returns no data and the plugin simply publishes nothing.

Development

npm test        # unit tests for unit conversions + mapping (node:test, no deps)

Attribution / licence

Weather and ocean data © DMI (Danish Meteorological Institute), licensed under Creative Commons CC BY 4.0. When using this data you must credit DMI, link the licence, and indicate changes.

Warnings are sourced from MeteoAlarm (EUMETNET), which republishes DMI's official warnings under CC BY 4.0 with additional redistribution terms; the plugin labels the warning source "DMI / MeteoAlarm".

The optional marine fallback uses Open-Meteo (Marine Weather API, models GWAM/EWAM/MFWAM), whose data is licensed CC BY 4.0. Filled values are labelled Open-Meteo in their …source marker.

Plugin code: MIT — see LICENSE.

Disclaimer — informational only, NOT for navigation

This plugin and the data it publishes are provided for general information only and are NOT a substitute for official nautical charts, notices to mariners, or an official marine forecast. Do not rely on it for navigation or for safety-of-life decisions. Weather, wave, ocean, and warning data may be delayed, incomplete, wrong, or unavailable, and the plugin may fail silently.

The software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND, express or implied (see LICENSE). Neither the author nor DMI nor MeteoAlarm is liable for any loss or damage arising from its use. You are responsible for verifying all data against authoritative sources before acting on it.