Best way to calculate a 60 point average

I’m developing a rule to calculate the PH value of water using a Phidgets PH Probe and Hub. Due to analogue noise I need to average out 60 values so I can get rid of the noise caused by an almost 0V signal being amplified.
I’ve created a rule that does what I want using around 250 lines of code which I start periodically via cron, but I’m thinking there is a better way. The code follows this pattern:

  1. get value using a command prompt and node
  2. convert value to a number
  3. store number as a variable
  4. sleep for a second
  5. repeat 59 times using a different variable name each time
  6. work out the average with a long formula in the form of “PH1 + PH2 + PH3 etc / 60”
  7. post the result to a number item

My long rule works, but is there an easier way of doing this? Currently I have MAPDB running as my persistance from a RAM Drive that is copied to the file system on reboot in case its relevant.

If u have access to the hardware you can use a single capacitor to do an analog average before the ad converter takes the reading. Makes the code simpler and less overheads.

If not wanting to hack, I would use an array to hold the readings not 60 separate variables. See this example and use a timer cron as in the second link.

How can I get each value into the array? Right now I have

var PH1 = Double::parseDouble(executeCommandLine('"node" "/home/pi/data/phidgets/singlePH.js" "localhost"', 5000))
var PH2 = Double::parseDouble(executeCommandLine('"node" "/home/pi/data/phidgets/singlePH.js" "localhost"', 5000))
repeated another 58 times

Maybe this could be done in Javascript and Node or one of the other languages instead, although you could write what I currently know about Node / Java / Python on the back of a postage stamp.
The actual data is available via a backend server which Node Queries

I think you need to upgrade from a postage stamp to a piece of paper. Google what is a static variable, also what is an array.

I am a noob c programmer so the following may not be perfect syntax and my phone is autocorrecting badly.

static var int iIndex=0
static var int iReading[60]

iReading[iIndex]=code to fetch reading here

Next time the rule runs the index is one value higher and gets placed in the next element. Don’t write past the end of the array and make sure you google what a fence post error is.

By doing this you can be calculating the average as the array fills.

The present rule engine doesn’t support arrays !

I’d use persistence to store the values every second and just use the build-in .averagesince to get the desired value. Read Here

1 Like

You could use a (digital) lowpass filter to get rid of the noise. It has the same effect as adding a resistor and a capacitor to your analog input.
Advantages: you don’t need to keep track of the 60 previous raw measurements and you get an updated filtered value for each new measurement, every second in your case.

Item to hold the filtered value:

Number PH_Filtered "Filtered pH [%.2f]"


// filter coefficient alpha
// The number "60" here relates more or less to the number of samples
// taken into account for filtering (similar to averaging),
// play with this value to adjust the amount of smoothing
val float alpha = 60

rule "Lowpass filter for pH"
	Time cron "* * * * * ?" // every second
	// Grab a new pH measurement (raw value)
	var float ph_raw = <ADD YOUR CODE HERE>

	if (PH_Filtered.state == NULL) { // Initialize the filter
	else { // Update the filtered value
		var float ph_filt_prev = (PH_Filtered.state as Number).floatValue()
		PH_Filtered.postUpdate(ph_filt_prev + (ph_raw - ph_filt_prev) / alpha)

If I understand that code correctly, the variable ph_filt_prev is used to store the previous value until the next run of the code. If that is true it won’t work since the variable is declared inside the rule and will the lost each time the rule ends! Declare it inside this fils but above the rule .

The previous value is stored in an item (PH_Filtered).

The ph_filt_prev variable is updated every time the rule runs, no need to declare it globally:

The rule then updates the item and exits:

Thanks very much for the reply, I’m currently experimenting with different values of “alpha” to see which works best. I’ll update once I have worked out which combination works well.

I moved the probe sampling over from Javascript to Python and learnt a little basic programming, The Probe now reports its state to Openhab via a single reading which is the average of 4 readings taken 6 seconds apart. I then add this to your formula with alpha = 120. This gives a very smooth graph that still changes when the PH of the water is changed by adding chemicals.
pH Probes are very very high resistance so adding a capacitor would probably upset its calibration due to the capacitors leakage current. I do use them elsewhere though to get rid of pick up noise on 3.3V contact sensors

I doubt it would be an issue. Hook up a scope and watch what happens when you add a “low ESR” cap say a 0.1 to 10uf value. If you connect too large a cap what it will do is prevent you from seeing fast changes as it takes time for the cap to charge to the ‘new analog average’ value. Internally this is how AD convertors work, they have a charge cap that they fill before reading the voltage level and when coding in assembly you have to calculate how long to wait (accusition time) before taking the reading. How long you wait depends on the size cap and the resistance of your sensor circuit and their are tables in the datasheets that cover this to tell you how long to wait. They do this as the AD convertor is multiplexed out to multiple pins. By looking at the IC’s on your pcb you can get the datasheet and read exactly what the IC manufacturers recommend. I would take this approach personally as I do it often in my own designs. If you don’t have a scope, just try a 1uf cap, you cant hurt anything.

I stuck my scope on the probe and there wasn’t much noise at all, and after disconnecting, the admittedly unterminated, A to D converter was delivering nearly half the noise all by itself, as does its mate next door. At PH7 the output is practically zero, so some noise is understandable when amplifying it. The A to D’s are brand new in module format and sealed in a box, so I’m very happy to leave it alone, keep my warranty intact and use the digital filter, especially as I’ve got Python doing half the work now.
My analogue plant moisture sensor fed into a WemosD1Mini however is a different matter and that got calmed down with a large electrolytic

Thanks for the information.
In the end I did an online course in Python and modified the example scripts supplied by Phidgets. The script runs every minute and takes an average over 48 seconds from both probes and then outputs the result as JSON for further processing in OH by a variant of the rule in post 6. It works well, and I have settled on a value of Alpha of 10 for my PH probe and 8 for my ORP probe which give a good balance between noise filtering and responsiveness…Python was surprisingly easy to learn to the level I needed.

1 Like

I know this is an old thread (and I may be being thick) but in simple terms
total = 0
for x=1 to 60
read value
total = total + value
wait 1 second
next x
average = total/60
and repeat
unless you’re wanting a moving average
but pH by its nature is a slow moving change. I’m not sure a reading every second is really useful. In the days when I used to sell pH systems it took about 15 seconds for an electrode to react.

The problem was the variation between individual readings I was getting due to electrical noise on the very low voltage that comes from the probe. I sorted it by modifying the Python script to take several readings of both ORP and PH over just under a minute, before returning the average. I then averaged this further within OH to get something approaching a steady state.
Now it tracks changes without most of the noise, although several months in the PH probe is overreading suggesting it needs a clean.
The ORP probe has been very useful to stop the Chlorine spiking when the cover has been in place for several days.