larsea-dk avatar image
larsea-dk asked

Nordpool Elpris integration


I just eant to hear if anyone has done some Nordpool integration to get prices into the NodeRed and control i.e. to stop/deactivate feedin when price is under at chosen value?

I have already done a little to get daily prices into an array, but I simply cant figure out how to work with arrays.

1) I want it to pick the price in the array(price per hour) for the hour(clock) of the system, so I can turn on and off the feedin. I also need to figure out how to read to the system to disable/enable feedin excess.

2) it could be great to calculate the feedin excess kwh and multiply with the price of the hour. This gives a sum of earned money of sold power to grid.


Lars (Denmark)

1 comment
2 |3000

Up to 8 attachments (including images) can be used with a maximum of 190.8 MiB each and 286.6 MiB total.

kevgermany avatar image kevgermany ♦♦ commented ·

Moving this to the modifications s0ace. Try searching there as well, lots of Node-Red topics there.

0 Likes 0 ·
3 Answers
larsea-dk avatar image
larsea-dk answered ·

Number 1) solved:


[{"id":"26d2970317042ea2","type":"inject","z":"b92a5966b4315b50","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"0 0-23 * * *","once":true,"onceDelay":0.1,"topic":"1","payload":"1","payloadType":"msg","x":146,"y":196,"wires":[["8ae247d0d694fece"]]},{"id":"8ae247d0d694fece","type":"nordpool-api","z":"b92a5966b4315b50","area":"DK1","currency":"DKK","timeSpan":"hourly","x":282,"y":196,"wires":[["2a6e87a3f62df8f9"]]},{"id":"2a6e87a3f62df8f9","type":"function","z":"b92a5966b4315b50","name":"Get Price current Hour","func":"const data = msg.payload;\nconst now = new Date();\nconst nearestHour = roundDate(now);\nconst findTimestamp = nearestHour.valueOf();\n\nmsg.payload = data.find(e => {\n return (new Date(e.Timestamp)).valueOf() == findTimestamp;\n});\n\nreturn msg;\n//Change 0 to -1 if time do not fit\nfunction roundDate(date) {\n date.setHours(date.getHours() -0 + Math.round(date.getMinutes() / 60));\n date.setMinutes(0, 0, 0);\n return date;\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":472,"y":196,"wires":[["646e449b84c58ef6"]]},{"id":"646e449b84c58ef6","type":"change","z":"b92a5966b4315b50","name":"","rules":[{"t":"move","p":"payload.Price","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":710,"y":196,"wires":[["be3f5cfce2726d38"]]},{"id":"be3f5cfce2726d38","type":"range","z":"b92a5966b4315b50","minin":"0","maxin":"10000","minout":"0","maxout":"10","action":"scale","round":false,"property":"payload","name":"","x":888,"y":196,"wires":[["cfd7b6cbcaef03f4","9e2a8c3f2b008ff3"]]},{"id":"cfd7b6cbcaef03f4","type":"switch","z":"b92a5966b4315b50","name":"FeedIn Excess","property":"payload","propertyType":"msg","rules":[{"t":"gt","v":"0","vt":"num"},{"t":"lte","v":"0","vt":"num"}],"checkall":"true","repair":false,"outputs":2,"x":212,"y":350,"wires":[["ee7b069fb5336b63"],["d41d97fe4b178d9c"]]},{"id":"ee7b069fb5336b63","type":"change","z":"b92a5966b4315b50","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"1","tot":"num"},{"t":"set","p":"topic","pt":"msg","to":"FeedIn Excess","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":400,"y":322,"wires":[["d0da421cfa23bef6"]]},{"id":"d41d97fe4b178d9c","type":"change","z":"b92a5966b4315b50","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"0","tot":"num"},{"t":"set","p":"topic","pt":"msg","to":"FeedIn Excess","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":400,"y":364,"wires":[["d0da421cfa23bef6"]]},{"id":"d0da421cfa23bef6","type":"victron-output-settings","z":"b92a5966b4315b50","service":"com.victronenergy.settings","path":"/Settings/CGwacs/OvervoltageFeedIn","serviceObj":{"service":"com.victronenergy.settings","name":"com.victronenergy.settings","paths":[{"path":"/Settings/CGwacs/AcPowerSetPoint","type":"float","name":"ESS control loop setpoint (W)","writable":true},{"path":"/Settings/CGwacs/BatteryLife/MinimumSocLimit","type":"float","name":"ESS Minimum SoC (unless grid fails) (%)","writable":true},{"path":"/Settings/CGwacs/BatteryLife/State","type":"enum","name":"ESS BatteryLife state","enum":{"0":"Unused, BL disabled","1":"Restarting","2":"Self-consumption","3":"Self-consumption","4":"Self-consumption","5":"Discharge disabled","6":"Force charge","7":"Sustain","8":"Low Soc Recharge","9":"Keep batteries charged","10":"BL Disabled","11":"BL Disabled (Low SoC)","12":"BL Disabled (Low SOC recharge)"},"writable":true},{"path":"/Settings/SystemSetup/MaxChargeCurrent","type":"float","name":"DVCC system max charge current (A DC)","writable":true},{"path":"/Settings/CGwacs/Hub4Mode","type":"enum","name":"ESS Mode","enum":{"1":"ESS with Phase Compensation","2":"ESS without phase compensation","3":"Disabled/External Control"},"writable":true},{"path":"/Settings/CGwacs/MaxChargePercentage","type":"float","name":"ESS max charge current (fractional) (%)","writable":true},{"path":"/Settings/CGwacs/MaxDischargePercentage","type":"float","name":"ESS max discharge current (fractional) (%)","writable":true},{"path":"/Settings/CGwacs/MaxDischargePower","type":"float","name":"ESS max discharge current (W)","writable":true},{"path":"/Settings/CGwacs/MaxFeedInPower","type":"float","name":"Maximum System Grid Feed In (W)","writable":true},{"path":"/Settings/CGwacs/OvervoltageFeedIn","type":"enum","name":"Feed excess DC-coupled PV into grid","enum":{"0":"Don’t feed excess DC-tied PV into grid","1":"Feed excess DC-tied PV into the grid"},"writable":true},{"path":"/Settings/CGwacs/PreventFeedback","type":"enum","name":"AC-coupled PV - grid feed in excess","enum":{"0":"Feed excess AC-tied PV into grid","1":"Don’t feed excess AC-tied PV into the grid"},"writable":true},{"path":"/Settings/SystemSetup/MaxChargeVoltage","type":"float","name":"Limit managed battery voltage (V DC)","writable":true}]},"pathObj":{"path":"/Settings/CGwacs/OvervoltageFeedIn","type":"enum","name":"Feed excess DC-coupled PV into grid","enum":{"0":"Don’t feed excess DC-tied PV into grid","1":"Feed excess DC-tied PV into the grid"},"writable":true},"initial":"","name":"","x":784,"y":322,"wires":[]},{"id":"9e2a8c3f2b008ff3","type":"ui_text","z":"b92a5966b4315b50","group":"61fb93da7947653b","order":5,"width":0,"height":0,"name":"","label":"Price/kWh this Hour","format":"{ {value | number:2}} DKK/kWh","layout":"col-center","className":"","x":1032,"y":140,"wires":[]},{"id":"61fb93da7947653b","type":"ui_group","name":"Battery","tab":"5c3457fb4e656873","order":3,"disp":true,"width":"6","collapse":false,"className":""},{"id":"5c3457fb4e656873","type":"ui_tab","name":"Home","icon":"dashboard","disabled":false,"hidden":false}]

1685300163736.png (36.8 KiB)
2 |3000

Up to 8 attachments (including images) can be used with a maximum of 190.8 MiB each and 286.6 MiB total.

kim-sandberg avatar image kim-sandberg commented ·

I'm running a setup like this to avoid using the nordpool api more than once / day.
The flow has some timezone things that you would not need as the nordpool spot prices are not in same timezone. (like fetching yesterdays prices and setting hour 23 of yesterday to firs hour of today.)



I then extend functionality in other flows:

- charging from grid if cheap price (wintertime)

- prioritize export over charging when price high during summer.

- don't export if price is negative.

1 Like 1 ·
1685602998161.png (135.8 KiB)
larsea-dk avatar image larsea-dk kim-sandberg commented ·
Could be great to only get prices once a day. I will indeed look into your code and maybe get some ideas, thanks a lot :-)

I also try to add no export when prices are below a value i.e. 0DKK and also to prioritize export instead of charge at high expected input based on solar forecast.

I do though have a bit conflict with my Nodered, that keep my charge low to avoid to much power to the battery on sunny days(tweaking setpoint). If my Nodered say max 1500w to battery and control setpoint ensure rest is feed to grid by tweaking the grid setpoint, it does work to send a Nodered command that turn off Feed in excess due to grid setpoint is taking over.

How have you done this? (Hoped I were better at Nodered)

0 Likes 0 ·
kim-sandberg avatar image kim-sandberg larsea-dk commented ·

I havent tried reducing the power to the battery with setpoint. I usually keep setpoint close to 0.

The battery just gets full and goes into float on really sunny days. then it just exports excess or throttles down production in case of negative prices.
In the picture the sun was shining all the time but there was negative prices between 14-16 so the production was throttled to match my usage.

0 Likes 0 ·
1685681220072.png (8.7 KiB)
Dirk-Jan Faber avatar image
Dirk-Jan Faber answered ·

I am quite sure that Nordpool takes the information from (as all European companies do). This gist contains a subflow for retrieving info from their API with the day ahead prices.

You need an ENTSO-e Restful API key if you want to collect the data from ENTSO-e. To request this API key, register on the Transparency Platform and send an email to transparency at with Restful API access in the subject line. Indicate the email address you entered during registration in the email body.

See the API documentation for more info.

2 |3000

Up to 8 attachments (including images) can be used with a maximum of 190.8 MiB each and 286.6 MiB total.

tobbo166 avatar image
tobbo166 answered ·

Hi! I copied some nodes from you Kim Sandberg.

I am struggling with Nodered and Javascript having a Microsoft/C#/PowerShell background.

Am I correct assuming the Change, Switch nodes aren't working as expected on the Nordpool node output? Hence the JSONata stuff etc Kim?

As I couldn't get switch and change to work as expected, by querying parameters of the payload etc, I had to revert to function programming.

This actually sends the first 15 hours of prices to the next node:

var dayOne = [];

var index = 0;

for (let i of msg.payload) {

i.timestampDate = new Date(i.timestamp).toLocaleDateString('SE') // Use SE format of time

i.timestampTime = new Date(i.timestamp).toLocaleTimeString('SE', {

hour: '2-digit'

}) // Till CET inkl sommar-/vintertid

i.price = ( (i.price / 1000) ) // Convert from MWh to kWh

if ( i.timestampTime < 15) {

dayOne[index] = ({ payload: { price: i.price, timestampDate: i.timestampDate, timestampTime: i.timestampTime } })

// funkar: node.send({ payload: { price: i.price, timestampDate: i.timestampDate, timeStampTime: i.timestampTime } })


index += 1;


return dayOne;

//return msg;

But, I would rather have the whole object, not 15 objects, not sure I can use that?

I want to build a new array with 24 positions, based on yesterday's prices (00:00-15:00) and tomorrow's prices (15:00-24:00). Once done I am going to apply your sort node, Kim, seems to work good but on object?

2 |3000

Up to 8 attachments (including images) can be used with a maximum of 190.8 MiB each and 286.6 MiB total.