How to approach this to handle hysterisis and prices


Our power rates are calculated based on wholesale rates which are updated every minute - i pull these rates down through an API and have then stored them (and use them throughout my home automation) for various roles.

The catch with this is that there is a "proposed/estimated) price at the start of each 1/2 hour period, and then as the 1/2 hour evolves this price is updated.

I want to run a large heatpump system that draws approx 5KW at max load and do not want to continually turn this on an off (as it is not good for the system)

My threshold where i am prepared to go cold as opposed to runnning up a huge powerbill is 25c/kw

So currently i scan at the start of a 1/2 hour period for the proposed price and if it is less than 25c then i will start up the heat pump - now during this 1/2 hour period the price is updated every minute and potentially can jump around within a couple of cents - if i am on the edge use case of the price oscillating every minute between 25c and 26c i do not wish to continually turn the system on and off - however we do experience large price spikes during which the price could jump from 25c to say $1.50 - which is when i would want to turn it off and leave it off for that 1/2 hour until the market sorted itself out.

So the main thing i am trying to solve is the edge use case of the price oscillating up and down around the price i am prepared to accept

Any ideas how i can attack this one whilst looking after my heatpump and my wallet ?


Hey Craig.

To me there are a couple of ways to do it.

I'll explain one:

Ok, you say you want the cost peak (per KWH) to be 25c. But 26c is ok. And $1.50 is not.
Alas you need another number in the equation.
Say 28c.

When the new time frame starts it checks the price against the 25c (preferred max) and if below it starts the A/C. Heat pump.
Then during the half hour if it goes above the 28c (new value) it cuts off.

Otherwise it just keeps on going.

The 28c could be a fixed value or you could make it something like:
Preferred max + 10% giving you a 10% increase curve.

Just a suggestion.

OK thats not a bad way of handling it - i could put these into trigger node and have it do the watchdog for me

Might try and put something together and see where that leads me

Thanks Andrew


1 Like

No problems.

Glad I could nudge you in the right direction.
(I think I've got the flu and my head is not very clear.)

Just trying to play around with combinations of switch, RBE and Trigger nodes to see if i can get it done.

May be easier in the end to code it into a function i am thinking


Just to maybe help.

This is how I would attack the question:

[{"id":"560fe789e6b0be00","type":"inject","z":"d188b95f33e5f7e4","name":"Main loop","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":3090,"y":2190,"wires":[["26a33d410f93272e"]]},{"id":"26a33d410f93272e","type":"function","z":"d188b95f33e5f7e4","name":"Get cost now  (24)","func":"let payload = 24;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":3380,"y":2190,"wires":[["274e9fa836c68313"]]},{"id":"274e9fa836c68313","type":"switch","z":"d188b95f33e5f7e4","name":"< flow.Max?","property":"payload","propertyType":"msg","rules":[{"t":"lt","v":"max","vt":"flow"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":3590,"y":2190,"wires":[["540f39dae242d597"],["dc9c3240aef09629"]]},{"id":"f62079db69bb13a0","type":"inject","z":"d188b95f33e5f7e4","name":"Setup","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":3130,"y":2110,"wires":[["6986c3abe5835262"]]},{"id":"6986c3abe5835262","type":"change","z":"d188b95f33e5f7e4","name":"Variables","rules":[{"t":"set","p":"max","pt":"flow","to":"25","tot":"num"},{"t":"set","p":"upper","pt":"flow","to":"28","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":3310,"y":2110,"wires":[[]]},{"id":"540f39dae242d597","type":"function","z":"d188b95f33e5f7e4","name":"Turn on A/C","func":"\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":3770,"y":2160,"wires":[[]]},{"id":"a9b623bf366d61ad","type":"function","z":"d188b95f33e5f7e4","name":"Get cost now  (30)","func":"let payload = 30;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":3380,"y":2260,"wires":[["513c99275003ffa1"]]},{"id":"513c99275003ffa1","type":"switch","z":"d188b95f33e5f7e4","name":"> flow.upper?","property":"payload","propertyType":"msg","rules":[{"t":"gt","v":"upper","vt":"flow"}],"checkall":"true","repair":false,"outputs":1,"x":3600,"y":2260,"wires":[["dc9c3240aef09629"]]},{"id":"dc9c3240aef09629","type":"function","z":"d188b95f33e5f7e4","name":"Turn off A/C","func":"\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":3770,"y":2210,"wires":[[]]},{"id":"6e02623843e54690","type":"inject","z":"d188b95f33e5f7e4","name":"Ongoing checks every minute","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":3150,"y":2260,"wires":[["a9b623bf366d61ad"]]}]

This is an EXAMPLE only and only for showing the concept of checking.

Good one thanks - Peak hour now - so will leave well enough alone and back onto this after 8PM this evening

Thanks for putting this together Andrew - will check it out and let you know


1 Like

Have a look at node-red-contrib-hysteresis (node) - Node-RED

Thanks Colin

I looked at that earlier but had the issue that i can not work out how to reset the deadband once it kicks in

Say i have my number of interest at 25c

I set an upper threshold in the node as 28c

i set a lower threshold (dont really need this) as 21c

So the first value comes in say 25c and my system turns on the pump on

Then a value of 21 comes in and it send a mesage (meaningless in this context)

Now a 28 comes in and the high threshold message gets sent (turn off the pump)

But until a new value below the bottom threshold - the deadband never resets so if my next value is 25 - the pump will not turn back on - and i can not work out what to send to the node to reset its deadband

Am i not using it correctly ?


Thankds Andrew - just had a look - had to assign the payload to msg.payload but otherwise it seems to work - not sure it will handle not turning the pump on an off too often though

I am just about to put it on my test system with some random numbers fed in to see what happens.


Yeah, sorry for that mistake with the payload. :wink:

Sorry, you lost me.

Each 30 minutes the main loop starts things happening.
If the value given is less than 25 the A/C is turned on.

Then every minute there after the lower inject node queries the value.
If it goes above the upper value the A/C is turned off.

The A/C can't be turned on again until the next 30 minute pulse occurs.

This is a slightly modified flow.
Again only to show the workings. Not the actual code.

[{"id":"560fe789e6b0be00","type":"inject","z":"d188b95f33e5f7e4","name":"Main loop","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":3090,"y":2190,"wires":[["26a33d410f93272e"]]},{"id":"26a33d410f93272e","type":"function","z":"d188b95f33e5f7e4","name":"Get cost now  (24)","func":"msg.payload = 24;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":3380,"y":2190,"wires":[["274e9fa836c68313"]]},{"id":"274e9fa836c68313","type":"switch","z":"d188b95f33e5f7e4","name":"< flow.Max?","property":"payload","propertyType":"msg","rules":[{"t":"lt","v":"max","vt":"flow"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":3590,"y":2190,"wires":[["540f39dae242d597","4c0856898ceee7f3"],["dc9c3240aef09629"]]},{"id":"f62079db69bb13a0","type":"inject","z":"d188b95f33e5f7e4","name":"Setup","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":3130,"y":2110,"wires":[["6986c3abe5835262"]]},{"id":"6986c3abe5835262","type":"change","z":"d188b95f33e5f7e4","name":"Variables","rules":[{"t":"set","p":"max","pt":"flow","to":"25","tot":"num"},{"t":"set","p":"upper","pt":"flow","to":"28","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":3310,"y":2110,"wires":[[]]},{"id":"540f39dae242d597","type":"function","z":"d188b95f33e5f7e4","name":"Turn on A/C","func":"\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":3770,"y":2160,"wires":[[]]},{"id":"a9b623bf366d61ad","type":"function","z":"d188b95f33e5f7e4","name":"Get cost now  (30)","func":"msg.payload = 30;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":3380,"y":2260,"wires":[["513c99275003ffa1"]]},{"id":"513c99275003ffa1","type":"switch","z":"d188b95f33e5f7e4","name":"> flow.upper?","property":"payload","propertyType":"msg","rules":[{"t":"gt","v":"upper","vt":"flow"}],"checkall":"true","repair":false,"outputs":1,"x":3600,"y":2260,"wires":[["dc9c3240aef09629","b91bc170c21d504a"]]},{"id":"dc9c3240aef09629","type":"function","z":"d188b95f33e5f7e4","name":"Turn off A/C","func":"\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":3770,"y":2210,"wires":[[]]},{"id":"6e02623843e54690","type":"inject","z":"d188b95f33e5f7e4","d":true,"name":"Ongoing checks every minute","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":3150,"y":2260,"wires":[["a9b623bf366d61ad"]]},{"id":"b91bc170c21d504a","type":"function","z":"d188b95f33e5f7e4","name":"Stop the minute by minute scan for prices.","func":"\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":3860,"y":2280,"wires":[["4f472e409dc91bb1"]]},{"id":"4c0856898ceee7f3","type":"function","z":"d188b95f33e5f7e4","name":"Start minute by minute scan","func":"\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":3820,"y":2120,"wires":[["4f472e409dc91bb1"]]},{"id":"4f472e409dc91bb1","type":"function","z":"d188b95f33e5f7e4","name":"Minute by minute price scan timer","func":"\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":3130,"y":2310,"wires":[["a9b623bf366d61ad"]]}]

If you want it turn back on if it gets down to 25 then set the lower threshold to that.(or just above).

Aah i get it now - your main triggers do not have timers set so did not realise the loop frequency you were using.

That makes more sense - will play now


Sorry it wasn't clearer.

But glad you get it now.

Problem I see:
Your main loop is 30 minutes.
At the start if the price is too high, it will wait until the next 30 minute block.
If the price is low enough, it turns on the A/C and then monitors the price every minute.

If that price gets above the upper limit, it turns off the A/C and stops the minute scanning.

But where's the demarcation if (say) at 28 minutes into the time the price goes up and the A/C is stopped.
Then in 2 minutes it may start again.

So when it is stopped because of a high price and stops the A/C: Does it wait for 30 minutes to then start again, or is it form the previous start time?

Sorry, but I see that as a possible point of contention if every 30-ish minutes there is a price spike that stops the A/C.

Maybe complicating it, but.....

You may need to see ,..... 2 (?) consecutive high prices before turning the A/C off?
But the original problem remains.

Yep - i ran it last niight with just the blunt force approach you described above - which means it may be off for 1/2 hour - but it does the scan every minute to see where the price currently is and as there were no edge cases last night it went OK.

Will have to monitor it closely as it looks like we may have some edge cases during the day tomorrow

thanks for the help and will report back

I have also put in place a parallel system with the Hysteresis node Colin mentioned - but have onyl got it operating in testing and logging mode and will see how it handles it as well


I have something similar in a commercial walk-in freezer where I send a txt message if it appears to be malfunctioning. I can't depend on the instantaneous temperature reading because the temperature fluctuates a few times a day when the defrost cycle kicks on, and when the employees leave the door open for a lengthy amount of time loading or unloading the cooler. I don't have access to the code at the moment, but I remember it uses a "Smooth" node to average the most recent 5 temperature readings, which is equivalent to 25 minutes because the temperature is read every 5 minutes. So if the cooler, (which is normally below 0F) reaches a 25 minute average of 25 degrees, then it's definitely time to check the door or compressor. There's other nodes involved, such as checking the threshold and another node watches to see when the temperature has re-entered the safe zone and sends a message that all is good again.
In your case you may want to see if the preceding 2/3/4/5 minute average has exceeded your threshold, and adjust the triggers accordingly. Pretty sure the 25 minute average is fed to 2 Hysteresis nodes as mentioned above - one to trigger the high temp message when the temp is rising, and one to trigger the "all clear" message when the temp re-enters the safe zone.

1 Like

Thanks for this feedback

This describes essentially where i have landed now and am testing (and looks good so far)

As described by Colin above i set the low level for the Hysteresis Node to be my "wanted" price (i.e. the price i do not want to go above and the High threshold is the price where i will turn off any devices

So in my case for the heat pump i set the high value at 28 (cents) and the low value at 25 (cents) - it appears to be working although i do need to analyse the logs closely this weekend

The outcome does appear to be what i wanted to achieve in that the heat pump is not running when prices are too high and is not cycling too much - i an yet to see the sustained hysteresis case i was concerned with in the prices but should get them this weekend

Essentially the Power company is doing the averaging function of the smooth node for me by striking the price every 5 minutes - i then feed this into a switch node - if the price is below 25 it goes straight through and then checks do we need heat etc - if it is above 30c then it goes straight through and turns the pump off - if it is in the middle of those two values is when i feed it through to the hysteresis node and let it do its thing


What have you got the threshold set to in the hysteresis node?

Ok, I'm not a rocket scientist but I am not sure that the hysterisis node is right one.

I know the term is used in the question, but I fear it is more for explaining than actual resolution.

And ok, if you flip the curve it could work.

But how Craig is wanting to check things: I am not sure there is any direct benefit to using that node.

Doing it the longer way may help all see a simpler way to get the desired end result.

You may need to tweak the timing of the readings for costs to better save getting unneeded values.

Then adjust how the newer values are parsed by the code to determine if the A/C should be kept on or turned off for costs.

How is it going over the past couple of days?

Yep it is/was going well - based on Colins input/suggestion for how to set the thresholds etc.

As with all things it has now evolved !

So now rather than just ave a set value (25c) for the max i am prepared to accept for running the heatpump i have now set a dashboard slider with multiple options (such as 15c, 17c, 20c, 23c, 25c and 30c)

it defaults to 25c as the baseline each day - if i happen to be glancing at my price forecast for the day and see some more sustained forecasts with lower prices i can then choose to "tune" the heat and choose (say 20c) from slider.

This will then set a Global flow variable

I am now playing with the same hysteresis node but in dynamic mode and trying to get that right in my flow, just testing mode now as i wrap my head around it - but looks OK so far.


1 Like