DS18B20 smoothing and PID tuning

I'm a home brewer. I make wine and brew beer but the temperature needs are similar so I created an node-red app that reads temperatures from several DS18B20 temperature probes and uses them to either turn on a heater or a freezer to maintain a constant temperature. As of now I can maintain a pretty steady temperature but i've noticed a lot of static in my temperatures. Colin Law has a PID node that does smoothing but it doesn't have two outputs like the IBM PID which I am using.
I"ve tried tinkering with a function that will average 5 temperature readings which are 5 seconds apart but I'm having no success. I'm not an experienced Java programmer. :frowning:
Has any one done smoothing for the DS18B20?

Hi, one of the good things about Node-Red is that it offers capabilities (core nodes) that lessen the amount of programming (JavaScript ) required to perform the task at hand.

In your case, we can take advantage of the node JOIN that has a reduce capability built in. This is how the configuration of this node looks like (the help in the node explains exactly this very same use case):


What we have to do before sending messages to the JOIN node is to slipt the flow in such a way that the reading for each sensor takes a different path (assuming there are several sensors active), therefore the use of a switch node.

This is how the flow looks like (for 3 sensors):


Another assumption for this flow to work properly is that the temperature reading is in the form:

msg.topic -> has the sensor ID
msg.payload -_ temperature reading


I used in the flow a function node with the only purpose of generating random temperature readings.

The batch node groups the temperature readings each 5 seconds (configurable as you wish).

Flow to import and test:

[{"id":"17ddefb0.b444c","type":"tab","label":"DS18B20 temperature probes","disabled":false,"info":""},{"id":"cb2c2667.60a828","type":"inject","z":"17ddefb0.b444c","name":"","topic":"sensor1","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":158.0999984741211,"y":85.00000095367432,"wires":[["37f436ae.42a18a"]]},{"id":"37f436ae.42a18a","type":"function","z":"17ddefb0.b444c","name":"Format sensor output","func":"let value = msg.payload%100;\nmsg.payload =  value;\nnode.status({text:value});\nreturn msg;","outputs":1,"noerr":0,"x":416.1000747680664,"y":128.00000190734863,"wires":[["15017a37.21a4f6","7bcc6e87.b6812"]]},{"id":"3c31e369.eb1a1c","type":"inject","z":"17ddefb0.b444c","name":"","topic":"sensor2","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":161.99999237060547,"y":130.9999885559082,"wires":[["37f436ae.42a18a"]]},{"id":"e0dc5d43.2940d","type":"inject","z":"17ddefb0.b444c","name":"","topic":"sensor3","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":157.99999237060547,"y":181.00000190734863,"wires":[["37f436ae.42a18a"]]},{"id":"7234c85c.daea08","type":"debug","z":"17ddefb0.b444c","name":"Average Sensor 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":882.0000534057617,"y":241.00000476837158,"wires":[]},{"id":"15017a37.21a4f6","type":"switch","z":"17ddefb0.b444c","name":"","property":"topic","propertyType":"msg","rules":[{"t":"cont","v":"sensor1","vt":"str"},{"t":"cont","v":"sensor2","vt":"str"},{"t":"cont","v":"sensor3","vt":"str"}],"checkall":"true","repair":false,"outputs":3,"x":400.10010147094727,"y":263.9999952316284,"wires":[["9292c17b.430b7"],["17a50.34fa45b0d"],["8531688e.337048"]]},{"id":"9292c17b.430b7","type":"batch","z":"17ddefb0.b444c","name":"","mode":"interval","count":10,"overlap":0,"interval":"5","allowEmptySequence":false,"topics":[],"x":584.1001014709473,"y":243.00000476837158,"wires":[["bd5ae1a.a41ad2"]]},{"id":"bd5ae1a.a41ad2","type":"join","z":"17ddefb0.b444c","name":"","mode":"reduce","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"","reduceRight":false,"reduceExp":"$A+payload","reduceInit":"0","reduceInitType":"num","reduceFixup":"$A/$N","x":712.1000556945801,"y":241.0000057220459,"wires":[["7234c85c.daea08"]]},{"id":"c28b755d.76d118","type":"debug","z":"17ddefb0.b444c","name":"Average Sensor 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":880.0000419616699,"y":292.99999141693115,"wires":[]},{"id":"17a50.34fa45b0d","type":"batch","z":"17ddefb0.b444c","name":"","mode":"interval","count":10,"overlap":0,"interval":"5","allowEmptySequence":false,"topics":[],"x":582.1000900268555,"y":294.99999141693115,"wires":[["6df2759a.4df60c"]]},{"id":"6df2759a.4df60c","type":"join","z":"17ddefb0.b444c","name":"","mode":"reduce","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"","reduceRight":false,"reduceExp":"$A+payload","reduceInit":"0","reduceInitType":"num","reduceFixup":"$A/$N","x":710.1000442504883,"y":292.99999237060547,"wires":[["c28b755d.76d118"]]},{"id":"a868298b.c152c8","type":"debug","z":"17ddefb0.b444c","name":"Average Sensor 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":880.0000419616699,"y":343.99999141693115,"wires":[]},{"id":"8531688e.337048","type":"batch","z":"17ddefb0.b444c","name":"","mode":"interval","count":10,"overlap":0,"interval":"5","allowEmptySequence":false,"topics":[],"x":582.1000900268555,"y":345.99999141693115,"wires":[["d7342bfb.7fc4e8"]]},{"id":"d7342bfb.7fc4e8","type":"join","z":"17ddefb0.b444c","name":"","mode":"reduce","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"","reduceRight":false,"reduceExp":"$A+payload","reduceInit":"0","reduceInitType":"num","reduceFixup":"$A/$N","x":710.1000442504883,"y":343.99999237060547,"wires":[["a868298b.c152c8"]]},{"id":"7bcc6e87.b6812","type":"debug","z":"17ddefb0.b444c","name":"Sensor Reading","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":663.0000076293945,"y":127.00000190734863,"wires":[]}]

Testing four readings with sensor2. The average is 44.25


1 Like

Thanks Andrei!

  I tried my version of Join but it doesn't match yours.  I'm using node-red v0.17.5 on a Raspberry PI3.

  I also tried importing your example but it fails.  The error message is module batch is unrecognized.

What version of node-red are you using?


  BTW: I got my smoother working but it's performance is somewhat less than stellar.  I'll attach the code for your comment if you like.  I implemented it in a simple function module.  The code needs a little polishing but, well, it works.  The sample rate is 5 seconds.

//----- code begins ------

var ta = [];

  Tpv = context.get('ta')||0;

  index = context.get('i')||0;

  total = context.get('t')||0;

  var Tout;

  var pv;

// initialize object if needed

  if(!Tpv){Tpv = [];}

// read the temp and initialize array if needed

  pv = msg.payload;

  if (total === 0){

      total = pv*5;


// calculate the new total of temps in the array

  total -= Tpv[index];


  total += pv;

// update context variables

  context.set('t', total)

  context.set('ta', Tpv);

// calculate average temp over 5 samples

  Tout = total/5;

// increment index and test for limit


  if (index >= 5){index = 0;}

      context.set('i', index);

  // send averaged temp

  msg.payload = Tout;

  return msg;

//----- code ends ------

Why not to use directly the smooth node from the palette where you can chose directly the number of read in you wanted to get the mean value?

If as example you have a value coming each second and you wanted the mean value of the latest 5 readings you will get on the output the mean value of the latest 5 seconds which in my opinions seems better than having 1 mean value each 5 seconds.


1 Like

Thanks davidcgu for the input!

  I upgraded my version of node-red and now have the correct version of Join and now also have Smooth.  I've installed my version of a smoother and the core Smooth on separate temperature probes and a sample of the resulting chart is attached.  The core Smooth is the grey line and my smoothie is the black line.  At the point where the air temperature in the freezer begins to drop the wort/must temperature shows a lot of indecision coming from the DS18B20.  The grey line is nice and smooth during it's entire course, however, and I am pretty convinced that it will be my choice.

  I'm going to stick with the 5 second interval because of the inertia of the wort/must I'll be controlling.  It takes several minutes before the wort/must even begins to change.  In between cooling cycles even with a 20ºF difference in the outside temperature and my wort/must set point the rate of change is very slow.

I appreciate both suggestions, thanks to each of you. :wink:


Hi Charles,

This is the second good thing about Node-RED: normally there is more than one way to achieve what we want.
I was not aware of the node smooth (it is not installed in my palette). I will spare some time to install and play (and learn). :wink:

Go to the palette, install make a search for node-red-node-smooth install and give it a try, in my opinion will be your choice for what u wanted to do in this case.


1 Like

What are the two outputs for? If it is one for heat and one for cool then I have provided a splitter function that takes the output from node-red-contrib-pid and provides separate heat and cool outputs, allowing for independent gain control for heat and cool and also heat/cool overlap if required.

Thanks for your input Colin.

  Yes, I'm using both cooling and heating.  Brewing beer sometimes requires a rise in wort temperature and also a drop in temperature during fermentation so I need both.  Secondarily my freezers are in my garage so the difference in ambient temperature can swing from 90F to 40F from Summer to Winter.  Most yeasts don't tolerate abrupt changes in temperature either.  The lager yeast I use for my Corona Extra clone will only tolerate a 5F change per day.  The recipe calls for a drop from 62F to 32F for lagering.  All that is just to say, there are a lot of parameters that need to be taken into consideration.

  I've changed over from the IBM PID to yours and am comparing them on the same 5 gallon bucket of water.  So far the big difference I see is that your PID is much more active than the IBM version.  My set temp is 65F and the water temp is 64.7375 and your PID is holding the time slice to 3.36% while the IBM PID waited until the temperature was up to 65.6 before even turning on.

  So, long story short, my first observation is that your PID will maintain a more constant temperature but will work my freezer harder; harder being more on/off cycles per time period.

  So far I've been using my smoothie and my power to time converter.  After a good sample time I'll switch over to your smoother and p2t converter.

I appreciate your contribution and input.

Thanks again Colin.


I've switched over to all Colin's nodes and am experiencing wild swings in heat and colling. What really puzzles me is when the pv is higher than the set temp and it is still heating at 100%. There must be something I'm missing in setup or parameters.


Have you got the smoothing node in the flow? If so then take it out for the moment.

What PID settings have you got?

Also what happened at 20:18 to make it change from cooling to heating?

Hi Colin:

  All my own coding was removed and I ran your nodes from beginning to end.

  I tinkered with the parameters a little but at the time of this graphic I had:

  Setpoint: 18.3333333

  Proportional: 1.25

  Int Time: 600

  Derivitive: 0

  Init integ: .6

  Max Samp: 60

The highest I tried Kp was 2 and the highest integral was 0.6.

  It started out slowly at around 35% while the pv was just about 0.2xxC below the setpoint.  I thought I need a little more agressive so I changed prop to 2 it just took off with heat.  After that I couldn't get it back under control.  To reset the PID I stopped node-red and restarted again.  That may be what happened at 20:18.

  I suppose the issue is my experience with the IBM PID.  I set Kp at 40 and Ki at 10 to get the graph I posted earlier.  I'll return all settings to default and run another graph and post it later today.


The oscillation is classic Integral induced oscillation (integral time is too small). Notice how the power lags the temperature by 90 degrees, so the power crosses zero when the temperature is at max and min. You have an integral time of 5 minutes but the oscillation period is about an hour so you want the integral at about half an hour or 1800. In fact it is always better to start with less integral effect (ie a longer time) so I suggest initally setting it to 3600 and see how that goes.

[edit] By the way did you realise your posts are coming out very strangely formatted and tricky to read? I don’t know what you are doing to get that effect.

I’ve been replying using Firebird email client rather than logging in for each email. I see what you mean and can’t explain it. I’ll start replying in the forum. Although, posts previous were fine.(?)

PID is running and holding 70% to 75% power with about 0.6375F above setpoint.

With 1 hour integral it will take around that sort of time to pull in the last little bit onto the setpoint. If it seems nicely stable at 1 hour you could try it at 30 mins. Making the Integral time shorter may increase the overshoot a bit though. It is always a compromise.

With starting at 9999 and then changing to 3600 after 30 minutes I have acheived a 0.1F change in my bucket temperature. I was hoping to get a faster response than that. I’ll try 1800 and report back.
I also noted that my power never got above 95% and for most of the time it was around 75%. I suspect that is a Ti setting too.

Thanks again for your help.

With integral time set to 1800 I’m holding my pv at 64.9625 with power at 70%. :slight_smile:
I have your timeprop cycle time set to 20 seconds. This means that my freezer is turning on and off every 20 seconds. I think I’ll try the time cycle at 1 minute so my freezer will have a little more rest time and fewer on/off cycles. Will that change everything?

It shouldn’t make much difference to the stability of the loop. If you make it too large then the temperature will go up and down over the cycle time (or down and up in the case of a cooler). You can keep a small cycle time on the heater of course, assuming it is just a relay, and put the cooler up to the point at which the temperature cycling becomes too great. I feel it is often good to push a parameter up too much initially and then pull it back, then you get a feel for what happens when it is too much. Then maybe split the difference each time till you get close to the optimum.

Going back to your point that the power only went to 95%, that is a bit odd. Can you post a chart showing that? It would be good if you could put the power from the pid on the graph as well as the heat and cool. Perhaps there is an unexpected feature in my splitter function.

What I have is the last 4 hours.
The bottom of the blue curve was 40.8F which would correspond to 99.2%
At approximately 12:05 I changed the timeprop cycle to 60 seconds.