Played around with it a bit and I have in-memory persistence enabled for this Item but cut-and-paste your script and I get error logs for the return statement and can’t resolve item.this_item.numericState.
Yes, entries you add in the Settings/Transformations menu will become available as a drop down item in the “Thing to Item Transformation” box.
I am experimenting with a script there now myself, but I am running into a brickwall using the private cache as the log is showing an error “java.lang.IllegalStateException: The Context is already closed” when the script runs.
For my example I am trying to store 10 previous values.
You have to replace “this_item” with the name of your item. For a temperature we can even make it a little simpler as we don’t have the problem when the value suddenly drops to constantly zero (like e.g. every production). So just paste the following (corrects another error too) and tell me if it works:
I’ll try that; thanks. Wasn’t sure what the return statement was doing in the original inline!
I was hoping (unnaturally) that the script environment could figure out what Item it was injected from using a “this” construct. That would make the cut-and-paste across 20 Items less error-prone. Silly me.
BTW Is there a debugging environment or sandbox for scripts? Seems like I’m not the only one trying to figure them out.
tjs.js isn’t the UID of the transformation. That would only be the case if this was the name of a file under $OH_CONF/transformations. The UID of that transformation is, according to the screen\shot, is config:359fd1d97d.
When creating an Item, rule, transformation or anything else, you should always repalce the randomly generated UID with something meaningful.
Caution must be used when using the cache from a transformation. The private cache will be private to that one transformation, but it will not be private to each use of that one transformation. In short, the same instance of the transformation is reused so you need to make sure the transformation and the information placed into the cache uniquely identifies the information it stores there as necessary so that using the transformation in one place doesn’t overwrite the data stored in the cache from a usage in another place.
You really should use a transformation defined under Settings → Transformations then.
The Developer Sidebar has a link to -Scratchpad- where you can write and run arbitrary code for testing things out and experimenting.
Now I can move on to trying to quiet down the hardware. Agree that a moving average would be a great addition and it sounds like there may be something in the works.
FWIW Rich, you’re right about the uid. Thanks. Would never have figured out
JS(config:js:359fd1d97d)
is the right way to invoke a Transform. Now I see the Transforms are in a dropdown menu for the Item scripts, but not in the Modbus Binding.
I’ve had more of an experiment with the hint from Rich and come up with a working solution. I’ve created a transformation that also uses an argument that allows you to differentiate each channel link that might be calling the transformation.
In my case I created a JS transformation with the id number_lpf
In the channel link “Thing to Item Transformation” field, I am calling the transformation with:
config:js:number_lpf?calling_id=item_name
calling_id is passed into the script, and then accessible inside the script as a string (ie calling_id will be ‘item_name’). It makes sense to use the linked item as a reference, so as to give it a unique key.
This script then uses a rolling list of the last 10 values and gives the average as a return value.
(function(data) {
var count, points, num_of_points, sum, cached_values, cached_values_key;
//calling_id is passed into script through channel link config:js:number_lpf?calling_id=xxxxx where xxxxx is a string (no single quotes)
cached_values_key = calling_id.concat('_cached')
num_of_points = 10;
sum = 0;
if (cache.private.exists(cached_values_key) === true) {
cached_values = cache.private.get(cached_values_key);
count = cached_values[0];
points = cached_values[1];
}
else {
count = 0;
points = new Array(num_of_points);
for (let i = 0; i < num_of_points; i++) {
points[i] = 0;
}
}
points[count] = data;
for (let i = 0; i < num_of_points; i++) {
sum = sum + parseFloat(points[i]);
}
count = (count + 1) % num_of_points;
cached_values = [count,points];
cache.private.put(cached_values_key, cached_values);
//console.log('sum = '.concat(sum) , 'num_of_points = '.concat(num_of_points) )
var returnValue = sum/num_of_points;
return returnValue
})(input)
I also found the source of the “java.lang.IllegalStateException: The Context is already closed” error. When you edit and update the script, it does not like to reuse the previous private cache key, so for each change to the script I had to change calling_id.concat(‘_cached’) to cached0, cached1 etc just to create a new key.
Others will have to chime in why this is the case, and how keys are locked to specific scripts / instances etc. It’s beyond my paygrade.
Going back to the original question. In my view you shall not be using any kind of persistence service or complicated averaging functions to reduce jitter on measurements from digital sensor. That is too complicated and resource hungry and also not fit for purpose.
Exactly for this purpuse you can use 1. degree numerical filter the will do what you want to achieve = remove the measurement noice and get smoth measurements of your variable.
All you need in your script are two variables:
Raw measuement from your sensor = X_RAW
Your last prior measurement (the value stored in your item) = X_PREV
Filter it by this formula:
X_NEW = X_PREV + (X_RAW - X_PREV) / alpha
”alpha” is your filtration coeficient that you need to set depending on your needs (sensitivity to change of input measurement) and depending on frequency of your input reading. E.g. for temperature and humidity measurements, that I read once per second, I apply alpha=60, which means that it is effectivelly spreading all changes in measurements over 1 minute.
I usually apply also rounding to the output values before updating value in the item, e.g. Math.round(X_NEW*100)/100. It is not necessary, but helps with the float values stored in the presistence to have reasonable number of decimal digits. Here you need to be careful to not round too much when your your aplha coeficient very high. Othervise all the granular changes from the filter will be removed by the rounding and your measurements will not change (or change only on very large input raw value change).
I often also check for the raw input value and if it is completly out of range, I ignore that single measurement. That helped me a lot with few unsual senors that were time to time sending bogus data.
Where I have the ability, I have this type of filter in place directly in PLC or controller (e.g. Arduino) for the sensor I use. Especially popular cheap digital sensors connected via I2C or 1wire tend to have issues with jitter. For analog measurements it is usually less critical, depending on the convertor used. But I have applied it even to few items directly in my OH that I’m unable to fix directecly in the controller of the sensor.
Thanks! You’ve inspired me to go back and look at a little bit of EE filter theory. We’re all talking about flavors of Exponential Moving Average that don’t need all the baggage of carrying more than a single state variable. Your format using a delta-x term is interesting and I have to look at how the convergence compares to using a simple weight on the x-raw term.
Moving on to looking at other aspects of Modbus integration and how to push values into formats the Modbus registers on the device want to see.
If anyone is interested I’ll post the results. There’s a bit more fiddling around for writing than I expected.