TLDR Version: I want to calculate a ‘Circular Mean’ Wind Direction value from a series of individual data points persisted over the last 10 minutes. What is the most easiest way of calculating that in OpenHAB, and if scripting is required, I would prefer UI based JS script.
I have a draft version, which gives the illusion of working, but I still have some doubts… Details below.
After replacing my old weather station, I noticed that one of my rules to close the Vents on the greenhouse, was frequently leaving the North facing vents open, when a strong wind from that general direction was occurring.
I eventually put the clues together, and realised that the old weather station wind-vane probably had a stiff bearing, and didn’t react to wind shifts as quickly (So less directional variation), and that also my greenhouse vent rule was using just an average of the last 10 minutes of the wind direction, which can produce incorrect outcomes when the wind directions are above & below zero (North).
So after a bit of googling, I found that I needed to be using a Circular Mean for this value, and also realised looking at the formula’s, that I probably slept through that class at high-school !!
I see that this question was kind-of asked before, but the solution was more targeted at concurrency of speed & direction measurements, and the actual calculation of the Circular Mean was not addressed…
Anyway, fast forward a bit, with some help from Chat-GPT (Who apparently slept through JavaScript classes, and kept on insisting it ‘now had the right answer’, many times over!!), I ended up taking the core of the calculation if provided, and made it work without throwing errors in JS (Via Main UI).
// UI rule script – ECMAScript 2021
var SOURCE_ITEM = "WeatherStation_WindDirection";
var TARGET_ITEM = "WeatherStation_WindDirection_Mean";
var MINUTES = 10;
// Get the source item
var item = items.getItem(SOURCE_ITEM);
// Java time API
var ZonedDateTime = Java.type("java.time.ZonedDateTime");
// Time window for averaging
var now = ZonedDateTime.now();
var startTime = now.minusMinutes(MINUTES);
// Read historic states from InfluxDB
var entries = item.persistence.getAllStatesBetween(startTime, now)
// Check that we actually received data
if (!entries || entries.length === 0) {
console.log("Circular mean: no persisted data available in InfluxDB");
} else {
// Convert degrees → radians
var radians = entries
.map(e => parseFloat(e.state))
.filter(v => !isNaN(v))
.map(v => v * Math.PI / 180);
// Summed sine and cosine components
var sinSum = radians.reduce((a, r) => a + Math.sin(r), 0);
var cosSum = radians.reduce((a, r) => a + Math.cos(r), 0);
// Circular mean calculation
var meanRad = Math.atan2(sinSum, cosSum);
// Convert back to degrees
let meanDeg = meanRad * 180 / Math.PI;
if (meanDeg < 0) meanDeg += 360;
// Update the target item
items.getItem(TARGET_ITEM).postUpdate(meanDeg);
// Log
//console.log(
// "Circular mean wind direction = " +
// meanDeg.toFixed(1) +
// "° (from " + entries.length + " InfluxDB samples)"
//);
var avgWindDirection = Math.round((items.WeatherStation_WindDirection.persistence.averageSince(time.ZonedDateTime.now().minusMinutes(MINUTES))).numericState);
items.getItem("WeatherStation_WindDirection_Avg").postUpdate(avgWindDirection);
}
This is just a test script, which takes the wind-direction, and then calculates the result using just an average (As per my current vent rule), and also a Circular Mean, and then I have all 3 items persisted so I can analyse the results….
Or over a slightly longer interval (and less granularity):
Although for this second graph, I suspect I am comparing values averaged by Grafana, with my calculated values (5m interval, with the first graph at 5s), so it may be a bit misleading.
So, at face value, it appears to be working OK, and both calculated values track closely when the wind is predominantly from a Southern direction (180deg), and perhaps diverge as expected, when the wind direction spans either side of North.
But I only make this post, as I do still have a few doubts about the validity of the formula used - Perhaps if we get a few more days of a predominant Northerly wind, it will answer my question as well (Where the Mean stays around either side of 0/360 deg, rather than averaging to 180.
But the other part, is I am wondering if the generated formula is unnecessarily complex (given its lineage) with a much easier/cleaner way of doing it eluding me.
Any feedback welcome/appreciated, before I move this test code into my main Greenhouse vent automation script…



