Filter Profile

This comes up periodically so I figured I’d write a quick tutorial since I had to implement this temporarily in my OH instance to debug a problem. This will probably become a design pattern at some point.

Problem Statement

Some sensors report really frequently, let’s say every second, and each update has a tiny change. For example, a temperature sensor reports values with changes of 0.01 or smaller with each update. For persistence you can filter changes like these out so they don’t fill up your database but that’s not the only place it can cause problems. If you’ve rules that trigger on this Item for every change, this rule is going to be triggered a lot unnecessarily.

Solution

Apply a transform profile at the link that only allows changes that are larger than a threshold to pass from the Thing to the Item. This will keep the Item from changing until the change is significant.

(function(data, id, threshold) {
  // If threshold doesn't have a unit, ONE will be assumed
  // For now, unit ONE doesn't print so the number will be returned without units
  console.loggerName = 'org.openhab.automation.transformation.filter.'+id;
  console.debug('Data: ' + data + ' Threshold: ' + threshold);
  const thresh = Quantity(threshold);
  const curr = Quantity(data);
  const last = cache.private.get(id, () => curr);
  const delta = curr.subtract(last);
  if(delta.greaterThanOrEqual(threshold) || delta.lessThanOrEqual(thresh.multiply(-1))) {
    console.debug('Threshold exceeded, updating item with ' + curr);
    cache.private.put(id, curr);
    return data;
  };
  console.debug('Delta too small at ' + delta);
  return last;
})(input, id, threshold)

Above is a JS transformation. It calculates the difference between the last reported value and the current value and it only forwards the new value if the difference is above a given threshold.

It uses Quantity to do the calculation but if no units are passed in Quantity will default to ONE as the unit. The calculations will also result in ONE as the unit and since ONE doesn’t print the result should be the same as if there were no units used in the first place. Therefore it should work both with and without units, though I’ve not tested it without units.

You will notice there are a couple of extra variables passed in to the transformation. This relies on the feature of script transforms where arguments can be passed to the transform using URL encoding.

I have this defined through the UI as a transform named “Filter”. A couple of examples:

config:js:filter?id=JennsOfficeTemp&threshold=0.5+°F
config:js:filter?id=MainFloorHumidity&threshold=0.5+%25

The id provides a unique ID to this rule which is used by the logger and to keep the values stored in the cache separate. Even when applied to multiple Items, the same instance of the transform is reused across them all. Without the id the values from one use will override the values from the others every time it runs. The id keeps the last values stored in the cache separate so make sure to use a unique value (the name of the Item makes a good choice).

The threshold is how much the value must have changed since the last reported change before the new value is returned. The units of the threshold should be compatible with the unit reported by the Channel.

Notice the space and 5 are encoded with URL encoding above. It might work to just \ escape them or use %% but I only tried this approach.

If the change isn’t large enough, the previously reported value is reported. So the Item will still get all the updates, but they won’t change as frequently. Change the return last; to return null; if you dont’ want the updates.

The same overall approach could be used to filter the values on time instead of a threshold. Instead of testing for the delta between the current and last reported value, check the amount of time since the last reported value.

10 Likes

I have a couple of zWave items (floor thermostat), they sometimes return unusual results, e.g. factor 10 or 100 too high or low. So I can use your transformation to do the opposite. Update items only when they are in a similar range from the last value.
But I have only file based items and I don’t know where to add the config.

Here’s an example of a items which I want to define with a filter.

Number    zWaveToiletMeterkWh
          "Heizung kWh [%.2f kWh]"
          (gPersist)
          {channel="zwave:device:5001237820:node18:meter_kwh"}

See Items | openHAB. It’s used as a standard transform profile.

I have a thing whose channels are defined as

      stateTransformation: JS:getEcoforestStat.js

Is it possible to pass a 2nd parameter as you are doing in the first example ?

Yes but I’m not 100% certain on the syntax for the HTTP/MQTT add-ons. They follow a slightly different syntax. But I suspect it’s the same though.

stateTransformation: JS:getEcoforestStat.js?argument1=value1&argument2=value2

See JavaScript Scripting - Automation | openHAB

1 Like