Syntax for Binding Transformations

Once this is done, hopefully, all binding transformations will:

  • Use core’s ChannelTransformation class to perform transformations, instead of using separate in-binding implementations
  • Support both colon JSONPATH:$.path and parentheses JSONPATH($.path) syntax for specifying transformations. The idea is that new users can just use the parantheses syntax everywhere, as documented and used in the core (for e.g. item labels). So this eliminates confusion of whether to use colon or parentheses.
  • Support transformation chaining by listing each transformation on a separate line in the UI, or as a comma separated list in .things file. The old intersection symbol is still supported and can be mixed with the multi-line / list stype. Every time I needed to chain transformations, I had to hunt around for where I can copy the symbol from. I won’t have to look at an intersection symbol again!
  • Support comments in the transformation list. This is especially for UI users. PR (merged)

Chaining in the UI:

Chaining in the UI Code Tab:

Chaining in a .things file

Thing exec:command:filetest [
  command="ls -l /",
  transform="RB(|logger.info 'one'; input)", "RB(|logger.info 'two'; input)",
  timeout=5
]

Chaining in a JRuby script

things.build do
  thing "exec:command:test",
        config: {
          command: "/bin/sh -c 'ls -al /*'",
          transform: ["RB(|logger.info 'hello'; input)", "RB(|logger.info 'second'; input)"]
        }
end

I’ve submitted these PRs related to transformations. I’m hoping that this makes it easier to deal with transformations and avoid future confusions to new users.

Foundations:

I’m working on unifying this on the other bindings.

Binding Status
exec PR (merged)
http PR (merged)
mqtt PR (merged)
modbus PR (merged)
serial PR (merged)
mail PR (merged)
neeo Not applicable. It uses core’s label formatting method REGEX(.*):%s, which doesn’t support chaining. It will be a separate task to implement chaining in this along with the core label.
enocean Not changed. It only supports a single transformation. Its config defines the transformation type and transformation function separately. Refactoring would require deprecating these configuration parameters and introducing a new transformation parameter like the other bindings.

This was inspired by Fix Documentation of Transform Values - specifically Script Transformations by vchrizz · Pull Request #2348 · openhab/openhab-docs · GitHub

5 Likes

:partying_face: I’ve been lightly pushing for this since OH 1.x. Bindings shouldn’t be in the business of implementing transformations themselves.

I’ve always disliked using that special symbol for chaining. I understand why it was chosen but it is hard to type as you describe.

These are great improvements! Thanks for posting.

While you are working on this sort of thing, does this perhaps open some opportunities to support chaining in the transform profile too? Awhile back adding support for that was looked into but it was determined to be too hard to implement. Maybe with chaining now implemented in core support for chaining in the transform profile is more feasible.

Also, what about Item label/state description patterns?

does this apply for the MAP-transformation too, so that a MAP-transformation could be used inline?

I don’t think that’s the same thing. What @JimT’s PRs do is:

  • make it so defining a transform using : in MQTT, HTTP, and a couple other bindings will work everywhere
  • make it so the tranditional way of defining transformations using () instead of : will work everywhere
  • make it so chaining transformations works everywhere

Allowing the definition of an inline Map transformation will require changes to the Map transform add-on, not core.

Having a multiline text area to enter the transformation certainly makes inline mapt transformations more attractive.

Eventually though it would be nice if we could select transformations from a list too similar to what the transform profile allows. But that’s definitely outside of scope for this effort.

I’m not sure how feasible that is to do across all the add-ons.

No, but this has been something I’ve thought of for a long time. Maybe I’ll look into this next.

Indeed

Yes. Although at the moment, one transformation must fit in one line. It cannot span multiple lines.

However, and I doubt it will get accepted, but I’m willing to make the POC for it, with parentheses syntax we could probably make it span multiple lines.

It allows inline script also be more complex, and support multi line map, for example

MAP(|
A=X
B=Y
)
# Chain it with ruby transform
RB   (|
logger.info "hello"
Switch1.on # turn on Switch1
input.downcase
)
# maybe chain it with this too later
# JSONPATH($.device.temperature)
// That's all, folks!

That’s two inline transformations map and RB spanning multiple lines.

This idea is a bit iffy, because a) it can probably be done on the yaml level anyway (and better if it can be done), and b) it makes the list/array weird. It’s only useful for UI input. Not that useful nor practical for, say, .things file

I thought of this too but haven’t investigated it.

1 Like
1 Like

I’ve thought of this but haven’t looked into it yet. It will have to use the old intersection symbol…

I am wondering though if we can require matching parens, to chain without intersection symbols e.g.

Jsonpath($.path) regex(.*) RB(| input.downcase)

It would make parsing much more difficult, so just a thought.

1 Like

right now it can be done using ruby or perhaps javascript, albeit at a different syntax

RB(| { "open" => "ON", "closed" => "OFF" }[input] )

An inline MAP transform would look like this (this currently doesn’t exist, just brainstorming here):

MAP(| open=ON; closed=OFF )

Here using a semicolon as the separator.

See Widget Expressions & Variables | openHAB For one way To do it as a ma in JS. It’s basically the same approach as ruby. Define the map as a dict and select using input.

@Larsen here you go

1 Like

that’s incredible. :+1:
And it’s already merged so I can try with next snapshot?

yes, whenever the addons + distro get rebuilt

@florian-h05 can we do this using some sort of autocomplete on the multiline text input, similar to how it’s done on the code editor autocompletion?

In terms of detecting which field to apply it, we could perhaps just check if the field name contains transform, unless you can think of a better way?

1 Like

I think “proposing” transformations can be done similar to how the Item category/icon and unit autocompletion work, see openhab-webui/bundles/org.openhab.ui/web/src/components/item/item-form.vue at b0d9ad75324355ce10d0af4802cb64e557ddec0a · openhab/openhab-webui · GitHub.
Therefore this shouldn’t even require larger UI changes (in terms of modifying Vue template), it mainly is to implement the logic in JS.

1 Like

I just found [MainUI] Select transformations from list · Issue #1944 · openhab/openhab-webui · GitHub again.
I think we talk about pretty much the same here (with minor differences), should be probably done in one go.

@JimT Since you contributed the SCRIPT profiles: Do you have an idea why the config description for profile:transform:JS does not list the available scripts as options for the stateFromItemScript und commandFromItemScript?

There was never one, afaik, for any of the parameters.

toItemScript and toHandlerScript list the available scripts as options and I haven’t found where this is done yet.

It was hiding in the TransformationService! Add missing ScriptProfile parameter options by jimtng · Pull Request #4360 · openhab/openhab-core · GitHub

1 Like

@Larsen fyi

1 Like