Best strategy for Reading & Sorting Electricity Cost Array

Hi

I have just switched electricity suppliers to Octopus Energy who I think are maybe one of the first to have variable half hour rates in the UK depending on wholesale price. Naturally I want to make some switching decisions based on future electricity cost.

There is an example curl statement that gets the results in file attache.result.txt (11.6 KB)

This gives the pricing for the next 24 hours.

I am no programmer but I have Node-RED successfully controlling all sorts of devices mainly by copying other peoples examples and some trial and error to get the result needed.

I thought it easier to explain in words what I am trying to achieve before diving in (and sinking!)

Get the data about 22:00 each day.

I then want to parse the results and find a number of patterns.

One - the lowest cost 2 consecutive 1/2 hour periods. Switch on smart plug A via MQTT. This is to operate washing machine which has a sub 1 hour cycle.

Two - the lowest cost 6 consecutive 1/2 hour periods. Switch on smart plug B via MQTT. This is to operate dryer which has a sub 3 hour cycle.
Optionally to set a threshold price which negates the above 2 scenarios and wait for another day.

Three - When I install a battery picking the X number of lowest 1/2 periods in ascending order of cost so the battery can be recharged at lowest cost. X being the number of 1/2 hours required to re-charge.
This will be far less in Summer when I have surplus solar power.

I guess this could all be done directly in Node-RED but that strikes me as somewhat daunting and way beyond my knowledge.

I wonder if I am better off to get the data into a file, manipulate the file in python and then read the resultant file from Node-RED.

Any thoughts, guidance or suggestions much appreciated.

(Incidentally if anyone is interested in switching to Octopus PM me and I can give a link which gives you a bonus on switching. The process is all done on line and took me all of 10 minutes)

Regards

Ian

3 Likes

Do you know how to connect to the API and do the call? That is possibly the hardest part. You also need to understand how the API works. In particular, in common with many API's, the output can be paged which means that you might need to do a loop to get more than 1 page of results.

Hard to give specifics since I don't have an account.

Once you have compiled all of the data into a single object, the rest should be pretty straight-forward.

I have similar programm running. Difference is that I get day ahead price data from Nordpool market and the structure of that data is much more complex. And we don't have half-hour prices but it changes hourly.
My program calculates schedule for each device at the beginning of the day. Schedule calculation based on predefined rules. Each rule defines how many hours can device be active and some additional parameters like deal with day time hours only, or consecutive hours prohibed and so on.
So based on rule and price list, the final schedule for each device is calculated and at every hour change if device schedule tells to change the state, the output is sent to relay board.
Of course the manual mode exists for each device. And some overrules.
I also developed some visuals as it is interesting to know how the price moves and why and when some device is on or off.

So it is entirely doable in Node-RED. I can't see any use for python or something else.

The pattern three you mentioned needs UI as you want to manually pick the cheapest hours. So some visual part needs to be done anyways as I understand. I don't have manual picking option as I am lazy enough to not push any button at 3 AM. But it is doable and probably the easiest part of this kind of complex system you are going to build.

3 Likes

Really nice use case for IOT. It is not complicated at all doing in Node-RED. I put together a flow that will select the best six consecutive periods from the file. I guess doing for 2 or n periods will be the same.

The flow will generate an array with these elements:

r-01

and afterwards a function node will select the one with the lowest costs among them.

Look at this flow as a possible candidate for a quick start. Additional work is required to meet your requirements (again, nothing very complicated) and make the flow robust.

[{"id":"5e45b8d5.4a6f28","type":"tab","label":"Flow 4","disabled":false,"info":""},{"id":"2d27dda5.9122d2","type":"inject","z":"5e45b8d5.4a6f28","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":100,"y":200,"wires":[["882fdde7.d0c6"]]},{"id":"15936ace.ccd895","type":"comment","z":"5e45b8d5.4a6f28","name":"Best strategy for Reading & Sorting Electricity Cost Array","info":"https://discourse.nodered.org/t/best-strategy-for-reading-sorting-electricity-cost-array/7929\n","x":310,"y":100,"wires":[]},{"id":"882fdde7.d0c6","type":"file in","z":"5e45b8d5.4a6f28","name":"Read file","filename":"C:\\Users\\OCM\\.node-red\\static\\nrfiles\\ag.json","format":"utf8","chunk":false,"sendError":false,"x":240,"y":200,"wires":[["3dc9c1e.8610e3e"]]},{"id":"3dc9c1e.8610e3e","type":"json","z":"5e45b8d5.4a6f28","name":"","property":"payload","action":"obj","pretty":false,"x":370,"y":200,"wires":[["17c80a7.0dae2f6"]]},{"id":"7583ef9e.d5ef2","type":"split","z":"5e45b8d5.4a6f28","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":630,"y":200,"wires":[["cad8ad0d.02291"]]},{"id":"17c80a7.0dae2f6","type":"change","z":"5e45b8d5.4a6f28","name":"Results","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.results","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":500,"y":200,"wires":[["7583ef9e.d5ef2"]]},{"id":"cad8ad0d.02291","type":"link out","z":"5e45b8d5.4a6f28","name":"","links":["8e60f88c.14de78","a93d7927.117a98"],"x":755,"y":200,"wires":[]},{"id":"85cc0983.6330e8","type":"batch","z":"5e45b8d5.4a6f28","name":"Batch 6 periods","mode":"count","count":"6","overlap":"5","interval":10,"allowEmptySequence":false,"topics":[],"x":200,"y":280,"wires":[["3f62a44b.8f82fc"]]},{"id":"a93d7927.117a98","type":"link in","z":"5e45b8d5.4a6f28","name":"","links":["cad8ad0d.02291"],"x":75,"y":280,"wires":[["85cc0983.6330e8"]]},{"id":"3f62a44b.8f82fc","type":"join","z":"5e45b8d5.4a6f28","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":370,"y":280,"wires":[["466dbf51.6f863"]]},{"id":"466dbf51.6f863","type":"function","z":"5e45b8d5.4a6f28","name":"Cost for batch","func":"function addValue(arr) {\n    let add = 0;\n    for (let ele of arr) {\n        add = add + ele.value_inc_vat;\n    }\n    return add;\n}\n\nmsg.value_batch = addValue(msg.payload);\n\nreturn msg;","outputs":1,"noerr":0,"x":540,"y":280,"wires":[["41755ece.0fe3a"]],"info":"Calculate the energy cost for the period"},{"id":"c14bf7d5.3bf658","type":"debug","z":"5e45b8d5.4a6f28","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"outcome","targetType":"msg","x":720,"y":340,"wires":[]},{"id":"41755ece.0fe3a","type":"change","z":"5e45b8d5.4a6f28","name":"Transform","rules":[{"t":"set","p":"topic","pt":"msg","to":"payload[5].valid_from","tot":"msg"},{"t":"set","p":"pay.start","pt":"msg","to":"payload[5].valid_from","tot":"msg"},{"t":"set","p":"pay.time_end","pt":"msg","to":"payload[0].valid_to","tot":"msg"},{"t":"move","p":"value_batch","pt":"msg","to":"pay.value","tot":"msg"},{"t":"delete","p":"filename","pt":"msg"},{"t":"delete","p":"_event","pt":"msg"},{"t":"set","p":"payload","pt":"msg","to":"pay.value","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":220,"y":340,"wires":[["15a53398.37b40c"]]},{"id":"15a53398.37b40c","type":"join","z":"5e45b8d5.4a6f28","name":"Create Array","mode":"custom","build":"array","property":"pay","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"2","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":370,"y":340,"wires":[["92b06136.75d29","bdc73762.b85d08"]]},{"id":"92b06136.75d29","type":"function","z":"5e45b8d5.4a6f28","name":"Select lowest","func":"msg.outcome = msg.pay.reduce((prev, current) => (prev.value < current.value) ? prev : current)\nreturn msg;","outputs":1,"noerr":0,"x":550,"y":340,"wires":[["c14bf7d5.3bf658"]]},{"id":"bdc73762.b85d08","type":"debug","z":"5e45b8d5.4a6f28","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":520,"y":420,"wires":[]}]

Another way to reduce the electricity bill:
I run my deep freezers if power is cheap (pv modules) and cool them down to -25°.

To achieve a longer off time, i use small bottles filled with salt water. The system should be failsafe like: node-red only turns the freezer off for a certain time - if no signal arrives, freezer go's on again.

and here is a flow that can be improved to accomplish task #3. You have to hard code the X number in the function node, or bring this value from the dashboard, if you develop one.

[{"id":"2d488151.3842ae","type":"tab","label":"Electricity Cost Array - v3","disabled":false,"info":""},{"id":"a9ee4982.8ee248","type":"inject","z":"2d488151.3842ae","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":100,"y":200,"wires":[["161e8b61.a77575"]]},{"id":"fae5bc8.e63304","type":"comment","z":"2d488151.3842ae","name":"Best strategy for Reading & Sorting Electricity Cost Array","info":"https://discourse.nodered.org/t/best-strategy-for-reading-sorting-electricity-cost-array/7929\n","x":310,"y":100,"wires":[]},{"id":"161e8b61.a77575","type":"file in","z":"2d488151.3842ae","name":"Read file","filename":"C:\\Users\\OCM\\.node-red\\static\\nrfiles\\ag.json","format":"utf8","chunk":false,"sendError":false,"x":240,"y":200,"wires":[["9d226909.884878"]]},{"id":"9d226909.884878","type":"json","z":"2d488151.3842ae","name":"","property":"payload","action":"obj","pretty":false,"x":370,"y":200,"wires":[["59f0573c.4d0ac8"]]},{"id":"590121a5.220bc","type":"split","z":"2d488151.3842ae","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":630,"y":200,"wires":[["da7fbbb5.431ac8"]]},{"id":"59f0573c.4d0ac8","type":"change","z":"2d488151.3842ae","name":"Results","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.results","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":500,"y":200,"wires":[["590121a5.220bc"]]},{"id":"da7fbbb5.431ac8","type":"link out","z":"2d488151.3842ae","name":"","links":["8e60f88c.14de78","a0256cd3.bce79"],"x":755,"y":200,"wires":[]},{"id":"13f43521.e33c7b","type":"batch","z":"2d488151.3842ae","name":"Batch 1 period","mode":"count","count":"1","overlap":"0","interval":10,"allowEmptySequence":false,"topics":[],"x":200,"y":280,"wires":[["6f47ffb9.beb71"]]},{"id":"a0256cd3.bce79","type":"link in","z":"2d488151.3842ae","name":"","links":["da7fbbb5.431ac8"],"x":75,"y":280,"wires":[["13f43521.e33c7b"]]},{"id":"6f47ffb9.beb71","type":"join","z":"2d488151.3842ae","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":370,"y":280,"wires":[["c928820c.cbc51"]]},{"id":"c928820c.cbc51","type":"function","z":"2d488151.3842ae","name":"Cost for batch","func":"function addValue(arr) {\n    let add = 0;\n    for (let ele of arr) {\n        add = add + ele.value_inc_vat;\n    }\n    return add;\n}\n\nmsg.value_batch = addValue(msg.payload);\n\nreturn msg;","outputs":1,"noerr":0,"x":540,"y":280,"wires":[["ea7518a4.de4508","fa30591c.6ca008"]],"info":"Calculate the energy cost for the period"},{"id":"71399baf.3c3094","type":"debug","z":"2d488151.3842ae","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":710,"y":340,"wires":[]},{"id":"ea7518a4.de4508","type":"change","z":"2d488151.3842ae","name":"Transform","rules":[{"t":"set","p":"topic","pt":"msg","to":"payload[0].valid_from","tot":"msg"},{"t":"set","p":"pay.start","pt":"msg","to":"payload[0].valid_from","tot":"msg"},{"t":"set","p":"pay.time_end","pt":"msg","to":"payload[0].valid_to","tot":"msg"},{"t":"move","p":"value_batch","pt":"msg","to":"pay.value","tot":"msg"},{"t":"delete","p":"filename","pt":"msg"},{"t":"delete","p":"_event","pt":"msg"},{"t":"set","p":"payload","pt":"msg","to":"pay.value","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":220,"y":340,"wires":[["8fde6029.957f1"]]},{"id":"8fde6029.957f1","type":"join","z":"2d488151.3842ae","name":"Create Array","mode":"custom","build":"array","property":"pay","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"2","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":370,"y":340,"wires":[["fbdd6b3c.f06518"]]},{"id":"fbdd6b3c.f06518","type":"function","z":"2d488151.3842ae","name":"const cycle = 6","func":"const cycle = 6;\n\nmsg.outcome = msg.pay.sort((a,b) => a.value - b.value);\n\nfor (let c = 0; c < cycle ; c++) {\n    node.send(msg.outcome[c]);\n}\n","outputs":1,"noerr":0,"x":560,"y":340,"wires":[["71399baf.3c3094"]]},{"id":"fa30591c.6ca008","type":"debug","z":"2d488151.3842ae","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":730,"y":280,"wires":[]}]

Well I am speechless!

Realising I did not understand how to connect to the API and do the call I suddenly thought can I put the curl command in a script and run with exec. Yes I can!

I then imported the flows that Andrei produced, changed the name to match and hit inject.

Magic! It all works. It took all in about 20 minutes to implement. Power To Node-RED...........

All I have to do is set up the timing, get the results to send appropriate mqtt commands, add some error checking where necessary and job largely done. And of course try to understand how the flows work.

One question is what am I going to do for the next month waiting for the smart meter install and tariff change. I thought I would be really struggling with this for days!

Many many thanks to all particularly to Andrei for the flow.

Regards

Ian

Sure you can, but do you really need to?

[{"id":"a58b0619.a19f08","type":"http request","z":"ba86d109.1f011","name":"","method":"GET","ret":"obj","url":"https://api.octopus.energy/v1/products/AGILE-18-02-21/electricity-tariffs/E-1R-AGILE-18-02-21-L/standard-unit-rates/","tls":"","x":380,"y":1250,"wires":[["5d69e1a8.0b8f9"]]},{"id":"15046c14.fbf814","type":"inject","z":"ba86d109.1f011","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":220,"y":1250,"wires":[["a58b0619.a19f08"]]},{"id":"5d69e1a8.0b8f9","type":"debug","z":"ba86d109.1f011","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":550,"y":1250,"wires":[]}]
1 Like

Indeed, this use case shows the power of Node-RED.

Once you add the http request node like said @hotNipi you will see that the flow still runs smoothly.

Probably it is possible some optimization in the flow but it is too early to think about that. Before you should imagine a strategy to trigger the MQTT message to control the devices.

I think it would be smart to delegate the task to a programmable timer/scheduler, instead of trying to code the timer in JavaScript. I wish we could find a scheduler node that is programmable (via msgs), persist after reset of the system, and can be programmed with a timestamp formatted string. Something like start : "2019-02-15T13:00:00Z and then stop: "2019-02-15T14:00:00Z. Looks like node-red-contrib-schedex comes close.

This is part of what I've been thinking about and starting to design into a new node.

The problem is that the scope of the requirements is much larger than that simple example and we will need the ability to manage multiple schedules along with overrides.

I've a bunch of thoughts and ideas that are gradually coming together but happy to consider requirements - but best to start a different thread for that I think.

2 Likes

Hi Julian, let's go for it. Node-RED deserves a better contrib node scheduler. I could give you a hand with coding and testing. See you in the upcoming new thread :wink:

Thanks.I need to add the API key to the http request and I cannot figure out how to do that. In the curl I put:-

curl **-u "nnnnnnnnnneayhhNn5RORJG:"** https://api.octopus.energy/v1/products/AGILE-18-02-21/electricity-tariffs/E-1R-AGILE-18-02-21-L/standard-unit-rates/

Where do you put that in the http request?

Like shown below

and this is where you enter the http request

r-07

Thanks

Sorry, I did not make myself clear. The flow worked but I got "Invalid API key."

When I issue Curl the format is:-
curl -u "nnnnnnnnnfEN2eayhhNn5RORJG:"
https://api.octopus.energy.........................

"nnnnnnnnnfEN2eayhhNn5RORJG:" is the API key which I guess I need to include in the GET somehow but I don't know where. I have tried a number of different things but none worked.

Assuming the server is using basic authentication. Check the option "Use basic authentication) and enter username and password. I never used but I assume the node will encode the pair as base64 and will build the required header. Test and let us know the result.

I like the idea of using schedex which I use on other applications. At the moment I run manual injected flows to restore schedex schedules after any deployments or restarts. I could do something similar in this application for the time being until a persistent scheduler is available.

Thinking about the decision flow I have realised I need to keep previous days data as well.

The tariff rates for the following day are set sometime after 16:00 every day. I have learnt that I will always avoid using electricity 16:00 to 19:00 every day as there is a 12p surcharge for these peak hours. That is applied by the UK grid network and pays for the grid. Therefore having got next days data at 17:30 the period I need to check is from 19:00 current day until 16:00 the following day. I am thinking of just merging the 2 days data and running the flow on the resultant file.

This is really coming together. Thanks for all the input. I have also realised the battery schedule is more complex than I first thought but I will leave that for another day.

What I do is every time I get new data, I run calculations to create additional total price for all data with all the taxes included. As grid network fee is not constant it makes significant difference. Different prices for night hours and for weekends makes cost saving a little bit challenge to do in head, but computer can and little bit of math helps out here :slightly_smiling_face:

I have the above two flows working and have started linking into Homeseer using MQTT to control EV charger and appliances based on lowest electricity price.

A couple of questions on best way to extend the above flows- particularly the flow in post 6:-

  1. I can work out how many half hour slots I need car charger on from from inputs I can get via MQTT- ie ((RequiredCharge% - CurrentCharge%) * BatteryCapacitykWh) / ChargerkW-Rating = HalfHourSlotsRequired”.
    What’s the cleanest way to then use this “HalfHourSlotsrequired” instead of the fixed constant in the function block for required number of lowest cost half hour charging times out as individual MQTT messages to store in Homeseer?
    Andrei mentioned bringing the number (constant c) in from dashboard in post 6 but unsure how to do this.
    I wondered if I need a context based flow for each case or if there’s another way?

  2. Is there an easy way to split the output price array into “LowestCost”, “SecondLowestCost” etc (16 lowest cost required) MQTT messages with the start time as the payload? I thought I may take an output for each array item out from the function block, change the topic to “LowestCost-x’ and move the value to payload- but unsure if there a cleaner way.

Thanks!

Chris

Hello @chrisgla. Welcome to the forum.

Both changes can be easily done.

#1 There is no need to use context (although it would work nicely). The easiest way to generate a reply with a variable number of slots is to inject the amount of slots required as a property of the original message. Later on we grab that property to filter the sorted array.

#2 It can be done by changing the code in the last function node, as you mentioned.

const cycle = msg.slots;

msg.outcome = msg.pay.sort((a,b) => a.value - b.value);

for (let c = 0; c < cycle ; c++) {
    node.send({"topic" : "Lowest-"+c, "payload" : msg.outcome[c].value});
}

Testing flow with both changes.

[{"id":"de3c1d87.4e098","type":"tab","label":"Electricity Cost Array - v3","disabled":false,"info":""},{"id":"106d5797.186888","type":"inject","z":"de3c1d87.4e098","name":"","topic":"","payload":"8","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":100,"wires":[["3b4e4499.34f60c"]]},{"id":"ee6bbaea.6ed528","type":"file in","z":"de3c1d87.4e098","name":"Read file","filename":"C:\\Users\\OCM\\.node-red\\static\\nrfiles\\ag.json","format":"utf8","chunk":false,"sendError":false,"x":240,"y":200,"wires":[["8724cdb.5b15f3"]]},{"id":"8724cdb.5b15f3","type":"json","z":"de3c1d87.4e098","name":"","property":"payload","action":"obj","pretty":false,"x":370,"y":200,"wires":[["d8d8bd26.d10e","ab7c8179.baf75"]]},{"id":"43fe274c.8b46b8","type":"split","z":"de3c1d87.4e098","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":630,"y":200,"wires":[["2045e377.a6238c"]]},{"id":"d8d8bd26.d10e","type":"change","z":"de3c1d87.4e098","name":"Results","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.results","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":500,"y":200,"wires":[["43fe274c.8b46b8"]]},{"id":"2045e377.a6238c","type":"link out","z":"de3c1d87.4e098","name":"","links":["8e60f88c.14de78","72387549.b70a1c"],"x":755,"y":200,"wires":[]},{"id":"f6e95c1c.f212b","type":"batch","z":"de3c1d87.4e098","name":"Batch 1 period","mode":"count","count":"1","overlap":"0","interval":10,"allowEmptySequence":false,"topics":[],"x":200,"y":280,"wires":[["de90f2b4.a4a23"]]},{"id":"72387549.b70a1c","type":"link in","z":"de3c1d87.4e098","name":"","links":["2045e377.a6238c"],"x":75,"y":280,"wires":[["f6e95c1c.f212b"]]},{"id":"de90f2b4.a4a23","type":"join","z":"de3c1d87.4e098","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":370,"y":280,"wires":[["ead4908d.0dad9"]]},{"id":"ead4908d.0dad9","type":"function","z":"de3c1d87.4e098","name":"Cost for batch","func":"function addValue(arr) {\n    let add = 0;\n    for (let ele of arr) {\n        add = add + ele.value_inc_vat;\n    }\n    return add;\n}\n\nmsg.value_batch = addValue(msg.payload);\n\nreturn msg;","outputs":1,"noerr":0,"x":540,"y":280,"wires":[["58ea53d.11f71ac","ad19f708.be4be8"]],"info":"Calculate the energy cost for the period"},{"id":"7151ba8a.ba3394","type":"debug","z":"de3c1d87.4e098","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":710,"y":340,"wires":[]},{"id":"58ea53d.11f71ac","type":"change","z":"de3c1d87.4e098","name":"Transform","rules":[{"t":"set","p":"topic","pt":"msg","to":"payload[0].valid_from","tot":"msg"},{"t":"set","p":"pay.start","pt":"msg","to":"payload[0].valid_from","tot":"msg"},{"t":"set","p":"pay.time_end","pt":"msg","to":"payload[0].valid_to","tot":"msg"},{"t":"move","p":"value_batch","pt":"msg","to":"pay.value","tot":"msg"},{"t":"delete","p":"filename","pt":"msg"},{"t":"delete","p":"_event","pt":"msg"},{"t":"set","p":"payload","pt":"msg","to":"pay.value","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":220,"y":340,"wires":[["f5ad4702.ef1868"]]},{"id":"f5ad4702.ef1868","type":"join","z":"de3c1d87.4e098","name":"Create Array","mode":"custom","build":"array","property":"pay","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"2","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":370,"y":340,"wires":[["dc811659.689778"]]},{"id":"dc811659.689778","type":"function","z":"de3c1d87.4e098","name":"Best slots","func":"const cycle = msg.slots;\n\nmsg.outcome = msg.pay.sort((a,b) => a.value - b.value);\n\nfor (let c = 0; c < cycle ; c++) {\n    node.send({\"topic\" : \"Lowest-\"+c, \"payload\" : msg.outcome[c].value});\n}\n","outputs":1,"noerr":0,"x":540,"y":340,"wires":[["7151ba8a.ba3394"]]},{"id":"ad19f708.be4be8","type":"debug","z":"de3c1d87.4e098","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":730,"y":280,"wires":[]},{"id":"ab7c8179.baf75","type":"debug","z":"de3c1d87.4e098","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":500,"y":160,"wires":[]},{"id":"3b4e4499.34f60c","type":"change","z":"de3c1d87.4e098","name":"","rules":[{"t":"set","p":"slots","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":130,"y":160,"wires":[["ee6bbaea.6ed528"]]}]

PS: when you want to draw attention of someone in this forum you can use the @ character in front of the user name (like I did in the beginning of this reply). This way there is no risk that you message goes unnoticed.

2 Likes

Thank you @Andrei.

The first change worked well, the second I never thought through fully and would be very messy to interface with (Homeseer) .

It seems a better way would be for Node Red to send a "Start Charging" message at the times in the array and start a 30 minute timer, then send a "Stop Charging" message providing another "Start Charging" message in the array doesn't reset the timer for another 30 minutes (important to avoid sending stop charge and start charge in quick succession)

I've started to do this by saving the array to a file once a day, then reading it back every 30 minutes and trying to compare the array for any date matching now- but I am struggling.

I've explored Schedex, Bigtimer, moment, cron nodes but none seem quite right.

I think the "check now" function I have at the bottom of the diagram might work but struggling with the logic and "new" Javascript.

Does this seem the correct approach- or do you have another suggestion?

[{"id":"e7268cec.10fc4","type":"tab","label":"Electricity Cost Array - v3","disabled":false,"info":""},{"id":"b81bb405.408628","type":"inject","z":"e7268cec.10fc4","name":"","topic":"","payload":"2","payloadType":"num","repeat":"","crontab":"00 18 * * *","once":false,"onceDelay":0.1,"x":110,"y":80,"wires":[["a526f220.b0c59","5d2e495a.fe4458"]]},{"id":"396b680e.65db08","type":"json","z":"e7268cec.10fc4","name":"","property":"payload","action":"obj","pretty":false,"x":370,"y":200,"wires":[["6ad2403c.7f126","6b0b826.35dcd7c"]]},{"id":"4e4de1e1.e51eb","type":"split","z":"e7268cec.10fc4","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":630,"y":200,"wires":[["1b4ddc80.899204"]]},{"id":"6ad2403c.7f126","type":"change","z":"e7268cec.10fc4","name":"Results","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.results","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":500,"y":200,"wires":[["4e4de1e1.e51eb"]]},{"id":"1b4ddc80.899204","type":"link out","z":"e7268cec.10fc4","name":"","links":["8e60f88c.14de78","1dab1119.b71d8f"],"x":755,"y":200,"wires":[]},{"id":"8a8ce1f6.9bfe1","type":"batch","z":"e7268cec.10fc4","name":"Batch 1 period","mode":"count","count":"1","overlap":"0","interval":10,"allowEmptySequence":false,"topics":[],"x":200,"y":280,"wires":[["e73de6e0.4d7d48"]]},{"id":"1dab1119.b71d8f","type":"link in","z":"e7268cec.10fc4","name":"","links":["1b4ddc80.899204"],"x":75,"y":280,"wires":[["8a8ce1f6.9bfe1"]]},{"id":"e73de6e0.4d7d48","type":"join","z":"e7268cec.10fc4","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":370,"y":280,"wires":[["78766d06.4958a4"]]},{"id":"78766d06.4958a4","type":"function","z":"e7268cec.10fc4","name":"Cost for batch","func":"function addValue(arr) {\n    let add = 0;\n    for (let ele of arr) {\n        add = add + ele.value_inc_vat;\n    }\n    return add;\n}\n\nmsg.value_batch = addValue(msg.payload);\n\nreturn msg;","outputs":1,"noerr":0,"x":540,"y":280,"wires":[["da76b813.73bab8","b3bce6c2.3cc1b8"]]},{"id":"aba7a46a.2d18b8","type":"debug","z":"e7268cec.10fc4","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":910,"y":340,"wires":[]},{"id":"da76b813.73bab8","type":"change","z":"e7268cec.10fc4","name":"Transform","rules":[{"t":"set","p":"topic","pt":"msg","to":"payload[0].valid_from","tot":"msg"},{"t":"set","p":"pay.start","pt":"msg","to":"payload[0].valid_from","tot":"msg"},{"t":"set","p":"pay.time_end","pt":"msg","to":"payload[0].valid_to","tot":"msg"},{"t":"move","p":"value_batch","pt":"msg","to":"pay.value","tot":"msg"},{"t":"delete","p":"filename","pt":"msg"},{"t":"delete","p":"_event","pt":"msg"},{"t":"set","p":"payload","pt":"msg","to":"pay.value","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":200,"y":340,"wires":[["74d6a20a.9a36ac"]]},{"id":"74d6a20a.9a36ac","type":"join","z":"e7268cec.10fc4","name":"Create Array","mode":"custom","build":"array","property":"pay","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"2","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":370,"y":340,"wires":[["a7921212.bd2c7"]]},{"id":"a7921212.bd2c7","type":"function","z":"e7268cec.10fc4","name":"Best slots","func":"const cycle = msg.slots;\n\nmsg.outcome = msg.pay.sort((a,b) => a.value - b.value);\n\nfor (let c = 0; c < cycle ; c++) {\n    node.send({\"topic\" : \"Lowest-\"+c, \"payload\" : msg.outcome[c].start});\n}\n","outputs":1,"noerr":0,"x":520,"y":340,"wires":[["aba7a46a.2d18b8","82677115.d7315"]]},{"id":"b3bce6c2.3cc1b8","type":"debug","z":"e7268cec.10fc4","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":730,"y":280,"wires":[]},{"id":"6b0b826.35dcd7c","type":"debug","z":"e7268cec.10fc4","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":500,"y":160,"wires":[]},{"id":"a526f220.b0c59","type":"change","z":"e7268cec.10fc4","name":"","rules":[{"t":"set","p":"slots","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":290,"y":100,"wires":[["37e15932.5cc506"]]},{"id":"37e15932.5cc506","type":"http request","z":"e7268cec.10fc4","name":"Get Agile Rates","method":"GET","ret":"obj","url":"https://api.octopus.energy/v1/products/AGILE-18-02-21/electricity-tariffs/E-1R-AGILE-18-02-21-N/standard-unit-rates/","tls":"","x":200,"y":200,"wires":[["396b680e.65db08"]]},{"id":"82677115.d7315","type":"file","z":"e7268cec.10fc4","name":"","filename":"ChargerStart.csv","appendNewline":true,"createDir":true,"overwriteFile":"false","x":870,"y":420,"wires":[]},{"id":"1209ae05.ad1fc2","type":"file in","z":"e7268cec.10fc4","name":"ChargerStart.csv","filename":"ChargerStart.csv","format":"lines","chunk":false,"sendError":false,"x":330,"y":480,"wires":[["c14d8b20.190f48"]]},{"id":"c14d8b20.190f48","type":"debug","z":"e7268cec.10fc4","name":"FileInMon","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":540,"y":560,"wires":[]},{"id":"821a15eb.071898","type":"inject","z":"e7268cec.10fc4","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"*/30 0-23 * * *","once":false,"onceDelay":0.1,"x":150,"y":480,"wires":[["1209ae05.ad1fc2"]]},{"id":"5d2e495a.fe4458","type":"file","z":"e7268cec.10fc4","name":"","filename":"ChargerStart.csv","appendNewline":true,"createDir":true,"overwriteFile":"delete","x":330,"y":60,"wires":[]},{"id":"a27db379.4db1d","type":"debug","z":"e7268cec.10fc4","name":"OPMon","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":710,"y":480,"wires":[]},{"id":"b53fdc8a.71ca8","type":"function","z":"e7268cec.10fc4","name":"Check Now","func":"var timeOn = flow.get(\"timeOn\")\nflow.set('timeOn',msg.payload);\n\nvar now = new Date();\nvar starTime = flow.get(\"startTime\", now);\nmsg.topic = (flow.get(\"startTime\") - 'timeOn');\nnode.warn(\"timeOn is \" + timeOn)\nnode.warn(\"startTime is \" + starTime)\nreturn msg;","outputs":1,"noerr":0,"x":537,"y":480,"wires":[[]]}]