node-red-contrib-norbico 0.3.2
Node-RED nodes for Danish utility APIs: Eloverblik (electricity) and Aalborg Forsyning (district heating). Per-meter outputs, encrypted credentials, MQTT runtime control, automatic retry, and CSV payloads ready for downstream systems like Niagara.
node-red-contrib-norbico
Node-RED nodes for Danish utility APIs. Both nodes fetch consumption data on a schedule, publish one MQTT message per meter, and emit CSV payloads designed to be split directly in Niagara or any other downstream system.
| Node | API | Data |
|---|---|---|
| Eloverblik | api.eloverblik.dk |
Electricity consumption |
| Aalborg Forsyning | services.aalborgforsyning.dk |
District heating & water |
Both appear under the Norbico category in the palette.
Install
cd ~/.node-red
npm install node-red-contrib-norbico
Or use the palette manager: Install tab → search norbico.
Requirements: Node-RED 2.0+, Node.js 14+, and credentials for whichever API you're using.
Quick start
- Drag an Eloverblik node onto the canvas.
- Set Topic prefix to identify the site, e.g.
<prefix>/Eloverblik. Everything the node publishes and listens to is built from this — see Topic prefix below. - Paste your refresh token from eloverblik.dk → Datadeling → Datatilgangs-token.
- Add your metering point IDs under Meters. One output appears per meter.
- Pick a Resolution and Range, set a Schedule if you want it to run daily, and wire each output to an MQTT out node.
- Deploy, then right-click the node → Force update (or send any message to its input) to test.
Topic prefix
The prefix is the single most important field. Both nodes derive every topic from it, with no automatic naming:
<prefix>/Consumption/<meterID> ← data out
<prefix>/Configuration/Token ← config in
<prefix>/Configuration/MeterIDs
<prefix>/Configuration/ForceUpdate
<prefix>/Configuration/ClearMeterCache
The default values are Eloverblik and AalborgForsyning, but these are just
placeholder text — the node does not append its own name. If you type
<prefix>, your topics become <prefix>/Consumption/..., not
<prefix>/Eloverblik/Consumption/....
When running one Node-RED for several sites, include both the site and the utility so the two nodes don't collide:
<prefix>/Eloverblik
<prefix>/AalborgForsyning
<prefix>/Eloverblik
<prefix>/AalborgForsyning
If the node shows "no token" while your Configuration MQTT-in nodes show connected, the prefix doesn't match what those nodes are subscribed to. That mismatch is the most common setup problem.
⚠️ Metering point IDs and MQTT-in nodes
Danish metering point IDs are 18-digit GSRN numbers. They exceed JavaScript's exact-integer limit (2^53), so any numeric conversion silently corrupts them:
Number("571313105200435440").toString() // "571313105200435460"
MQTT carries payloads as bytes, so the broker holds the ID correctly. The damage happens in Node-RED if the MQTT-in node parses it as JSON — a bare 18-digit number is valid JSON.
Set the Output of any MQTT-in node feeding MeterIDs to "a String".
Not auto-detect, not a parsed JSON object. Note that a comma-separated list
of several IDs isn't valid JSON and survives auto-detect intact, so this bug
only shows up with a single meter — which makes it easy to miss.
Since 0.2.2 the Eloverblik node defends itself: it rejects numeric payloads outright, validates the GS1 check digit, keeps the previous meter list rather than caching a corrupted one, and tells you which setting to change. The Aalborg node has no such validation, so set its MQTT-in nodes to string too.
Eloverblik
Fetches electricity consumption time series from Energinet's customer API.
Configuration:
| Field | Notes |
|---|---|
| Refresh token | Encrypted at rest, roughly 1 year validity |
| Resolution | Actual / Quarter / Hour / Day / Month / Year |
| Range | Presets (yesterday, last 7 days, last full month, …) or custom dates |
| Meters | One output per meter, in dialog order |
| Output topic | Stable (default) or Timestamped — see below |
| Schedule | Optional HH:MM daily run |
| Retry | Attempts, initial delay, backoff multiplier |
Output topic
Stable (default, since 0.3.0) publishes one message per meter per fetch to a fixed topic:
SiteNameHere/Eloverblik/Consumption/571313105200435440
The topic never changes — not between fetches, and not when you change Resolution or Range. That's what a fixed-topic subscriber like Niagara needs. The resolution travels in field 6 of the payload instead.
A single fetch can return many periods (three months for last full month, ~720 for Hour over 30 days). The node sorts them chronologically — the API does not return them in order — publishes the most recent one, and summarises the rest in the appended fields.
Timestamped is the pre-0.3.0 behaviour: one message per period, with the period start appended to the topic. Use it if you want the broker to hold the history as separate retained records. Note the topic moves on every fetch, so subscribers need a wildcard.
Payload (CSV, 16 fields)
571313105200435440,2026-08-01T00:00:00,2372.660,2026-07-01T00:00:00,2026-08-01T00:00:00,Month,1,A04,KWH,Input,1,2372.660,2026-07-01,2026-08-01,,0
| # | Field | Notes |
|---|---|---|
| 1 | Metering point ID | 18-digit GSRN |
| 2 | Timestamp | Representative time = period end, local |
| 3 | Value | Latest period, 3 decimals |
| 4 | Period start | Local |
| 5 | Period end | Local — same as field 2 by definition |
| 6 | Resolution | Month, Hour, … |
| 7 | Position | Point index within the period |
| 8 | Quality | See codes below |
| 9 | Unit | Usually KWH |
| 10 | Trigger | Input, Schedule, Retry, … |
| 11 | Period count | Periods returned for this meter |
| 12 | Range total | Sum of all periods in the range |
| 13 | Date from | Query start, YYYY-MM-DD |
| 14 | Date to | Query end, YYYY-MM-DD |
| 15 | Previous value | Period before the latest — empty if only one period |
| 16 | Non-measured count | Periods where quality ≠ A04 |
Timestamps in fields 2, 4 and 5 are Europe/Copenhagen local time.
Fields 1–10 are unchanged from earlier versions, so existing parsers keep working after upgrading. Fields 11–16 were appended, not inserted.
When parsing fields 11–16, note that field 15 is empty whenever field 11 is
1 — there's no previous period to report. Handle the empty string rather
than assuming it parses as a number.
Quality codes (ENTSO-E / Energinet)
| Code | Meaning |
|---|---|
A04 |
Measured — a real reading |
A03 |
Estimated — the reading didn't arrive in time |
| Other | Adjusted, substituted, or unavailable |
Treat A04 as authoritative. Field 16 counts everything else, so you can judge
how much of the range total is estimated without parsing each period.
MQTT runtime control
| Topic suffix | Effect |
|---|---|
/Configuration/Token |
Update refresh token (persists encrypted) |
/Configuration/MeterIDs |
Override meter list — comma-separated, or empty for all meters on the account |
/Configuration/ForceUpdate |
Trigger a fetch (truthy payload) |
/Configuration/ClearMeterCache |
Drop the MQTT override and revert to the config dialog list |
Publish these retained so they survive a Node-RED restart. Note that
Mosquitto needs persistence true in its config for retained messages to
survive a broker restart.
ClearMeterCache exists because publishing an empty string to MeterIDs means
"all meters on the account", which is a different thing — it's not a way back to
the configured list.
Output count and MQTT-set meters
A node's output count is a deploy-time property. Setting meters over MQTT updates the meter list but cannot add outputs, so data for meters beyond the last wired output is discarded. The node warns when this happens.
To resync: open the node's config dialog (it repopulates from the MQTT-cached list), click Done, then Deploy.
On a central multi-site server it's usually simpler to enter meters in the dialog and skip the MQTT override entirely — you're editing the flow anyway, and the output count then stays correct automatically.
Aalborg Forsyning
Fetches district heating and water meter data. Meter type is detected from the API response; each profile has its own CSV format and topic segment.
Configuration:
| Field | Notes |
|---|---|
| Username + password | Encrypted at rest |
| Meters | One output per meter; heating and water can be mixed in one node |
| Schedule | Optional HH:MM daily run |
API limits enforced by the node
- 3 calls per 24 hours (the official limit)
- 5-hour minimum between scheduled calls —
ForceUpdatebypasses this - Budget persisted to disk, so it survives Node-RED restarts
Because calls are scarce, the node keeps daily counter snapshots per meter (62-day
retention) and computes daily, current-month and previous-month consumption from
them between API calls. Confidence labels (complete / partial /
unavailable) tell you whether snapshot coverage was sufficient.
Heating
Topic: <prefix>/Consumption/Heating/<meterID>
Payload (CSV, 16 fields):
meterID, timestamp, energy_MWh, volume_m3, temp_supply, temp_return, flow,
operating_hours, readings_count, daily_MWh, currentMonth_MWh,
previousMonth_MWh, currentMonth_daysMissing, currentMonth_confidence,
previousMonth_confidence, trigger
Fields 1–13 match the legacy EnergyMeter_xxx function-node format.
Water
Topic: <prefix>/Consumption/Water/<meterID>
Payload (CSV, 11 fields):
meterID, timestamp, volume_m3, readings_count, daily_m3, currentMonth_m3,
previousMonth_m3, currentMonth_daysMissing, currentMonth_confidence,
previousMonth_confidence, trigger
The Heating / Water topic segment exists here because the two payload
layouts genuinely differ — downstream systems can route on the topic without
inspecting the payload. The Eloverblik node has no equivalent segment because
every resolution produces the same layout.
MQTT runtime control
| Topic suffix | Effect |
|---|---|
/Configuration/Username |
Update username (persists encrypted) |
/Configuration/Password |
Update password (persists encrypted) |
/Configuration/MeterIDs |
Override meter list |
/Configuration/ForceUpdate |
Trigger a fetch |
State files
In the Node-RED user directory:
norbico-aalborg-<nodeId>.json— call history + heating snapshotsnorbico-aalborg-<nodeId>-water.json— water snapshots
Writes are atomic (temp file + rename), so a crash mid-write can't corrupt them.
Common behaviour
- Encrypted credentials in Node-RED's credential store. Credentials set over MQTT are persisted encrypted too.
- One output per meter, in dialog order.
msg.debugcarries the same data as a structured object alongside the CSV inmsg.payload— useful for inspection without parsing.msg.retainis set on stable-topic messages, but the MQTT out node's own Retain setting takes priority. Set Retain totruethere.- Automatic retry on transient failures (HTTP 5xx, 408, 429, network errors) with configurable exponential backoff. Permanent errors (4xx) don't retry.
- Optional daily schedule as
HH:MM. For anything more complex, drive the node from an inject node or your own scheduler.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| Status shows "no token" but MQTT-in nodes are connected | Topic prefix doesn't match the Configuration topics |
Configured meters not found on account |
Meter ID corrupted — set the MQTT-in Output to "a String" (see above), or the metering point isn't authorised on the account |
MeterIDs arrived as a number |
Same — the MQTT-in node is parsing JSON |
| Fixed a meter ID by hand but it reverts | A corrupted ID is cached in flow context and repopulates the dialog. Publish to ClearMeterCache |
| Nothing arrives at the subscriber | Retain not set on the MQTT out node, or a topic in the out node's Topic field overriding msg.topic (leave it empty) |
| Data for some meters missing | More meters configured than wired outputs — reopen the dialog, click Done, redeploy |
| Aalborg node refuses to fetch | Daily 3-call budget spent, or inside the 5-hour window. Use ForceUpdate to bypass the window |
License
MIT — see LICENSE.