This is my first post after I’ve announced moving from Homeseer to Home Assistant more or less a year ago. This blog is not only for showing things the the outside world but also reminds me how things have been done. In case something brakes down, I use my own posts to restore it.
This post is all about the Dutch Energy Weather Forecast dashboard that is provided here: https://ned.nl/nl/energieweerbericht#Energieweerbericht. This overview shows you which days generate the most solar and wind energy. Curious about which days generate the most sustainable electricity? Decide yourself which day fits best your residence energy consumption.
Things needed:
- Home Assistant (tested on v2025.10.2)
- MQTT broker
- Node Red (added as add-on in Home Assistant. no need to have a working connection to HA though)
- MQTT Integration in HA
- ApexCharts card (via HACS)
This post will not explain how above integrations are being configured, there are other sources available online for this.
An example overview could look as follows on that earlier mentioned website (in Dutch):
What data is presented per day?
- Day of the week in Dutch
- Wind speed and direction
- Cloud type
- In Blue: wind production in %
- In Yellow: solar production in %
- The horizontal line means the average production for that type of energy.
Ideally, we want this data available in Home Assistant, where we’d like to have a similar chart card that represents this data and maybe more important, use it in automations.
This dashboard data is available online, for free (not commercial) and can be grabbed via API or directly via GET in the browser: https://ned.nl//sites/default/files/dataset_chart_jsons/Energy_Weather_Forecast.json
I’ve seen a few online posts that people wants to have this but there is no integration available. I’ve tried myself a few things, like with a rest sensor to grab information directly from this url, but it’s not a success (lack of yaml knowledge at this moment to extract json data from the raw data), For now I’m using Node-Red as man in the middle to extract the data from the json response and feed it into mqtt. Home Assistant will grab the information from the mqtt entities automatically.
Building:
First of all, make sure the json url still works by entering the following url in a new browser tab: https://ned.nl//sites/default/files/dataset_chart_jsons/Energy_Weather_Forecast.json. You should see something like:
Or in Pretty print, per day of the week the following data is presented:
{
"avgTemp": "12",
"cloudType": "3",
"date": "2025-10-17",
"gasAverage": "51",
"gasHeight": "60",
"gasRelative": "20",
"sunAverage": "47",
"sunHeight": "70",
"windAverage": "30",
"windDirection": "267",
"windHeight": "19",
"windspeed": "2"
},
Personally, I’m only interested in the date, sunHeight and windHeight. The date needs to be translated to a weekday name with only the first 3 letters ==> MON, TUE, WED. This in preparation for the x-axis on the chart.
Node-Red
Node-Red is step one. The flow needs to run once a day, grab the response from the json url, convert it into JSON, use a function to extract the right key value pairs and push it into mqtt. Explanation of the flow:
Inject node:
This Inject node, starts at a specific time, at 02:05am on every day of the week.
Http request node:
This will grab the json response from the url.
JSON node:
It converts the string response from the url into a JSON format type.
Function node:
This function will extract all necessary data from the response and prepares the data to publish to the MQTT broker.
Be aware, this flow focus only on the avgTemp, sunHeight and windHeight, however it’s possible to enhance it with the other JSON keys as well, like gas, temperature etc. These sections are marked in red color.
let rows = msg.payload;
if (typeof rows === 'string') {
try { rows = JSON.parse(rows); } catch (e) { node.error('Payload is not JSON'); return null; }
}
if (!Array.isArray(rows) || rows.length === 0) { node.warn('No rows'); return null; }
// ---- Settings ----
const discoveryPrefix = 'homeassistant';
const device = {
identifiers: ['ned_energy_weather'],
name: 'NED Energy Weather',
manufacturer: 'NED',
model: 'Energy_Weather_Forecast.json'
};
// Which fields to expose as HA sensors (keys must match your JSON)
const FIELDS = [
'avgTemp', 'sunHeight', 'windHeight'
];
// Optional units per key (leave blank for none)
const UNITS = {
avgTemp: '°C',
sunHeight: '%',
windHeight: '%'
};
// Helper: weekday label (3 letters, upper)
function dow3(iso) {
try {
const dt = new Date(iso);
return dt.toLocaleDateString('en-US', { weekday: 'short' }).toUpperCase();
} catch { return 'N/A'; }
}
function cfgMsg(id, name, unit) {
const cfgTopic = `${discoveryPrefix}/sensor/ned/${id}/config`;
const stateTopic = `${discoveryPrefix}/sensor/ned/${id}/state`;
const attrTopic = `${discoveryPrefix}/sensor/ned/${id}/attributes`;
const cfg = {
name, unique_id: id, object_id: id,
state_topic: stateTopic,
json_attributes_topic: attrTopic,
device
};
if (unit) cfg.unit_of_measurement = unit;
return { topic: cfgTopic, payload: JSON.stringify(cfg), retain: true };
}
function stateMsgs(id, state, attrs) {
const stateTopic = `${discoveryPrefix}/sensor/ned/${id}/state`;
const attrTopic = `${discoveryPrefix}/sensor/ned/${id}/attributes`;
return [
{ topic: stateTopic, payload: (state ?? 'unknown').toString(), retain: true },
{ topic: attrTopic, payload: JSON.stringify(attrs || {}), retain: true }
];
}
// 1) Publish configs for ALL sensors first (labels + every whitelisted field)
for (let i = 0; i < 7; i++) {
const labelId = `ned_day_${i+1}_label`;
node.send(cfgMsg(labelId, `NED Day ${i+1} Label`, ''));
for (const key of FIELDS) {
const id = `ned_${key}_day_${i+1}`;
node.send(cfgMsg(id, `NED ${key} Day ${i+1}`, UNITS[key] || ''));
}
}
// small gap so HA registers configs before states
node.send({ delay_marker: true });
// 2) Publish states per day index + raw row per date
for (let i = 0; i < Math.min(rows.length, 7); i++) {
const row = rows[i];
const date = row.date;
const label = dow3(date);
// Per-date combined JSON (handy for other consumers)
node.send({
topic: `ned/energyweather/${date}/state`,
payload: JSON.stringify({ ...row, label }),
retain: true
});
// Label entity
stateMsgs(`ned_day_${i+1}_label`, label, { date }).forEach(m => node.send(m));
// Whitelisted fields
for (const key of FIELDS) {
let v = row[key];
// try to coerce numeric strings to numbers, then back to string for HA
if (v !== null && v !== undefined && v !== '' && !isNaN(Number(v))) v = Number(v);
const attrs = { date, label, key };
stateMsgs(`ned_${key}_day_${i+1}`, v, attrs).forEach(m => node.send(m));
}
}
// 3) Diagnostic entity (shows first row and keys)
const diagId = 'ned_energy_weather_diag';
node.send(cfgMsg(diagId, 'NED Energy Weather Diagnostics', ''));
node.send({ delay_marker: true });
stateMsgs(diagId, 'ok', {
keys: Object.keys(rows[0] || {}),
sample: rows[0] || {},
count: rows.length
}).forEach(m => node.send(m));
return null;
Delay node:
Since a lot of data is generated by the function recommended a delay before it’s getting pushed to the MQTT broker.
MQTT Out node:
This node is connecting to a MQTT broker in my network. Make sure you adjust this node and change this to you own MQTT broker.
Deploy the full flow in Node-Red and make sure there are no errors generated.
MQTT Explorer:
Once the Node-Red flow is successfully deployed and ran a few times, you can check if the data is successfully generated. on my Windows desktop, I use the free tool “MQTT Explorer” to review this.
MQTT in HA:
Since all entities are created under the topic “homeassistant”, in my case, the MQTT integration will recognize any entity that is created here. If we open this integration in HA, it can look like:
A few of the entities will look like this:
ApexCharts card:
Time to present the mqtt entities in a card on the dashboard.
I’m using the ApexCharts card integration via HACS. A fully customizable chart card that fit my needs. I basically want to reproduce the energy dashboard from the website on my dashboard, with the focus on weekday name, sunHeight and windHeight. The avgTime is not added here (yet).
Open your dashboard, add a manual card and paste the following yaml into it:
type: custom:apexcharts-card
header:
show: true
title: Energy Weather 7d
now:
show: true
apex_config:
chart:
type: bar
plotOptions:
bar:
horizontal: false
columnWidth: 40%
endingShape: rounded
stroke:
width: 0
legend:
show: false
position: bottom
horizontalAlign: center
xaxis:
type: category
yaxis:
- decimals: 0
min: 0
max: 100
series:
- name: Sun
type: column
color: rgb(255, 155, 48)
entity: sensor.ned_sunheight_day_1
data_generator: |
const S = (e) => Number(states[e]?.state);
const L = (e) => states[e]?.state || 'N/A';
return [
{ x: L('sensor.ned_day_1_label'), y: S('sensor.ned_sunheight_day_1') },
{ x: L('sensor.ned_day_2_label'), y: S('sensor.ned_sunheight_day_2') },
{ x: L('sensor.ned_day_3_label'), y: S('sensor.ned_sunheight_day_3') },
{ x: L('sensor.ned_day_4_label'), y: S('sensor.ned_sunheight_day_4') },
{ x: L('sensor.ned_day_5_label'), y: S('sensor.ned_sunheight_day_5') },
{ x: L('sensor.ned_day_6_label'), y: S('sensor.ned_sunheight_day_6') },
{ x: L('sensor.ned_day_7_label'), y: S('sensor.ned_sunheight_day_7') },
].filter(p => !isNaN(p.y));
- name: Wind
type: column
color: rgb(80, 150, 255)
entity: sensor.ned_windheight_day_1
data_generator: |
const S = (e) => Number(states[e]?.state);
const L = (e) => states[e]?.state || 'N/A';
return [
{ x: L('sensor.ned_day_1_label'), y: S('sensor.ned_windheight_day_1') },
{ x: L('sensor.ned_day_2_label'), y: S('sensor.ned_windheight_day_2') },
{ x: L('sensor.ned_day_3_label'), y: S('sensor.ned_windheight_day_3') },
{ x: L('sensor.ned_day_4_label'), y: S('sensor.ned_windheight_day_4') },
{ x: L('sensor.ned_day_5_label'), y: S('sensor.ned_windheight_day_5') },
{ x: L('sensor.ned_day_6_label'), y: S('sensor.ned_windheight_day_6') },
{ x: L('sensor.ned_day_7_label'), y: S('sensor.ned_windheight_day_7') },
].filter(p => !isNaN(p.y));
The results would look like this:
Related Posts
October 30, 2024
Bye Bye Homeseer…..Welcome Home Assistant!
August 2, 2017
From HSTouch to realtime responsive dashboard
September 25, 2014












