By way of review, I have a 7CU chest type freezer I use for brewing and aging wine. I use a 125W heat lamp for warming and, obviously, the freezer for cooling. Colin Law graciously helped me tune his PID node for the cooling side last summer and it has been working flawlessly. (Thanks!)
It's winter and time to tune the warming side.
Here is the cooling flow:
[{"id":"8aae4b77.5f52a8","type":"tab","label":"PID-BrewBucket","disabled":false,"info":""},{"id":"2805617b.3c39c6","type":"comment","z":"8aae4b77.5f52a8","name":"Temperature Control","info":"","x":220,"y":40,"wires":[]},{"id":"7fb90422.38a1cc","type":"link out","z":"8aae4b77.5f52a8","name":"BrewBox_Cool_PID","links":["2f2d49d.cd842b6","3eec1917.f77a7e","23f91614.cb3b52","5254a3d5.631ff4","33adeae3.377626"],"x":1135,"y":180,"wires":[]},{"id":"cd0aa371.e74d4","type":"comment","z":"8aae4b77.5f52a8","name":"BrewBox PID","info":"PID Control Node\n\nThis node has 2 outputs, the first is for \nheating control, the second is for cooling \ncontrol.\n\nBoth outputs provide a floating point value \nbetween 0 and 10.\n\nSettings\n\nThe node takes the following configuration values:\n\n Gain: The % gain to apply to the difference between the setpoint and input value\n Ti: The time in seconds over which to apply the gain value\n Dead Band: The +/- each side of the setpoint to consider the setpoint reached\n Recalculation Time: The time in seconds between each calculation of values\n Setpoint Topic: the topic a message should have to set the setpoint\n Fire Topic: the topic a message should have to enable/disable Fire mode\n Fixed Value Topic: the topic a message should have to enable/disable fixed output mode\n Fixed Value: the value to output when in fixed mode, +ve for heating -ve for cooling\n\nOperations\n\nBefore you can use the node you need to set \nthe setpoint, to do this you need send a \nmessage with the setpoint value as the payload \nand a topic set to the Setpoint Topic \nconfigured in the editor UI.\n\nFire mode sets all outputs to 0. \nFire mode is enabled by sending a message \nwith a payload of false to and a topic equal \nto the Fire Topic. To disable Fire mode send \na message with a payload of true and a topic \nequal to the Fire Topic","x":560,"y":40,"wires":[]},{"id":"a6666729.59d64","type":"link in","z":"8aae4b77.5f52a8","name":"BrewBox_Stop","links":["3cf71be3.8491dc","bf31d5f2.1a3828"],"x":285,"y":120,"wires":[["e8716a19.58a768"]]},{"id":"631bb978.f50118","type":"link in","z":"8aae4b77.5f52a8","name":"BrewBox_Start","links":["9281b062.7edd48","859b757a.2891d8","d025e0d6.f340d"],"x":285,"y":80,"wires":[["e8716a19.58a768"]]},{"id":"858fbfb3.29de7","type":"function","z":"8aae4b77.5f52a8","name":"Split Heat/Cool","func":"/* A function designed to be used with node-red-contrib-pid in applications where both\n * heating and cooling are available to control the system.\n * The node is given a power value in msg.payload in the range 0 to 1, such as is produced by \n * node-red-contrib-pid and splits this into a heat power (o/p 1) and cool power (o/p 2) where\n * each is in the range 0 to 1. These can then be fed directly into an output device, if this\n * is continuously variable, or they may be passed to node-red-contrib-timeprop nodes to generate\n * time proportioned on/off outputs.\n * There are two particular issues to be dealt with in a heat/cool application. Firstly is the fact\n * that the cooling device may be more or less powerful than the heating device. It is necessary\n * therefore to be able to adjust the gain of the system separately for heating and cooling. Secondly\n * is the highly non-linear response of some devices, notably refrigerant systems, that can have a\n * large effect initially, then this tails off. To compensate for this it is useful to have an \n * overlap range where both heat and cool are slightly on.\n *\n * To allow for these requirements two variables can be set below. The value of the power input value\n * where the heating starts to come on is determined by the variable heatMin. Above this value the\n * heating will rise till it is fully on with an input of 1.\n * The cooling is fully on when value of the power input is 0, and falls till the cooling is fully\n * off at an input of coolMin.\n *\n * If the heating and cooling systems are of similar power then set heatMin and coolMin both to 0.5\n * in which case input values of 0.5 to 1.0 will map to heating outputs of 0.0 to 1.0,\n * and 0.5 down to 0.0 will map to cooling 0.0 to 1.0.\n * If, for example, the cooling system is more powerful than heating then they can both be set\n * to something like 0.7 which increases the gain in the heating region and reduces it\n * in the cooling region, to compensate for the different powers in the heating/cooling systems.\n * If some overlap is desired (so that both heat and cool are on slightly near the crossover\n * point) then overlap the two settings so that, for example, heatMin might be 0.45 and coolMin\n * might be 0.55\n */\n\n// set these as described above\nvar heatMin = 0.5; // the value of input corresponding to 0 heat o/p\nvar coolMin = 0.5; // the value of input corresponding to 0 cool o/p\n \nvar power = msg.payload;\nvar heat = (power - heatMin)/(1 - heatMin);\n// limit to range 0 to 1\nheat = Math.min(Math.max(heat, 0), 1);\nvar cool = (coolMin - power) / coolMin;\n// limit to range 0 to 1\ncool = Math.min(Math.max(cool, 0), 1);\nreturn [{payload: heat}, {payload: cool}];\n","outputs":"2","noerr":0,"x":790,"y":90,"wires":[[],[]]},{"id":"e25eceb3.16d238","type":"link in","z":"8aae4b77.5f52a8","name":"BrewBucket_RX","links":["5ada9764.19b6a"],"x":285,"y":160,"wires":[["e8716a19.58a768"]]},{"id":"e8716a19.58a768","type":"PID","z":"8aae4b77.5f52a8","name":"BrewBucket.PID","setpoint":"18.333","pb":"5","ti":"57600","td":"0","integral_default":"1","smooth_factor":"0","max_interval":"600","enable":1,"disabled_op":"1","x":570,"y":160,"wires":[["c3edcaf2.c8de7"]]},{"id":"468cec3c.edc1d4","type":"function","z":"8aae4b77.5f52a8","name":"Fetch setpoint","func":"var s = global.get('BB_setpoint');\nmsg.topic = 'setpoint';\nmsg.payload = s;\nreturn msg;","outputs":1,"noerr":0,"x":280,"y":220,"wires":[["e8716a19.58a768"]]},{"id":"bd9ca82e.9de228","type":"inject","z":"8aae4b77.5f52a8","name":"Setpoint Restart","topic":"setpoint","payload":"1","payloadType":"num","repeat":"","crontab":"","once":true,"onceDelay":"","x":270,"y":350,"wires":[["40d9fdb8.4f7b6c"]]},{"id":"d94c80ab.7550e8","type":"link in","z":"8aae4b77.5f52a8","name":"","links":["211309ae.9cc57e"],"x":135,"y":220,"wires":[["468cec3c.edc1d4"]]},{"id":"40d9fdb8.4f7b6c","type":"delay","z":"8aae4b77.5f52a8","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":300,"y":290,"wires":[["468cec3c.edc1d4"]]},{"id":"7dccdaa1.132f14","type":"comment","z":"8aae4b77.5f52a8","name":"PID last","info":"pb = 4.575\nTi = 5746\nd = 0","x":540,"y":120,"wires":[]},{"id":"c3edcaf2.c8de7","type":"range","z":"8aae4b77.5f52a8","minin":"1","maxin":"0","minout":"1","maxout":"0.85","action":"scale","round":false,"property":"payload","name":"Power Scale","x":990,"y":180,"wires":[["7fb90422.38a1cc"]]}]```