Central Heating/Home Control schedule. Suggestions please

I have been thinking about this for (quite) a while, and getting nowhere fast.

I am trying to move away from using a file with a temperature for each hour of the week (basically a .csv file) to a more readable format, i.e. identifying the change events for temperature/ON/OFF etc..

After having problems with my system becoming unresponsive (several times) using the node-red-contrib-ramp-thermostat, and getting into trouble with the other half, I decided that using the cronplus node I can get the schedule in the format I need. The problem with this is that should I do a redeploy, the current required 'state' is not loaded into the flow. It is read into context, but that does not mean that the current value is what is actually required.

My aim is to end up with an easily editable event driven schedule that is stored in one file/variable/place.

My question is, does anyone have any suggestion as to how to do this without using the long inflexible .csv method?

Here is what I have so far using cronplus

[{"id":"ee855b993163e811","type":"cronplus","z":"8c6966a6107dcb0f","name":"LR SP","outputField":"payload","timeZone":"","persistDynamic":true,"commandResponseMsgOutput":"output1","outputs":1,"options":[{"name":"schedule1","topic":"topic1","payloadType":"default","payload":"","expressionType":"cron","expression":"0 12 * * MONL","location":"","offset":"0","solarType":"all","solarEvents":"sunrise,sunset"}],"x":410,"y":400,"wires":[["c5b67c2fa77fa613"]]},{"id":"cc10a01f25a66dc7","type":"function","z":"8c6966a6107dcb0f","name":"LivingRoom","func":"msg.payload = [\n    { \"command\": \"add\", \"name\": \"0600\", \"expression\": \"0 0 6 * * * *\", \"expressionType\": \"cron\", \"payloadType\": \"default\", \"payload\": 18 },\n    { \"command\": \"add\", \"name\": \"0700\", \"expression\": \"0 0 7 * * * *\", \"expressionType\": \"cron\", \"payloadType\": \"default\", \"payload\": 19 },\n    { \"command\": \"add\", \"name\": \"0730\", \"expression\": \"0 30 7 * * * *\", \"expressionType\": \"cron\", \"payloadType\": \"default\", \"payload\": 20 },\n    { \"command\": \"add\", \"name\": \"1000\", \"expression\": \"0 0 10 * * * *\", \"expressionType\": \"cron\", \"payloadType\": \"default\", \"payload\": 19 },\n    { \"command\": \"add\", \"name\": \"1600\", \"expression\": \"0 0 16 * * * *\", \"expressionType\": \"cron\", \"payloadType\": \"default\", \"payload\": 20 },\n    { \"command\": \"add\", \"name\": \"1700\", \"expression\": \"0 0 17 * * * *\", \"expressionType\": \"cron\", \"payloadType\": \"default\", \"payload\": 21 },\n    { \"command\": \"add\", \"name\": \"1800\", \"expression\": \"0 0 18 * * * *\", \"expressionType\": \"cron\", \"payloadType\": \"default\", \"payload\": 22 },\n    { \"command\": \"add\", \"name\": \"2000\", \"expression\": \"0 0 20 * * * *\", \"expressionType\": \"cron\", \"payloadType\": \"default\", \"payload\": 20 },\n    { \"command\": \"add\", \"name\": \"2200\", \"expression\": \"0 0 22 * * * *\", \"expressionType\": \"cron\", \"payloadType\": \"default\", \"payload\": 16 }\n]\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":210,"y":400,"wires":[["ee855b993163e811","6a5d826e85828b9b","71d14181cb58fe69"]]},{"id":"1e01c124aee0dee7","type":"inject","z":"8c6966a6107dcb0f","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":200,"y":360,"wires":[["cc10a01f25a66dc7"]]},{"id":"6a5d826e85828b9b","type":"function","z":"8c6966a6107dcb0f","name":"HotWater","func":"msg.payload = [\n    { \"command\": \"add\", \"name\": \"0700HW\", \"expression\": \"0 0 7 * * * *\", \"expressionType\": \"cron\", \"payloadType\": \"default\", \"payload\": 38 },\n    { \"command\": \"add\", \"name\": \"0800HW\", \"expression\": \"0 30 7 * * * *\", \"expressionType\": \"cron\", \"payloadType\": \"default\", \"payload\": 23 }\n]\nreturn msg;\n\n/*\nmsg.topic = \"setProfile\"\nmsg.payload = {\n    \"name\": \"hot water\",\n    \"points\": [\n        { \"00:00\": 23 },\n        { \"06:59\": 23 },\n        { \"07:00\": 38 },\n        { \"07:59\": 38 },\n        { \"08:00\": 23 },\n        { \"23:59\": 23 }\n    ]\n}\nreturn msg;\n*/","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":220,"y":440,"wires":[["a59ea161b4e504ab"]]},{"id":"a59ea161b4e504ab","type":"cronplus","z":"8c6966a6107dcb0f","name":"HW SP","outputField":"payload","timeZone":"","persistDynamic":true,"commandResponseMsgOutput":"output1","outputs":1,"options":[{"name":"schedule1","topic":"topic1","payloadType":"default","payload":"","expressionType":"cron","expression":"0 12 * * MONL","location":"","offset":"0","solarType":"all","solarEvents":"sunrise,sunset"}],"x":420,"y":440,"wires":[[]]},{"id":"c5b67c2fa77fa613","type":"debug","z":"8c6966a6107dcb0f","name":"debug 90","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":560,"y":400,"wires":[]},{"id":"71d14181cb58fe69","type":"debug","z":"8c6966a6107dcb0f","name":"debug 91","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":400,"y":360,"wires":[]}]

TIA

I'm a bit confused about what you mean when you say

are you saying you don't want to code the object information in the function nodes?

msg.payload = [
    { "command": "add", "name": "0700HW", "expression": "0 0 7 * * * *", "expressionType": "cron", "payloadType": "default", "payload": 38 },
    { "command": "add", "name": "0800HW", "expression": "0 30 7 * * * *", "expressionType": "cron", "payloadType": "default", "payload": 23 }
]
return msg;

You want a simpler way to get the information into cronplus? no matter what, you will have to pass in at lease the values for each of the parts of the object.

You could actually create a CSV file with the data in there. Using the above data, the CSV file would look like this:

command,name,expression,expressionType,payloadType,payload
add,0700HW,007****,cron,default,38
add,0800HW,0307****,cron,default,23

you would use a file-in node to read the file as a single utf8 string, feed that into a csv node and the output into the cronplus node.

Here is a CSV file I built, using your data and the fle is called 'crontest.csv':

command,name,expression,expressionType,payloadType,payload
add,0700HW,0  0 7 * * * *,cron,default,38
add,0800HW,0 30 7 * * * *,cron,default,23

and here is the flow* I built to use it:

[{"id":"3b123601852e3f02","type":"inject","z":"e1482d64f0022802","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":180,"y":140,"wires":[["a9f8d87d38cb8594"]]},{"id":"a9f8d87d38cb8594","type":"file in","z":"e1482d64f0022802","name":"","filename":"/Volumes/Gremil-two/Moved_Users/Paul/test/crontest.csv","filenameType":"str","format":"utf8","chunk":false,"sendError":false,"encoding":"none","allProps":false,"x":540,"y":140,"wires":[["773036bdc6f51f15"]]},{"id":"773036bdc6f51f15","type":"csv","z":"e1482d64f0022802","name":"","sep":",","hdrin":true,"hdrout":"none","multi":"one","ret":"\\n","temp":"","skip":"0","strings":true,"include_empty_strings":"","include_null_values":"","x":390,"y":220,"wires":[["b87fa0fda556754e","82577b62d8a5288f"]]},{"id":"b87fa0fda556754e","type":"debug","z":"e1482d64f0022802","name":"debug 93","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":680,"y":220,"wires":[]},{"id":"82577b62d8a5288f","type":"cronplus","z":"e1482d64f0022802","name":"HW SP","outputField":"payload","timeZone":"","persistDynamic":true,"commandResponseMsgOutput":"output1","outputs":1,"options":[],"x":400,"y":340,"wires":[["f60e11abf0a4a1e5"]]},{"id":"381f4ea69594eb1c","type":"catch","z":"e1482d64f0022802","name":"","scope":null,"uncaught":false,"x":680,"y":300,"wires":[["0964e021e4a295a5"]]},{"id":"0964e021e4a295a5","type":"debug","z":"e1482d64f0022802","name":"debug 94","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":680,"y":360,"wires":[]},{"id":"f60e11abf0a4a1e5","type":"debug","z":"e1482d64f0022802","name":"debug 95","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":400,"y":440,"wires":[]},{"id":"b7de727a528aa9ce","type":"inject","z":"e1482d64f0022802","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"remove","payload":"0700HW","payloadType":"str","x":160,"y":300,"wires":[["82577b62d8a5288f"]]},{"id":"3a15a378c1429606","type":"inject","z":"e1482d64f0022802","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"remove","payload":"0800HW","payloadType":"str","x":160,"y":360,"wires":[["82577b62d8a5288f"]]}]
    • note you will need to change the path to the file, I did this on my Mac.

Doing it this way, you just edit the CSV file and save it, then run the flow.

Of course I might have gone off in a direction you didn't mean :grin:

1 Like

Thanks Paul, much appreciated. I see the flow, looks good, and will analyse tomorrow to try to completely understand.

I am looking at options at the moment. I have come to an impasse. I am not averse to using a .csv file, just the modification of 24*7 times every time I want to make a small change, hence the 'drive' towards an event driven schedule. The flow looks good and the addition of the File in the format you have shown makes it even easier rather than passing the Dynamic Schedules from a function node!

As I say, it is the defining of the desired temperature on a redeploy or resetting of the computer as the flow restarts which is stumping me at the moment.

As far as I can see (or understand) cronplus will only set the data when the allotted time is reached, it is the passing of the data between cron times that I am trying to understand.

Why not save the current state in a file context (after any change in state) and reload on startup with an inject node? I have possibly missed where you have explained why this does not work but it is a simple way of keeping track.

1 Like

Thank you both! I am going to have a play because the combination is almost what I am looking for.

The last scenario I envisage is after, say a Power Cut, where it things may be off for, say, several hours. There could have been a change in 'requirements' during that time, so it may be that I need to read the Dynamic Schedule on Power Up.

Thinking out loud, Paul's file format makes things easier, I could grab the cron time and create a table of time and Desired Temp and read from that, but then, why use cronplus, I could just read the table events and compare with current time, which means the current Desired data is available all of the time..

At the end of the day, I feel that I have again been trying to make things way more complicated than they need to be!!

Thank you both for your time, effort and ideas. Such a good and helpful Community reside here!!

May take a few days, but I will post a flow here.

An alternative is to do what (if I remember correctly) bigtimer can do, which is to repeat an output every minute, so that on restart the latest state is updated within 1 minute. Repeating every minute between times can be done in the schedule if I remember correctly, though I would have to remind myself exactly how.

Thank you Colin.

Yes, I have seen that, but rightly or wrongly, I prefer to try and write my own code using the Basic Node-RED nodes. Then if a Node is no longer supported, I don't end up having to write it later. It also means I keep my brain active, I would say learn, but come tomorrow, I may have forgotten it. Retention is becoming more of a problem!!:rofl:

1 Like

OK, I thought at the start you said you wanted to use cronplus but didn't know how to get round this issue.

By the way, I have found the ramp thermostat node absolutely solid. I don't use the t/stat, just the ramping feature.

I came to cronplus to replace ramp-thermostat to make things event driven, but saw the reset/restart 'problems' that would affect this particular application. Glad ramp-thermostat is solid, probably me, I just needed the time feature and had to fudge the ramp by putting an added time to ramp over 1 minute. I will return to that after I get this routine going.

Thhis routine reads in the file in the format Paul suggested. I can get the desired times and temperatures into the relevant locations, I am just trying to get the event time < time now < event+1 time match to the correct temperatures trapping the first and last events (00:00, 23:59). I am happy plodding away doing this, learnt some more JS in the process! Basically, just a glutton for punishment.

I am using cron plus since long. I am loading my schedules from csv files.
one is attached fyi.

my flow looks like this

[{"id":"70489757.9ad0d8","type":"cronplus","z":"4e969c7c.f77dc4","name":"Heating","outputField":"payload","timeZone":"","persistDynamic":true,"commandResponseMsgOutput":"fanOut","outputs":2,"options":[],"x":260,"y":800,"wires":[["e381ec2d.78eb2","31a8f2ab7e3d71ec"],["4fec987f.72b318"]]},{"id":"5355c793.e8c0b8","type":"ui_template","z":"4e969c7c.f77dc4","group":"8784088a.4ec318","name":"","order":8,"width":16,"height":22,"format":"<style type=\"text/css\">\n.t1  {border-collapse:collapse;border-spacing:0;border-color:#ccc;}\n.t1 td{font-family:Arial, sans-serif;font-size:14px;padding:8px 3px;border-style:solid;border-width:0px;overflow:hidden;word-break:normal;border-top-width:1px;border-bottom-width:1px;border-color:#ccc;color:#333;background-color:#fff;}\n.t1 th{font-family:Arial, sans-serif;font-size:14px;font-weight:normal;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:#ccc;color:#333;background-color:#f0f0f0;}\n.t1 .t1-36r9{background-color:#000000;color:#ffffff;text-align:left;vertical-align:top}\n.t1 .t1-x2zo{background-color:#c0c0c0;font-size:small;text-align:left;vertical-align:top}\n.t1 .t1-o73e{background-color:#c0c0c0;font-size:small;text-align:center;vertical-align:top}\n.t1 .t1-5qt9{font-size:small;text-align:left;vertical-align:top}\n.t1 .t1-5qta{font-size:small;text-align:center;vertical-align:middle}\n</style>\n<table class=\"t1\">\n  <tr>\n    <th class=\"t1-36r9\" colspan=\"9\">CRON Schedules</th>\n  </tr>\n  <tr>\n    <td class=\"t1-x2zo\">Name</td>\n    <td class=\"t1-x2zo\">Expression</td>\n    <td class=\"t1-x2zo\">Payload</td>\n    <td class=\"t1-x2zo\">Description</td>\n    <td class=\"t1-x2zo\">Next run</td>\n    <td class=\"t1-x2zo\">State</td>\n    <!-- <td class=\"t1-o73e\">Delete</td>\n    <td class=\"t1-o73e\">Pause</td>\n    <td class=\"t1-o73e\">Resume</td> -->\n  </tr>\n  <tr  ng-repeat=\"item in msg.payload\">\n    <td class=\"t1-5qt9\">{{item.config.name}}</td>\n    <td class=\"t1-5qt9\" style=\"white-space: nowrap\" nowrap>{{item.config.expression}}</td>\n    <td class=\"t1-5qt9\">{{item.config.payload}}</td>\n    <td class=\"t1-5qt9\">{{item.status.description}}</td>\n    <td class=\"t1-5qt9\">{{item.status.nextDescription}}</td>\n    <td class=\"t1-5qta\"><i class=\"fa fa-{{item.status.isRunning ? 'play' : 'pause'}}\"> </i></td>\n\n   <!-- <td class=\"t1-5qta\">\n       <md-button class=\"md-raised\"\n        ng-click=\"send([[{payload:{command:'remove', name: item.config.name}}, {payload:{command:'list-all'}}]]);\"> <i class=\"fa fa-trash\"> </i></md-button>\n   </td>\n   <td class=\"t1-5qta\">\n       <md-button class=\"button\" \n       ng-click=\"send([[{payload:{command:'pause', name: item.config.name}}, {payload:{command:'list-all'}}]]);\"> <i class=\"fa fa-pause\"> </i></md-button>\n   </td>\n   <td class=\"t1-5qta\">\n       <md-button class=\"button\" \n       ng-click=\"send([[{payload:{command:'start', name: item.config.name}}, {payload:{command:'list-all'}}]]);\"> <i class=\"fa fa-play\"> </i></md-button>\n   </td> -->\n\n  </tr>\n\n </tbody>\n</table>\n\n<!--<table id=\"table\" class=\"table table-striped table-responsive-md btn-table\" >-->\n<!--     <tr>-->\n<!--        <th>Name</th> -->\n<!--        <th>Expression</th> -->\n<!--        <th>Payload</th> -->\n<!--        <th>Description</th>-->\n<!--        <th>Next run</th>-->\n<!--        <th>State</th>-->\n<!--        <th>Delete</th>-->\n<!--        <th>Pause</th>-->\n<!--        <th>Resume</th>-->\n<!-- </tr>-->\n<!-- <tbody>-->\n<!-- <tr ng-repeat=\"item in msg.payload\">-->\n<!--   <td >{{item.config.name}}</td>-->\n<!--   <td style=\"white-space: nowrap\" nowrap>{{item.config.expression}}</td>-->\n<!--   <td >{{item.config.payload}}</td>-->\n<!--   <td >{{item.status.description}}</td>-->\n<!--   <td >{{item.status.nextDescription}}</td>-->\n<!--   <td >{{item.status.isRunning ? 'Running' : 'Paused'}}</td>-->\n<!--   <td>-->\n<!--       <md-button class=\"md-raised\"-->\n<!--        ng-click=\"send([[{payload:{command:'remove', name: item.config.name}}, {payload:{command:'list-all'}}]]);\"> <i class=\"fa fa-trash\"> </i></md-button>-->\n<!--   </td>-->\n<!--   <td>-->\n<!--       <md-button class=\"button\" -->\n<!--       ng-click=\"send([[{payload:{command:'pause', name: item.config.name}}, {payload:{command:'list-all'}}]]);\"> <i class=\"fa fa-pause\"> </i></md-button>-->\n<!--   </td>-->\n<!--   <td>-->\n<!--       <md-button class=\"button\" -->\n<!--       ng-click=\"send([[{payload:{command:'start', name: item.config.name}}, {payload:{command:'list-all'}}]]);\"> <i class=\"fa fa-play\"> </i></md-button>-->\n<!--   </td>-->\n<!-- </tr>-->\n<!-- </tbody>-->\n<!--</table>-->\n\n","storeOutMessages":false,"fwdInMessages":false,"resendOnRefresh":false,"templateScope":"local","x":820,"y":960,"wires":[[]]},{"id":"3b9c3e39.b0eaf2","type":"function","z":"4e969c7c.f77dc4","name":"status-all","func":"if(!msg.payload || !msg.payload.result || !msg.payload.result.length){\n    msg.payload = [];\n    return msg;\n}\nmsg.payload = msg.payload.result;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":620,"y":960,"wires":[["5355c793.e8c0b8"]]},{"id":"3dba72ab.a9c45e","type":"ui_button","z":"4e969c7c.f77dc4","name":"Status all-dynamic","group":"8784088a.4ec318","order":2,"width":4,"height":1,"passthru":true,"label":"Heizprofilstatus","tooltip":"","color":"","bgcolor":"","className":"","icon":"","payload":"","payloadType":"str","topic":"status-all","topicType":"str","x":510,"y":200,"wires":[["8ed2d296.e5771"]]},{"id":"59f8dec0.63298","type":"ui_button","z":"4e969c7c.f77dc4","name":"Remove all dynamic","group":"8784088a.4ec318","order":4,"width":4,"height":1,"passthru":true,"label":"Heizprofil löschen","tooltip":"","color":"","bgcolor":"","icon":"","payload":"1","payloadType":"str","topic":"remove-all-dynamic","x":220,"y":280,"wires":[["93efb1f9.b065f","9c5dd9ca.052698"]]},{"id":"fe876335.8d72c","type":"ui_button","z":"4e969c7c.f77dc4","name":"Load all dynamic","group":"8784088a.4ec318","order":3,"width":4,"height":1,"passthru":true,"label":"Heizprofil laden","tooltip":"","color":"","bgcolor":"","icon":"","payload":"","payloadType":"date","topic":"","x":230,"y":340,"wires":[["bee78fa3.da3b6","ce5507d1.7ea898"]]},{"id":"ce186cd3.00ed4","type":"function","z":"4e969c7c.f77dc4","name":"","func":"msg.payload = {\"command\":\"describe\",\n               \"expressionType\":\"solar\",\n               \"location\":\"48.38157356491496 10.437845885753632\",\n               \"solarType\":\"selected\",\n               \"solarEvents\":\"sunrise,sunset\",\n               \"timeZone\":\"Europe/Berlin\"\n              };\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":480,"y":100,"wires":[["6e94c237.fde8dc"]]},{"id":"be95831c.56938","type":"inject","z":"4e969c7c.f77dc4","name":"at 00:01","props":[{"p":"payload"}],"repeat":"","crontab":"01 00 * * *","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":260,"y":80,"wires":[["ce186cd3.00ed4"]]},{"id":"4fec987f.72b318","type":"switch","z":"4e969c7c.f77dc4","name":"","property":"payload.command.command","propertyType":"msg","rules":[{"t":"eq","v":"describe","vt":"str"},{"t":"eq","v":"status-all","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":470,"y":920,"wires":[["8cf018fb.860648"],["3b9c3e39.b0eaf2"]]},{"id":"8cf018fb.860648","type":"function","z":"4e969c7c.f77dc4","name":"get sr + ss items","func":"\nvar sunrise = msg.payload.result.eventTimes.find(e => {\n    return e.event == \"sunrise\"\n})\n\nvar sunset = msg.payload.result.eventTimes.find(e => {\n    return e.event == \"sunset\"\n})\n\nmsg.payload = {\n    sunrise : sunrise.time,\n    sunset : sunset.time\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":640,"y":880,"wires":[["eddd2a47.4c59d8","698182eb.6b7f3c"]]},{"id":"3c9082b6.02527e","type":"moment","z":"4e969c7c.f77dc4","name":"timestamp","topic":"","input":"","inputType":"msg","inTz":"Europe/Berlin","adjAmount":0,"adjType":"days","adjDir":"add","format":"x","locale":"de-DE","output":"","outputType":"msg","outTz":"Europe/Berlin","x":970,"y":840,"wires":[["89731ed5.1eaa","89878eaf.1f54e"]]},{"id":"14a096a8.7cfa79","type":"moment","z":"4e969c7c.f77dc4","name":"timestamp","topic":"","input":"","inputType":"msg","inTz":"Europe/Berlin","adjAmount":0,"adjType":"days","adjDir":"add","format":"x","locale":"de-DE","output":"","outputType":"msg","outTz":"Europe/Berlin","x":970,"y":920,"wires":[["57a09140.7e8e1","c7211032.45a01"]]},{"id":"b009ed30.c3d08","type":"ui_dropdown","z":"4e969c7c.f77dc4","name":"","label":"Heizungsprofile","tooltip":"","place":"Select option","group":"8784088a.4ec318","order":1,"width":0,"height":0,"passthru":false,"multiple":false,"options":[{"label":"Standard","value":1,"type":"num"},{"label":"Gäste","value":2,"type":"num"},{"label":"DG 18 Grad","value":3,"type":"num"},{"label":"Urlaub","value":4,"type":"num"}],"payload":"","topic":"","x":500,"y":40,"wires":[["37fd8f27.715"]]},{"id":"37fd8f27.715","type":"change","z":"4e969c7c.f77dc4","name":"","rules":[{"t":"set","p":"profilenummer","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":710,"y":40,"wires":[["9cb0f7c5.74fd78"]]},{"id":"93efb1f9.b065f","type":"delay","z":"4e969c7c.f77dc4","name":"20 ms","pauseType":"delay","timeout":"20","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"outputs":1,"x":270,"y":220,"wires":[["3dba72ab.a9c45e"]]},{"id":"ec3a1a4e.dfa0e8","type":"link in","z":"4e969c7c.f77dc4","name":"Status","links":["a21f9d7c.8aa0b","3448e26.76c571e"],"x":55,"y":220,"wires":[["93efb1f9.b065f"]]},{"id":"5f636878.66aff8","type":"file in","z":"4e969c7c.f77dc4","name":"","filename":"/home/pi/Heizungsprofile/Standard.txt","format":"lines","chunk":false,"sendError":false,"encoding":"none","x":570,"y":480,"wires":[["13483ad5.b67f65"]]},{"id":"33b32bc9.a11024","type":"json","z":"4e969c7c.f77dc4","name":"","property":"payload","action":"","pretty":true,"x":1030,"y":540,"wires":[["4a9fce4d.54d75","a21f9d7c.8aa0b","3f76705c.b36e5"]]},{"id":"4a9fce4d.54d75","type":"debug","z":"4e969c7c.f77dc4","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1190,"y":540,"wires":[]},{"id":"13483ad5.b67f65","type":"switch","z":"4e969c7c.f77dc4","name":"","property":"payload","propertyType":"msg","rules":[{"t":"nempty"}],"checkall":"true","repair":false,"outputs":1,"x":910,"y":540,"wires":[["33b32bc9.a11024"]]},{"id":"3b6dab5.be4ea54","type":"file in","z":"4e969c7c.f77dc4","name":"","filename":"/home/pi/Heizungsprofile/Gaeste.txt","format":"lines","chunk":false,"sendError":false,"encoding":"none","x":560,"y":520,"wires":[["13483ad5.b67f65"]]},{"id":"cdc555ba.a9b5e8","type":"file in","z":"4e969c7c.f77dc4","name":"","filename":"/home/pi/Heizungsprofile/DG-18-Grad.txt","format":"lines","chunk":false,"sendError":false,"encoding":"none","x":580,"y":560,"wires":[["13483ad5.b67f65"]]},{"id":"a2ee7cd9.983b3","type":"file in","z":"4e969c7c.f77dc4","name":"","filename":"/home/pi/Heizungsprofile/Urlaub.txt","format":"lines","chunk":false,"sendError":false,"encoding":"none","x":560,"y":600,"wires":[["13483ad5.b67f65"]]},{"id":"bee78fa3.da3b6","type":"switch","z":"4e969c7c.f77dc4","name":"flow profilenummer","property":"profilenummer","propertyType":"flow","rules":[{"t":"eq","v":"1","vt":"num"},{"t":"eq","v":"2","vt":"num"},{"t":"eq","v":"3","vt":"num"},{"t":"eq","v":"4","vt":"num"}],"checkall":"true","repair":false,"outputs":4,"x":250,"y":540,"wires":[["5f636878.66aff8"],["3b6dab5.be4ea54"],["cdc555ba.a9b5e8"],["a2ee7cd9.983b3"]]},{"id":"a21f9d7c.8aa0b","type":"link out","z":"4e969c7c.f77dc4","name":"load done","links":["ec3a1a4e.dfa0e8"],"x":1135,"y":500,"wires":[]},{"id":"89731ed5.1eaa","type":"change","z":"4e969c7c.f77dc4","name":"","rules":[{"t":"set","p":"sunrise","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1190,"y":820,"wires":[["e4da9af1.a16978"]]},{"id":"57a09140.7e8e1","type":"change","z":"4e969c7c.f77dc4","name":"","rules":[{"t":"set","p":"sunset","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1180,"y":940,"wires":[[]]},{"id":"89878eaf.1f54e","type":"mqtt out","z":"4e969c7c.f77dc4","name":"","topic":"sun/rise","qos":"0","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"99603ece.52e2a","x":1160,"y":860,"wires":[]},{"id":"c7211032.45a01","type":"mqtt out","z":"4e969c7c.f77dc4","name":"","topic":"sun/set","qos":"","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"99603ece.52e2a","x":1160,"y":900,"wires":[]},{"id":"21386ae0.1c6dd6","type":"function","z":"4e969c7c.f77dc4","name":"","func":"delete msg.topic;\nlet room = msg.payload.room;\nlet type = msg.payload.type;\nlet temp = msg.payload.value;\nmsg.payload = msg.time + \": \" + room + \" \" + type + \" \" + temp;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":820,"y":720,"wires":[["c1463448.066898"]]},{"id":"59b54d7f.fed3d4","type":"ui_toast","z":"4e969c7c.f77dc4","position":"top right","displayTime":"3","highlight":"","sendall":true,"outputs":0,"ok":"OK","cancel":"","raw":false,"topic":"","name":"Soll-Temperaturwechsel","x":1230,"y":720,"wires":[]},{"id":"9c5dd9ca.052698","type":"link out","z":"4e969c7c.f77dc4","name":"","links":["f1362681.3482f8"],"x":635,"y":280,"wires":[]},{"id":"8ed2d296.e5771","type":"link out","z":"4e969c7c.f77dc4","name":"","links":["f1362681.3482f8"],"x":635,"y":200,"wires":[]},{"id":"3f76705c.b36e5","type":"link out","z":"4e969c7c.f77dc4","name":"","links":["f1362681.3482f8"],"x":1135,"y":580,"wires":[]},{"id":"f1362681.3482f8","type":"link in","z":"4e969c7c.f77dc4","name":"CRON PLUS","links":["3f76705c.b36e5","6e94c237.fde8dc","8ed2d296.e5771","9c5dd9ca.052698"],"x":95,"y":800,"wires":[["70489757.9ad0d8"]]},{"id":"eddd2a47.4c59d8","type":"change","z":"4e969c7c.f77dc4","name":"sunrise","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.sunrise","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":820,"y":840,"wires":[["3c9082b6.02527e"]]},{"id":"698182eb.6b7f3c","type":"change","z":"4e969c7c.f77dc4","name":"sunset","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.sunset","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":810,"y":920,"wires":[["14a096a8.7cfa79"]]},{"id":"6e94c237.fde8dc","type":"link out","z":"4e969c7c.f77dc4","name":"","links":["f1362681.3482f8"],"x":635,"y":100,"wires":[]},{"id":"9cb0f7c5.74fd78","type":"link out","z":"4e969c7c.f77dc4","name":"profil out","links":["3bbc34a9.cfd8bc","e767c620.4102b8"],"x":875,"y":40,"wires":[]},{"id":"e767c620.4102b8","type":"link in","z":"4e969c7c.f77dc4","name":"load dynamic","links":["9cb0f7c5.74fd78","bdc9d93.69b7f28"],"x":55,"y":340,"wires":[["fe876335.8d72c"]]},{"id":"b1d3a5cd.458c88","type":"link in","z":"4e969c7c.f77dc4","name":"remove all dynamic","links":["63c75b60.30bb04"],"x":55,"y":280,"wires":[["59f8dec0.63298"]]},{"id":"a35ed99c.1f34c8","type":"link out","z":"4e969c7c.f77dc4","name":"","links":["12480e97.570921"],"x":915,"y":760,"wires":[]},{"id":"1b9cda61.b7a3f6","type":"comment","z":"4e969c7c.f77dc4","name":"to room-router --->","info":"","x":1170,"y":760,"wires":[]},{"id":"c1463448.066898","type":"delay","z":"4e969c7c.f77dc4","name":"","pauseType":"rate","timeout":"4","timeoutUnits":"seconds","rate":"1","nbRateUnits":"2","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"outputs":1,"x":1000,"y":720,"wires":[["59b54d7f.fed3d4"]]},{"id":"a0d6c44.a702438","type":"moment","z":"4e969c7c.f77dc4","name":"HH:mm","topic":"","input":"xyz","inputType":"flow","inTz":"Europe/Berlin","adjAmount":0,"adjType":"days","adjDir":"add","format":"HH:mm","locale":"de-DE","output":"time","outputType":"msg","outTz":"Europe/Berlin","x":660,"y":720,"wires":[["21386ae0.1c6dd6"]]},{"id":"defdf4d8.0e4288","type":"change","z":"4e969c7c.f77dc4","name":"no topic","rules":[{"t":"delete","p":"topic","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":660,"y":760,"wires":[["0336b18870e612c7"]]},{"id":"3448e26.76c571e","type":"link out","z":"4e969c7c.f77dc4","name":"","links":["ec3a1a4e.dfa0e8"],"x":635,"y":340,"wires":[]},{"id":"ce5507d1.7ea898","type":"delay","z":"4e969c7c.f77dc4","name":"10 ms","pauseType":"delay","timeout":"10","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"outputs":1,"x":470,"y":340,"wires":[["3448e26.76c571e"]]},{"id":"b1013812.06a738","type":"link in","z":"4e969c7c.f77dc4","name":"cron init","links":["35ca8d8a.b96ce2","e51bdc1c.bb49a"],"x":295,"y":120,"wires":[["ce186cd3.00ed4","494b9af3.0911c4"]]},{"id":"494b9af3.0911c4","type":"delay","z":"4e969c7c.f77dc4","name":"","pauseType":"delay","timeout":"1","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"outputs":1,"x":260,"y":180,"wires":[["3dba72ab.a9c45e"]]},{"id":"e381ec2d.78eb2","type":"function","z":"4e969c7c.f77dc4","name":"if cron true","func":"if (global.get('cron') === true){\n    node.status({fill:\"green\",shape:\"ring\",text:\"true\"});\n    return msg;\n}\nelse {\n    node.status({fill:\"red\",shape:\"ring\",text:\"false\"});\n}\nreturn null;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":490,"y":740,"wires":[["a0d6c44.a702438","defdf4d8.0e4288"]]},{"id":"a1dcf39b.e4e5c","type":"function","z":"4e969c7c.f77dc4","name":"","func":"let day = 1000*60*60*24;\nlet sr = parseInt (flow.get('sunrise'));\nlet ss = parseInt(flow.get('sunset'));\nlet dif = (ss - sr);\n//msg.topic = \"debug\"  \n//msg.payload = dif;\n//node.send(msg);\nlet dauer_tag = runtime_str(dif);\ndif = day - dif;\nlet dauer_nacht = runtime_str(dif);\nflow.set('tag', dauer_tag);\nflow.set('nacht', dauer_nacht);\nmsg.payload = dauer_tag + \" : \" + dauer_nacht; \nreturn msg;\n\n//----------------------------------\n\n//  HH:MM:SS\n\nfunction runtime_str(ms){\n    let t = ms / 1000;\n    let h = (Math.floor(t / 3600)).toString();\n    let m = (Math.floor(t%3600 / 60)).toString();\n    let s = (Math.floor(t%3600 % 60)).toString();\n//    let r = h + \":\" + (\"0\" + m).slice(-2) + \":\" + (\"0\" + s).slice(-2);\n    let r = h + \":\" + (\"0\" + m).slice(-2);    \n    return r;  \n}\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1580,"y":820,"wires":[["21b33ac3.087866"]]},{"id":"e4da9af1.a16978","type":"delay","z":"4e969c7c.f77dc4","name":"","pauseType":"delay","timeout":"500","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"outputs":1,"x":1400,"y":820,"wires":[["a1dcf39b.e4e5c"]]},{"id":"21b33ac3.087866","type":"debug","z":"4e969c7c.f77dc4","name":"","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":1730,"y":820,"wires":[]},{"id":"0336b18870e612c7","type":"function","z":"4e969c7c.f77dc4","name":"","func":"let t = JSON.stringify(msg.payload); \nnode.status({fill:\"green\", shape:\"ring\", text:t});\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":820,"y":760,"wires":[["a35ed99c.1f34c8"]]},{"id":"8518d8882da9f81e","type":"function","z":"4e969c7c.f77dc4","name":"store CurrentSoll in global","func":"if (msg.payload.type === \"soll\") {\n    let room = \"CurrentSoll_\" + msg.payload.room;\n    let value = parseInt(msg.payload.value);\n    global.set(room,value);\n    let t = msg.time + \":\" + room + \" \" + value + \" soll\";\n    node.status({fill:\"blue\",shape:\"ring\",text:t});    \n}\nreturn null;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":670,"y":660,"wires":[[]]},{"id":"31a8f2ab7e3d71ec","type":"moment","z":"4e969c7c.f77dc4","name":"HH:mm","topic":"","input":"","inputType":"date","inTz":"Europe/Berlin","adjAmount":0,"adjType":"days","adjDir":"add","format":"HH:mm","locale":"de-DE","output":"time","outputType":"msg","outTz":"Europe/Berlin","x":480,"y":660,"wires":[["8518d8882da9f81e"]]},{"id":"8784088a.4ec318","type":"ui_group","name":"CRON","tab":"9920c734.9de2a8","order":3,"disp":false,"width":"16","collapse":false},{"id":"99603ece.52e2a","type":"mqtt-broker","broker":"10.0.0.43","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":4,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"9920c734.9de2a8","type":"ui_tab","name":"CRON Plus","icon":"dashboard","order":22,"disabled":false,"hidden":false}]

this my standard profile file:
Standard.txt (6.7 KB)

1 Like

I think similar to you, and like to avoid full-dependence on 3rd party stuff because it probably won't have great long term support.
But I also started migrating my household heat to microcontrollers and NodeRed management.
For the scheduling side, I did end up going with a contrib node that has been very reliable and works great: node-red-contrib-ui-time-scheduler .

I build a schedule and save it to file (home or vacation/away), and it sends a numeric output to a change node that sets a global variable, which is read elsewhere and compared to temp sensor values. Between the scheduler and the change node that sets the global variable, there's a user input box that allows a user to override the schedule if cold or leaving and want to turn the heat down.
The vacation file is loaded by UI button that trips a whole bunch of actions if we go on vacation (kills water heater, well pump, loads vacation heat sched, etc).

I'm not sure that will help with the "last state" issue (not 100% sure I understand it), but if you have it write a file with every schedule change, it can then reload that same file. Without that file output method, I found it would wipe it's schedule with every redeploy.

Regarding the last-state issue, do you mean last state of the schedule?
I would expect that you'd want the system to read the current environment parameters, combined with the current schedule state, and set heat control according to that.
IE, upon restore after crash or power outage, it gets all the temp readings, gets the current state of the burner or contactor, then processes whether or not it should demand heat or not.

UI Scheduler Node:

Edit: Sorry, still writing, not sure how I posted it...

1 Like

Thanks you everyone, @juntiedt, I will have a look at your flow later - visitors over the weekend!

Literally, just finished my version of a flow and it looks as though it may work!! Not saying it is clean, but had some problems getting indexing correct in the for loop, hence the defining of the time variable before the if statement. Cleaning up will be done next week. Time, as usual will tell if I have cracked it!!

EDIT: This seemed to work at the time for HW, but testing shows problems - wrong parameters in loop. NOT WORKING!!
Deleted flow...

Times files...
CHt.imes.txt (103 Bytes)
HWt.imes.txt (49 Bytes)
(Remove .txt from file name)

Comments welcome. (Fingers crossed for midnight rollover!!)

I knew there was easier logic!! Decrement the loop!!

[{"id":"0f754cfa5d2f11b3","type":"tab","label":"Heating Event driven switch","disabled":false,"info":"","env":[]},{"id":"e55ff2a0097c1601","type":"comment","z":"0f754cfa5d2f11b3","name":"filepath","info":"// file path with / at the end\nvar filepath = \"/mnt/array1/NodeRed/AutoBackups/\"; // This is the path\nvar localfilepath = \"/home/pi/node-red-storage/\";\n//\"220129_nodered_backup.zip\"","x":310,"y":60,"wires":[]},{"id":"d22324c7f6658b15","type":"inject","z":"0f754cfa5d2f11b3","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":"30","topic":"","payload":"","payloadType":"date","x":140,"y":100,"wires":[["236e0b92934aa646","f4961ee17e09efa4"]]},{"id":"236e0b92934aa646","type":"file in","z":"0f754cfa5d2f11b3","name":"HW times file","filename":"/home/colin/node-red-storage/HWt.imes","filenameType":"str","format":"utf8","chunk":false,"sendError":false,"encoding":"none","allProps":false,"x":310,"y":100,"wires":[["2c5e99851d92fd90"]]},{"id":"2c5e99851d92fd90","type":"csv","z":"0f754cfa5d2f11b3","name":"","sep":",","hdrin":true,"hdrout":"none","multi":"one","ret":"\\n","temp":"","skip":"0","strings":true,"include_empty_strings":"","include_null_values":"","x":450,"y":100,"wires":[["90934bf9f347ac05"]]},{"id":"347f7f877f9020c9","type":"catch","z":"0f754cfa5d2f11b3","name":"","scope":null,"uncaught":false,"x":620,"y":60,"wires":[["1835d45f1fd96af9"]]},{"id":"1835d45f1fd96af9","type":"debug","z":"0f754cfa5d2f11b3","name":"debug 98","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":760,"y":60,"wires":[]},{"id":"90934bf9f347ac05","type":"join","z":"0f754cfa5d2f11b3","name":"","mode":"custom","build":"array","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"1","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":570,"y":100,"wires":[["a7c6c2c983db796b"]]},{"id":"e314060fc6833351","type":"debug","z":"0f754cfa5d2f11b3","name":"debug 99","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1060,"y":160,"wires":[]},{"id":"a7c6c2c983db796b","type":"change","z":"0f754cfa5d2f11b3","name":"","rules":[{"t":"set","p":"#:(storeInFile)::timesHW","pt":"global","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":730,"y":100,"wires":[["2a322bc99fb6d87c"]]},{"id":"2a322bc99fb6d87c","type":"function","z":"0f754cfa5d2f11b3","name":"set HW deg","func":"delete msg.payload;\ndelete msg.topic;\ndelete msg.filename;\ndelete msg.columns;\n\nlet length;\nlet setHours;\nlet setMinutes;\nlet eventTime;\nlet timeEvent;\nlet payload = global.get('timesHW', \"storeInFile\");\nlet tempDesired = global.get('tempDesiredHW', \"storeInFile\") || 0;\n\n//Get current day start and end timestamp\nlet dateNow = Date.now();\nlet timeFrom = new Date(dateNow).setHours(0, 0, 0, 0);\nlet timeTo = new Date(dateNow).setHours(23, 59, 59, 0)\n//msg.payload = timeTo; //dateNow + \" + \" + timeFrom + \" + \" + timeTo;\n//msg.timeFrom = timeFrom;\n//msg.timeTo = timeTo;\n\n//let i=3;\n\n//Add offset, iterate until file time < current time\nfor (let i = payload.length - 1; i >= 0; i--) {\n    eventTime = payload[i].time;\n    //    msg.eventTime = eventTime\n    setHours = eventTime.slice(0, eventTime.indexOf(\":\"));\n    setMinutes = eventTime.slice(eventTime.indexOf(\":\") + 1);\n    timeEvent = new Date(timeFrom).setHours(setHours, setMinutes, 0, 0); //Date from midnight this morning\n    msg.timeEvent = timeEvent;\n//    msg.payload = timeFrom + \" + \" + timeEvent + \" + \" + timeTo;\n    msg.index = i;\n\n\n    tempDesired = payload[i].desired;\n    msg.tempDesired = tempDesired;\n    msg.dateNow = dateNow;\n    //    msg.timeThen = timeThen;\n\n    if (dateNow > timeEvent) {\n        global.set('tempDesiredHW', tempDesired, 'storeInFile');\n        node.status({ text: timeEvent + \" | \" + tempDesired });\n        return msg;\n    }\n\n}\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":910,"y":100,"wires":[["b1e54be8e7b858ad"]]},{"id":"b1e54be8e7b858ad","type":"debug","z":"0f754cfa5d2f11b3","name":"debug 100","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1070,"y":100,"wires":[]},{"id":"f4961ee17e09efa4","type":"file in","z":"0f754cfa5d2f11b3","name":"CH times file","filename":"/home/colin/node-red-storage/CHt.imes","filenameType":"str","format":"utf8","chunk":false,"sendError":false,"encoding":"none","allProps":false,"x":310,"y":140,"wires":[["4d488e485025ed7f"]]},{"id":"4d488e485025ed7f","type":"csv","z":"0f754cfa5d2f11b3","name":"","sep":",","hdrin":true,"hdrout":"none","multi":"one","ret":"\\n","temp":"","skip":"0","strings":true,"include_empty_strings":"","include_null_values":"","x":450,"y":140,"wires":[["392fde4e9b6b416b"]]},{"id":"392fde4e9b6b416b","type":"join","z":"0f754cfa5d2f11b3","name":"","mode":"custom","build":"array","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"1","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":570,"y":140,"wires":[["22639536baf79828"]]},{"id":"22639536baf79828","type":"change","z":"0f754cfa5d2f11b3","name":"","rules":[{"t":"set","p":"#:(storeInFile)::timesCH","pt":"global","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":730,"y":140,"wires":[["dd79303f44f035c0"]]},{"id":"dd79303f44f035c0","type":"function","z":"0f754cfa5d2f11b3","name":"set CH deg","func":"delete msg.payload;\ndelete msg.topic;\ndelete msg.filename;\ndelete msg.columns;\n\nlet length; \nlet setHours;\nlet setMinutes;\nlet eventTime;\nlet timeEvent;\nlet payload = global.get('timesCH', \"storeInFile\");\nlet tempDesired = global.get('tempDesiredCH', \"storeInFile\")||0;\n\n//Get current day start and end timestamp\nlet dateNow = Date.now();\nlet timeFrom = new Date(dateNow).setHours(0, 0, 0, 0);\nlet timeTo = new Date(dateNow).setHours(23, 59, 59, 0)\n//msg.payload = timeTo; //dateNow + \" + \" + timeFrom + \" + \" + timeTo;\n//msg.timeFrom = timeFrom;\n//msg.timeTo = timeTo;\n\n//let i=3;\n\n//Add offset, iterate until file time < current time\nfor (let i = payload.length - 1; i >= 0; i--) {\n    eventTime = payload[i].time;\n//    msg.eventTime = eventTime\n    setHours = eventTime.slice(0, eventTime.indexOf(\":\"));\n    setMinutes = eventTime.slice(eventTime.indexOf(\":\") + 1);\n    timeEvent = new Date(timeFrom).setHours(setHours,setMinutes,0,0); //Date from midnight this morning\n    msg.timeEvent = timeEvent;\n//    msg.payload = timeFrom + \" + \" + timeEvent + \" + \" + timeTo;\n    msg.index = i;\n\n\n    tempDesired = payload[i].desired;\nmsg.tempDesired = tempDesired;\nmsg.dateNow = dateNow;\n//    msg.timeThen = timeThen;\n\n    if (dateNow > timeEvent){\n        global.set('tempDesiredCH', tempDesired, 'storeInFile');\n        node.status({ text: timeEvent + \" | \" + tempDesired });\n        return msg;  \n    }\n\n}\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":910,"y":160,"wires":[["e314060fc6833351"]]}]
1 Like

@juntiedt Had a look at the flow, and it may suffer from the problem described.

After a Power Loss (say 2 hours), if the desired temperature increases during that time, it will not be recognised until the next Cron event from the Dynamic Schedule. For a Heating system that takes a while to heat up, this could be a long time, especially if this occurs over a period where the heating is off.

Similarly, if the desired temperature decreases during that time, the heating system may be on until the next cron event.

Hence my desire to update regularly as with the boiler using lower temperatures for Condensing Mode, the thermal lag can be very slow.

if you loose power your boiler or heating system will also be down in most cases.
I am saving my cron data into memory which is persisted. if you restart after a power loss you can simulate the last cron event depending of the temperatur of your boiler or heating system. Not perfectly but closer to what you are looking for.

As I think I posted earlier if the dynamic schedule is set to repeat the action every minute then that problem goes away.

Sorry, I missed that. Are you just injecting into cronplus every minute to enable this?

If so, then I have missed a bit of understanding with regard to cronplus and need to go experiment.

Here are a couple of examples of triggering cron jobs every so often between two times

Thank you Colin, I get forget those 'hidden gems' within CRON. I keep thinking (and must change my mindset) that CRON triggers single events. Having done most of my programming in Machine Code, or Assembler, these higher level functions take some getting used to!

this is ok if your RPI still has power. If the controller is down this does not work.