Mqtt transformationPattern no longer working in OH4

I was using some JS transformation patterns in OH3. They are no longer working in OH4 (I already found out I had to install the javascript add-on)
I also tried to convert them to DSL syntax, no luck either. Who can help me?

Things

Thing mqtt:topic:mybroker:airquality_thing_portableco2_3 "AirQuality PortableCO2 3" (mqtt:broker:mybroker) @ "Sensors" {
  Channels:
    Type number: co2 [stateTopic="/airquality/portableco2_3/sensor/co2", unit="ppm", transformationPattern="JS:roundTo10.js"]

roundTo10.js

(function(i) {
        return Math.round(parseFloat(i) / 10) * 10;
})(input)

The error I get:

2023-08-20 22:37:43.119 [WARN ] [transport.mqtt.internal.Subscription] - A subscriber of type 'class org.openhab.binding.mqtt.generic.ChannelState' failed to process message '34392E3436' to topic '/airquality/portableco2_3/sensor/co2'.

With the DSL engine, I get another error:
Things

Thing mqtt:topic:mybroker:airquality_thing_portableco2_3 "AirQuality PortableCO2 3" (mqtt:broker:mybroker) @ "Sensors" {
  Channels:
    Type number: co2 [stateTopic="/airquality/portableco2_3/sensor/co2", unit="ppm", transformationPattern="DSL:roundTo10.dsl"]

roundTo10.dsl

// just something to try out
input * 10

The error I get:

2023-08-20 22:37:52.290 [WARN ] [t.generic.ChannelStateTransformation] - Executing the DSL-transformation failed: Failed to execute script.

I’ve no clue on how to debug this further, anyone has a hint?

Does it work if you try:

(function(i) {
        return (Math.round(parseFloat(i) / 10) * 10).toString();
})(input)

Rounding to 10 in a JRuby script:

roundto10.rb:

input.to_f.round(-1)

Or with an inline syntax:

    Type number: co2 [stateTopic="/airquality/portableco2_3/sensor/co2", unit="ppm", transformationPattern="RB:|input.to_f.round(-1)"]

You could of course also do (input.to_f / 10).round * 10 if you prefer

Found the solution, at least for the JS part.
Seems I was missing the directory /etc/openhab/automation/js/modules and openhab did not have permissions to create it. With that directory, the JS transformation is working again.
The DSL one is still giving issues, not sure why.

input in a transformation is always a String. It has to be converted to a number before you can do math with it.

And for clarity on @JustinG’s code, the transformation is expected to return a String as well.

I was actually curious if it works both with the toString() and without. According to the docs:

The result of the transformation is provided by the script as its return value. It can be null , a string , or a value of a type that properly implements .toString() .

However, it seems to me that we’ve been seeing several instances lately where even some values that implement toString() have been failing. I was wondering if the docs are now out of date and the value must be a string or if something is going wrong in the JS transform.

I wonder if String(Math.round(parseFloat(i) / 10) * 10) which might be more appropriate. I don’t know that numbers in JavaScript have toString() now that you mention it. Number does but I don’t know when a value is a Number or not, if ever.

The input seems to be the main culprit. If you don’t parse that properly, you’ll run into trouble. The JS will print an error that you can learn something from, but the DSL just says “error”. If you don’t return a string, that does not seem to matter, it gets casted.

I now also have it working with this DSL script:

var output = Math.round(Double.parseDouble(input) / 10) * 10

output.toString

I must say this is pretty unobvious, as the channel is of type “Number”

But thanks for the help!

Perhaps but it’s completely consistent. In a transform, the input is always a String. I’m pretty sure that’s in the docs (if not it should be added).

The transform is correct on the java side. It calls object_returned_from_scripting_engine.toString() inside java. It works fine in jruby, for example. A jruby transformation script can return a numeric without having to convert it to a string.

From the perspective of a transformation with a scripting language, yes I agree. However, bear in mind that transformations can also be done using other methods, e.g. JSONPATH, MAP, REGEX, etc. All of those only deal with Strings. They won’t be able to deal with and differentiate between Number (DecimalType), or QuantityType, or OnOffType, or UpDownType, etc. So everything going through the openhab transformation service must be in a String, both going into the transformer, and coming out of the transformer.

This fact is documented in the Script Transformation documentation. The script transformation tries to be helpful, in that it will try to convert things to string for you, even if you returned a non string object.

This works well in a JRuby scripting for example. I can return a numeric, even a QuantityType, UpDownType, or almost any other type, and openhab will convert it to a string. See my JRuby transformation code example above. It just doesn’t seem to work as well in other scripting languages but that’s not the fault of the transformation service.

Another thing to consider is transformations can be used to change the type of the Item. For example, it could convert a Number Channel to a Switch Item. To handle that sort of thing consistently the input and output of the transformations need to be normalized.