Prevent Short-cycling

I'm new to node-red and am trying to figure out the best way to prevent my whole house fan from short-cycling.
What I am looking to do is take a look at outside temp and compare to inside. if outside is cooler turn on whole house fan. Once fan hits comfort, turn fan to low. if temp gets to min comfort turn fan off.
I'm able to set this up, but what I do not know how to do is to have the fan only turn back on once it hits the comfort value. it turns back on once it get to min comfort. Any suggestions?

Here's a sample of where I currently am getting a short cycle. IE when temp gets to low_temp it turns off, but once the temp gets above it turns on. I want it to turn on when the temp gets to comfort.

[{"id":"a607dc72.c9d2f","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"3263c44d.89e73c","type":"debug","z":"a607dc72.c9d2f","name":"Once low_temp gets to comfort, turn back on","active":true,"tosidebar":true,"console":true,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":660,"y":560,"wires":[]},{"id":"a3234b70.e4e9b8","type":"api-current-state","z":"a607dc72.c9d2f","name":"inside >=comfort","server":"2ee084e7.d905bc","version":1,"outputs":2,"halt_if":"$entities('input_number.whf_master_comfort_temp').state","halt_if_type":"jsonata","halt_if_compare":"gte","override_topic":false,"entity_id":"sensor.master_bedroom_temperature","state_type":"num","state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","blockInputOverrides":false,"x":150,"y":520,"wires":[["33703d4b.a25152"],["8e25d83d.5fa668"]]},{"id":"33703d4b.a25152","type":"function","z":"a607dc72.c9d2f","name":"Compare","func":"const globalHomeAssistant = global.get('homeassistant');\nvar outside = globalHomeAssistant.homeAssistant.states[\"sensor.dark_sky_temperature\"].state;\nvar inside = globalHomeAssistant.homeAssistant.states[\"sensor.master_bedroom_temperature\"].state;\nmsg.topic = 'speed'\nif (outside <= inside-3) \n{\n    msg.payload = 'max';\n} else if (outside > inside-3 && outside <= inside-1)\n{\n    msg.payload = 'mid';\n}else\n{    \n    msg.payload = 'min';\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":340,"y":500,"wires":[["598779ca.4bfce8"]]},{"id":"8e25d83d.5fa668","type":"api-current-state","z":"a607dc72.c9d2f","name":"inside >=low","server":"2ee084e7.d905bc","version":1,"outputs":2,"halt_if":"$entities(\"input_number.whf_master_low_temp\").state","halt_if_type":"jsonata","halt_if_compare":"gte","override_topic":false,"entity_id":"sensor.master_bedroom_temperature","state_type":"num","state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","blockInputOverrides":false,"x":350,"y":540,"wires":[["14b496b9.2ed8c9"],["11120d8e.507d62","3263c44d.89e73c"]]},{"id":"598779ca.4bfce8","type":"switch","z":"a607dc72.c9d2f","name":"","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"min","vt":"str"},{"t":"eq","v":"mid","vt":"str"},{"t":"eq","v":"max","vt":"str"}],"checkall":"true","repair":false,"outputs":3,"x":510,"y":460,"wires":[["14b496b9.2ed8c9"],["715ab2ea.86fb5c"],["96af83e6.ec7b3"]]},{"id":"11120d8e.507d62","type":"api-call-service","z":"a607dc72.c9d2f","name":"Master WHF Off","server":"2ee084e7.d905bc","version":1,"debugenabled":true,"service_domain":"fan","service":"turn_off","entityId":" fan.master_whole_house_fan","data":"","dataType":"json","mergecontext":"","output_location":"payload","output_location_type":"msg","mustacheAltTags":false,"x":1240,"y":280,"wires":[[]]},{"id":"14b496b9.2ed8c9","type":"api-call-service","z":"a607dc72.c9d2f","name":"Master WHF speed 1","server":"2ee084e7.d905bc","version":1,"debugenabled":true,"service_domain":"fan","service":"set_speed","entityId":" fan.master_whole_house_fan","data":"{\"speed\":\"1\"}","dataType":"json","mergecontext":"","output_location":"payload","output_location_type":"msg","mustacheAltTags":false,"x":1260,"y":340,"wires":[[]]},{"id":"715ab2ea.86fb5c","type":"api-call-service","z":"a607dc72.c9d2f","name":"Master WHF speed 2","server":"2ee084e7.d905bc","version":1,"debugenabled":true,"service_domain":"fan","service":"set_speed","entityId":" fan.master_whole_house_fan","data":"{\"speed\":\"2\"}","dataType":"json","mergecontext":"","output_location":"payload","output_location_type":"msg","mustacheAltTags":false,"x":1260,"y":400,"wires":[[]]},{"id":"96af83e6.ec7b3","type":"api-call-service","z":"a607dc72.c9d2f","name":"Master WHF speed 3","server":"2ee084e7.d905bc","version":1,"debugenabled":true,"service_domain":"fan","service":"set_speed","entityId":" fan.master_whole_house_fan","data":"{\"speed\":\"3\"}","dataType":"json","mergecontext":"","output_location":"payload","output_location_type":"msg","mustacheAltTags":false,"x":1260,"y":460,"wires":[[]]},{"id":"94f478e6.6d7fe8","type":"inject","z":"a607dc72.c9d2f","name":"Run","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":110,"y":460,"wires":[["a3234b70.e4e9b8"]]},{"id":"2ee084e7.d905bc","type":"server","name":"Home Assistant","legacy":false,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true}]

You have some nodes in your flow that aren't standard codes. It's common practice to list what those nodes are so others know what to add. Probably named something like node-red-contrib-something. Without that it's just a guess what the nodes are.
I think the piece of logic you are missing is a value that knows if the fan is on or off. Essentially what you are doing is a thermostat that turns a fan on or off instead of a heater or air conditioner. If you aren't comfortable writing fuctions search the pallete manager for thermostats, there are several.

Hi,

maybe you should have used nested if statements .. they make more sense :wink:
Try this example flow ..

[{"id":"33703d4b.a25152","type":"function","z":"a607dc72.c9d2f","name":"Compare","func":"const globalHomeAssistant = global.get('homeassistant');\n\nvar outside = globalHomeAssistant.homeAssistant.states[\"sensor.dark_sky_temperature\"].state;\nvar inside = globalHomeAssistant.homeAssistant.states[\"sensor.master_bedroom_temperature\"].state;\n\nmsg.topic = 'speed'\n\nif (outside <= inside - 1) \n{\n    if (outside <= inside - 3) { msg.payload = 'max';} \n    else { msg.payload = 'mid';}\n} \n\nelse\n{    \n    msg.payload = 'min';\n}\n\nnode.status({fill:\"green\",shape:\"dot\",text:`Outside: ${outside}, Inside: ${inside}, Fan: ${msg.payload}`})\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":520,"y":240,"wires":[["598779ca.4bfce8"]]},{"id":"598779ca.4bfce8","type":"switch","z":"a607dc72.c9d2f","name":"","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"min","vt":"str"},{"t":"eq","v":"mid","vt":"str"},{"t":"eq","v":"max","vt":"str"}],"checkall":"true","repair":false,"outputs":3,"x":770,"y":240,"wires":[["7104e4ac.0749bc"],["4eacb01f.d6f368"],["aa924e4e.40aa8"]]},{"id":"94f478e6.6d7fe8","type":"inject","z":"a607dc72.c9d2f","name":"Run","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":150,"y":240,"wires":[["3d148a19.172bb6"]]},{"id":"3d148a19.172bb6","type":"function","z":"a607dc72.c9d2f","name":"fake temps","func":"random = parseFloat((Math.random() * 10).toFixed(2))\n\nglobal.set('homeassistant.homeAssistant.states[\"sensor.dark_sky_temperature\"].state', 18 + random )\nglobal.set('homeassistant.homeAssistant.states[\"sensor.master_bedroom_temperature\"].state', 23) \n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":330,"y":240,"wires":[["33703d4b.a25152"]]},{"id":"7104e4ac.0749bc","type":"debug","z":"a607dc72.c9d2f","name":"min","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":950,"y":200,"wires":[]},{"id":"4eacb01f.d6f368","type":"debug","z":"a607dc72.c9d2f","name":"mid","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":950,"y":240,"wires":[]},{"id":"aa924e4e.40aa8","type":"debug","z":"a607dc72.c9d2f","name":"max","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":950,"y":280,"wires":[]}]
1 Like

You have posted three threads with this same question. Please decide which one you want to continue with and add a comment to the others saying not to use them and linking to this one.

@UnborN there is a flaw in your logic that I hit when I was trying to do this before.
If the outside is -5 say below inside the fan turns on to the min value because -5 is less than or equal to -1. As the temps approach each other and say gets to -3 the first if will still be true. You will never get to your second if statement as the first if won't go false.
You need to know if the fan is running and if it is then is the temp difference less than one. If so then go to min.
If fan off and temps different greater than 3 turn on fan max.
If fan on and temp difference less than 3 and greater than 1 go to mid.
If fan on and difference less than 1 go to min.
In this scenario the fan will never turn off so I'd add something like
If outside greater than inside turn fan off.

Alternatively you can place the if statement is the difference less than 3 ABOVE the if statement less than 1 so eventually you would get to the less than 1 statement. But for reasons I can't remember now I found I needed to know the state of the device to make it work like I wanted. Can't get to my pi that flow is on right now to look at my code so this is the best I got at this moment.

I think my logic was I wanted the temps to get at least 4 degrees apart before the device started as I found it would short cycle if a breeze blew across the sensor for a moment and lowered the temp. Instead of stopping a short cycle I created one. So my device needed to be off and temps greater than 4 degrees apart before it would start. Then if it was on and less than 3 apart mid and so on.

Colin, the 3 threads are different ask. I didn't want to cause confusion in asking for help to solve 3 different request.
One request is around if smoke alarm goes off, turn off fan immediately. One smoke is clear wait 5 minutes. if still clear turn on the system again.
Second request is around short-cycling, when temp hits low (say 68 degrees) turn off fan till temp reaches comfort (say 71 degrees) then turn on system again (but keep it off for anything below comfort.
Third request is if a door(s) is/are open for 5 minutes, add total doors to total count of windows. This way when doors open/close it doesn't increase/decrease speed unless doors stay open for 5 minutes, as then we are using the doors to help vent the home. I could possibly use part of the smoke alarm here, but am also adding/subtracting depending on qty open over 5 minutes.

In the future would it have been best to bundle these 3 questions into one, seeing they are different? If I tried to share my flow it would be VERY confusing if you were not familiar with how its suppose to run.

I'll take a look at this! The nested if could definitely help out with the short cycling.

Indeed you are right. It wasnt clear to me in what case the fan actually switches off :wink:
Surely more parameters need to be considered for Hollapm to implement.

maybe you could filter out those fluctuations with a Trigger node ?

image

You can definitely filter those out but I think you would be looking at 5 minutes not seconds

1 Like

I took your previous data and updated it to what I think should work. Fortunately my temperature sensor's update every 5 minutes (to my knowledge), so I don't think I'll run into the challenge of a breeze changing the temp. I think moving off to the first if statement will turn it off

Here's what I did.

[{"id":"a6b89655.f8e918","type":"function","z":"6b59bcb8.ba4474","name":"Compare","func":"const globalHomeAssistant = global.get('homeassistant');\nvar outside = globalHomeAssistant.homeAssistant.states[\"sensor.dark_sky_temperature\"].state;\nvar inside = globalHomeAssistant.homeAssistant.states[\"sensor.master_bedroom_temperature\"].state;\nvar comfort = globalHomeAssistant.homeAssistant.states[\"input_number.whf_master_comfort_temp\"].state;\nvar mincomfort = globalHomeAssistant.homeAssistant.states[\"input_number.whf_master_low_temp\"].state;\nvar mintocomfort = mintocomfort;\nmsg.topic = 'speed'\n\nif (inside >= outside)\n{\n    if (inside >= mincomfort)\n    {\n        if (inside >= comfort)\n        {\n            if (outside <= inside-8)\n            {\n                msg.payload = 'max';\n                mintocomfort = 1;\n            }\n            else if (outside > inside-8 && outside <= inside-5)\n            {\n                msg.payload = 'midmax';\n                mintocomfort = 1;\n            }\n            else if (outside > inside-5 && outside <= inside-3)\n            {\n                msg.payload = 'mid';\n                mintocomfort = 1;\n            }\n            else if (outside > inside-3 && outside <= inside-1)\n            {\n                msg.payload = 'midmin';\n                mintocomfort = 1;\n            }\n            else\n            {\n                msg.payload = 'min';\n                mintocomfort = 1;\n            }\n        }\n        else if (mintocomfort = 1)\n        {\n            msg.payload = 'min';\n        }\n        else\n        {\n            msg.payload = 'off';\n        }\n    }\n    else\n    {\n        msg.payload = 'off';\n        mintocomfort = 0;\n    }\n}\nelse\n{\n    msg.payload = 'off';\n}\nnode.status({fill:\"green\",shape:\"dot\",text:`Outside: ${outside}, Inside: ${inside}, Fan: ${msg.payload}`})\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":400,"y":340,"wires":[["1c47157a.c8220b"]]}]

The editor flagged line 41 you need == in the expression but other than that it looks pretty good.

1 Like

Thanks gerry! I'm going to play with this and see how it works. Now I just need to solve the smoke detector activated, turn off fans. When smoke is clear, wait 5 minutes before turning back on.
Also need to do something similar with open doors. iI door is open for 5 minutes, add to total count.
When door closes, remove from total count. I'm getting really close to having the WHF automated where I can start to tweak it. :-). IE time of day (when its morning, if inside temp slope is > outside temp slope, don't pull in outside air even if outside is cooler), or time of year (pull in warm air in winter when house is cooler than outside).

Here's what I have so far. I believe (knock on wood) this is the solution!

Thank you @gerry and @UnborN

[{"id":"a6b89655.f8e918","type":"function","z":"6b59bcb8.ba4474","name":"Compare","func":"const globalHomeAssistant = global.get('homeassistant');\nvar outside = globalHomeAssistant.homeAssistant.states[\"sensor.dark_sky_temperature\"].state;\nvar inside = globalHomeAssistant.homeAssistant.states[\"sensor.master_bedroom_temperature\"].state;\nvar comfort = globalHomeAssistant.homeAssistant.states[\"input_number.whf_master_comfort_temp\"].state;\nvar mincomfort = globalHomeAssistant.homeAssistant.states[\"input_number.whf_master_low_temp\"].state;\nvar mintocomfort = mintocomfort;\nmsg.topic = 'speed'\n\nif (inside >= outside)\n{\n    if (inside >= mincomfort)\n    {\n        if (inside >= comfort)\n        {\n            if (outside <= inside-8)\n            {\n                msg.payload = 'max';\n                mintocomfort = 1;\n            }\n            else if (outside > inside-8 && outside <= inside-5)\n            {\n                msg.payload = 'midmax';\n                mintocomfort = 1;\n            }\n            else if (outside > inside-5 && outside <= inside-3)\n            {\n                msg.payload = 'mid';\n                mintocomfort = 1;\n            }\n            else if (outside > inside-3 && outside <= inside-1)\n            {\n                msg.payload = 'midmin';\n                mintocomfort = 1;\n            }\n            else\n            {\n                msg.payload = 'min';\n                mintocomfort = 1;\n            }\n        }\n        else if (mintocomfort == 1)\n        {\n            msg.payload = 'min';\n        }\n        else\n        {\n            msg.payload = 'off';\n        }\n    }\n    else\n    {\n        msg.payload = 'off';\n        mintocomfort = 0;\n    }\n}\nelse\n{\n    msg.payload = 'off';\n}\nnode.status({fill:\"green\",shape:\"dot\",text:`Outside: ${outside}, Inside: ${inside}, Fan: ${msg.payload}`})\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":400,"y":340,"wires":[["1c47157a.c8220b"]]}]

Well, it looks like its not solved yet. I have what I thought would be a simple challenge which is hanging me up. The Variable once its set after its run appears to reset the next time the program runs (I tried this with inject and notice that it keeps resetting).

Basically I want var - mintocomfort to be set to 1 when temp is above 6. When it gets to 65, Var mintocomfort = 0. Between 65-68 I don't want to change the variable. This way when temp is below 65 the fan will not turn back on till temp gets above 68.
Currently when temp is between 65-68 I am getting Var undefined (when I believe it should be set when the temp is above 68, and the Var should stay when I inject the next temp value.

Any guidance as to what I may be doing wrong?

[{"id":"3f2367e1.001668","type":"function","z":"65a22b1f.4bb5d4","name":"Compare","func":"var outside = 62;\nvar inside = msg.payload;\nvar comfort = 68;\nvar mincomfort = 65;\nvar mintocomfort = mintocomfort;\nmsg.topic = 'speed';\n\nif (inside >= outside)\n{\n    if (inside >= mincomfort)\n    {\n        if (inside >= comfort)\n        {\n            mintocomfort = 1\n            if (outside <= inside-8)\n            {\n                msg.payload = 'max';\n            }\n            else if (outside > inside-8 && outside <= inside-5)\n            {\n                msg.payload = 'midmax';\n            }\n            else if (outside > inside-5 && outside <= inside-3)\n            {\n                msg.payload = 'mid';\n            }\n            else if (outside > inside-3 && outside <= inside-1)\n            {\n                msg.payload = 'midmin';\n            }\n            else\n            {\n                msg.payload = 'min';\n            }\n        }\n        else if (mintocomfort == 1)\n        {\n            msg.payload = 'min - test';\n        }\n        else\n        {\n            msg.payload = 'off - test1';\n        }\n    }\n    else\n    {\n        msg.payload = 'off - test2';\n        mintocomfort = 0;\n    }\n}\nelse\n{\n    msg.payload = 'off';\n    mintocomfort = 0;\n}\nnode.status({fill:\"green\",shape:\"dot\",text:`VAR: ${mintocomfort}, Inside: ${inside}, Payload: ${msg.payload}`})\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":300,"y":240,"wires":[["b74691db.d89a4"]]}]

One question....
Where does mintocomfort get set?
Or why is there a statement
Var mintocomfort = mintocomfort?

You may have answered my question!
I was under the assumption that since I was having the function search for 0 or 1 within mintocomfort, I would need to create a variable, as it wouldn't know what mintocomfort is w/o adding a variable.
If I remove that line from the function it looks like it works.

Seeing that I set mintocomfort in the if/then statements, I was under the belief that I would need to create a variable, otherwise if I said mintocomfort = 1, the function would have never heard of mintocomfort, therefore it would have failed.