From the example in the page of the ModBus Binding
Thing data rollershutterData [ readStart="0", readValueType="int16", writeTransform="JS(rollershutter.js)" ]
and the transformation service:
// Wrap everything in a function
// variable "input" contains data passed by openHAB
(function(cmd) {
var cmdToValue = {"UP": 1, "DOWN": -1, "MOVE": 1, "STOP": 0};
var cmdToAddress = {"UP": 1, "DOWN": 1, "MOVE": 2, "STOP": 2};
var value = cmdToValue[cmd];
var address = cmdToAddress[cmd];
if(value === undefined || address === undefined) {
// unknown command, do not write anything
return "[]";
} else {
return (
"["
+ "{\"functionCode\": 6, \"address\":" + address.toString() + ", \"value\": [" + value + "] }"
+ "]"
);
}
})(input)
Now, the code is pretty straight forward but I’d like to generalize it…
Is there a way to not create a JS for every rollershutter and get the Thing that originate the call to return back the correct address??
Generalize it how? It already looks pretty general to me. I don’t see anything that is apparently specific to a given Thing in the code. I don’t know modbus so maybe I’m missing something.
Anyway, the answer to your question is no. All you can get in the transform is the command, not the name of the Item or Thing ID that it’s connected to.
It reads the position from the address 0 and write the command to the address 1 or 2…
So the second rollershutter reads from 3 and write to 4 and 5…
If I have 40 rollershutter this means that every thing needs a js transformation script that should to be personalized and linked to the thing that call it… generalized in sense that only one script can handle every rollershutter thing…
I don’t know this binding very well, but often when you encounter a problem like this it can be solved using Proxy Items and Rules intstead of 40 copies of the same JavaScript transform.
You’re correct. In order to invoke the advanced feature of processing commands into data targetting different addresses, you would need to use different JS scripts. There’s no way to provide an offset based on read address, for example.
Bear in mind this binding happily reads and writes to different registers, or to different register types.
It’s just the oddball requirement to split a single command out to one or more of several different target registers that has this restriction.
There are a lot of oddball Modbus devices of course, but I’d try to find something a bit more standard before buying forty.
An alternative approach is to set up fixed Modbus channels for each target, each linked to an Item. If you use techniques like Groups and ‘Associated Items’ you could write one rule to analyse commands and pass to one of the associated Items from many similar sets of Items representing one shutter.
Unfortunately the PLC and interface are already installed (there is a little SCADA system that works on top of that, but only on windows pc and pretty closed as ecosystem).
The difficulties here is that the roller shutter module has It’s own firmware (that can work also without the PLC in case of failure) and what this module exposes are an actual position, a requested position and a move stop that I can read as variable thru the PLC modbus.
I think I will go with the personalized JS (maybe another python script )
Is there a way to create a subfolder inside the transform folder?? And how to call a script?? Something like
I understand your problem. It’s like a roller with three push button controls Up/Down/Stop. Modbus needs to write to one of three different binary outputs (coils) to “push” the buttons.
Some PLC programming will allow you to mirror discrete coils into bits in a register. Is this option available to you? It simplifies the task to one write address, allowing for JS transform.
Although - you say you can request a position. That’s a percentage I guess? And directed to a holding register, rather than coils?
I outlined how you can manage this using Items and rules (and no JS)
It might be worth showing detail for this
modbus.things
// poller only needs to poll position
Bridge poller s22 [ start=400, length=2, refresh=1000, type="input" ] {
// read only roller position
Thing data s22position [ readStart="400", readValueType="int16"]
// write only target position
Thing data s22target [ writeStart="500", writeValueType="int16", writeType="holding" ]
// write only buttons
Thing data s22buttUp [ writeStart="5", writeValueType="bit", writeType="coil" ]
Thing data s22buttDown [ writeStart="6", writeValueType="bit", writeType="coil" ]
Thing data s22buttStop [ writeStart="7", writeValueType="bit", writeType="coil" ]
}
roller.items
// put all rollers in this group
Group gRollers
// put all buttons and targets in this group
Group gRegisters
// master Item, put on sitemap
RollerShutter Shutter22 "shutter 22 [%d]" (gRollers) { channel="modbus:data:slave22:s22:s22position:rollershutter", autoupdate="false" }
// for writing target position
Number Shutter22_target (gRegisters) { channel="modbus:data:slave22:s22:s22target:number"}
// for pushing buttons
// expire un-pushes after one second
Switch Shutter22_Up (gRegisters) {channel="modbus:data:slave22:s22:s22buttUp:switch", expire="1s, command=OFF"}
Switch Shutter22_Down (gRegisters) {channel="modbus:data:slave22:s22:s22buttDown:switch", expire="1s, command=OFF"}
Switch Shutter22_Stop (gRegisters) {channel="modbus:data:slave22:s22:s22buttStop:switch", expire="1s, command=OFF"}
// next roller
RollerShutter Shutter23 "shutter 23 [%d]" (gRollers) { ... //etc
shutter.rules
// one rule does all shutters
rule "rollershutter command process"
when
Member of gRollers received command
then
var basename = triggeringItem.name // get name "Rollerxx"
if (receivedCommand == UP) {
// fetch button Item from group
val button = gRegisters.members.filter[dt|dt.name == basename + "_Up"].head
button.sendCommand(ON)
} else if (receivedCommand == DOWN) {
val button = gRegisters.members.filter[dt|dt.name == basename + "_Down"].head
button.sendCommand(ON)
} else if (receivedCommand == STOP) {
val button = gRegisters.members.filter[dt|dt.name == basename + "_Stop"].head
button.sendCommand(ON)
} else {
// else its a numeric position command
val button = gRegisters.members.filter[dt|dt.name == basename + "_target"].head
button.sendCommand(receivedCommand.toString)
}
end