I’m so sorry – I should have checked the code before commenting. I was sure I implemented it otherwise.
You are right, currently both read
and write
are with relative address.
You have good point regarding entity numbers, perhaps we need to require the user to understand the minimum basics of modbus addressing.
I have logging on debug and there’s a lot of lines regarding transformations.
…
To me it seems wasted processing time.
Yeah there is some additional work going on currently, something I might optimize later once the thing structure and other things “stabilize”. Due to the nature of channels in openhab2, it might be hard though. I should probably have logic not to process the channel transformation on read if the channel is not linked in neither read
and readwrite
things.
For example is it possible to combine connection and poller definitions as one thing?
I have been thinking this as well but it does not really reduce the number of things considerably. I think typically people have only single connection?
And according to thing configuration it would create channels?
I have not found other way except to provide “all” channels (dimmer, number, switch, contact, etc.), such that user can link it to the item of matching type. From modbus configuration alone it is impossible to say which types are necessary?
Naturally we could decide that only Number
items are supported (-> only one channel) but this would mean that user needs to introduce additional rules and proxy items to convert the number to Contact
, for example. I have been trying to avoid additional rules & proxy items for the typical use cases.
Alternative idea for the thing structure
I have alternative proposal that perhaps could solve the “too many things” issue.
First a bit of history: currently the thing structure is quite one-to-one mapping with openhab1 binding.
For example, the extended item configuration string format in the old binding maps to single readwrite
, with read
and write
things as children. The read
things are <
definitions in the old binding, Analogously, write
things match to >
definitions.
Since we want to support multiple <
(read) or >
(write) definitions (to support e.g. the Rollershutter example above) it was natural to have multiple things in the new binding as well. Having separate things has the benefit of not introducing a new “mini-language” for configuration strings like in openHAB1. Initially for me the <
and >
looked a bit cryptic initially but I decided to go with those in the old binding as other bindings used similar syntax as well (http, mqtt).
Instead of thinking how it was with the old binding, I have been trying to think the common (?) use cases with the modbus binding, and use that information as basis of designing the binding from scratch.
Known use cases
- Same read/write address, same value type (e.g. reading/writing integer to coil/holding register) – the most common use case for sure
- “Set coil to true on any command”, writing to different address than reading, write transformed differently than read. example in old binding
- read & write different addresses. In new binding must point to same endpoint, though.
- Rollershutter example, mix-match of other use cases
- converting different commands to different register values (ON=256, OFF=512)
- writing an openHAB command to multiple registers (e.g. turning many devices off)
- inverting values on read/write
(anything else?)
As you were already thinking, my proposal is to have single “readwrite” thing (EDIT: this is now called data thing in the latest version. Also check the docs for descriptions of parameters), with the following configuration parameters
readAddress
Can be empty for write-only.readTransform
readValueType
writeType
writeAddress
(absolute address, withwriteType=holding
,writeAddress=0
would refer to holding register #40001 for example). Keep empty for read-only.writeTransform
writeValueType
writeMultipleEvenWithSingleRegister
Essentially you would have the current thing configurations from read
and write
things, with the exception of missing trigger
. Transformation would take the role of trigger.
I realized that the need to have multiple read
(<
in openhab1) or write
(>
in openhab2) can be worked around by using transformations. You can branch the logic based on incoming command, same as with trigger
parameter in openHAB1
. This would not solve the issue of writing to different register/coil based on command (e.g. roller shutter example). I have proposal below to solve that using “general purpose write”.
This is how the above use cases would be solved
readAddress
equalswriteAddress
,readValueType
equalswriteValueType
- transformation returning always 1
- just configure read index and write index
- more advanced case – would be solved by “mini binding” or “general purpose write” approaches
- solvable e.g. using JS transformation and
switch-case
- more advanced case – would be solved by “general purpose write” approaches
- solvable by JS transformation
general purpose write For complex scenarios we could allow complex output from transformation.
We could represent the raw modbus writes using the JSON syntax:
[ {... write instruction ...}, {... write instruction ...}, ... ]
where each {... write instruction ...}
is a JSON object describing a modbus write request.
For example, if the transformation returns the following JSON
[
{"functionCode":5, "index":0, "value":1},
{"functionCode":6, "index":0, "value":256},
{"functionCode":16, "index":1, "value":[512, 256]}
]
EDIT: the JSON keys turned slightly different in the final implementation: functionCode, address, and value.
the binding would execute following modbus write requests
- set coil 0 to on, using FC5 (write single coil)
- set holding register 0 to 256, using FC6 (write single holding register)
- set holding register 1 and 2 to 16-bit values 512, 256, respectively, using FC16 (write multiple holding registers), in a single modbus request.
One can also suppress all writes using empty json list []
.
We SHOULD support simple transformations as well (outputting just value), and leave the JSON syntax for advanced cases only (assuming the advanced cases are much more rare).
mini bindings Provide special use-cases as mini bindings
For example, we could introduce rollershutter binding (built on top modbus binding) which would introduce rollershutter-readwrite
thing with the following configuration
up_down_write_index
: in what (coil/holding) index to write UP and DOWN commandsup_down_type
: coil or holding. What FC is used to write UP/DOWNup_value
: value to write with UP, e.g. 1down_value
: value to write with DOWN, e.g. -1move_stop_write_index
: in what (coil/holding) index to write MOVE and STOP commandsmove_stop_write_type
: coil or holding. What FC is used to write MOVE/STOPmove_value
: value to write with UP, e.g. 1stop_value
: value to write with DOWN, e.g. 0position_index
: from what index position is read
There are other ways to encode rollershutter using modbus. These different versions can be incorporated in the binding as the need arises.
Note that the kind of rollershutter mini binding could be implemented also with “general purpose write” described above.
more examples
“Simple case” (use case 0)
Bridge modbus:tcp:endpointTCP [ host="192.168.1.100", port=502, id=2 ] {
Bridge poller coils [ start=2, length=4, refresh=5000, type="coil" ] {
Bridge readwrite DO2 {
Thing read readTCP [ start=0, valueType="bit" ]
Thing write writeTCP [ start=0, valueType="bit" ]
}
Bridge readwrite DO3 {
Thing read readTCP [ start=1, valueType="bit" ]
Thing write writeTCP [ start=1, valueType="bit" ]
}
}
Bridge poller holding [ start=0, length=5, refresh=5000, type="holding" ] {
Bridge readwrite modbusMakuuhuone4 {
Thing read mh4 [ start=0, transform="default", trigger="*", valueType="uint16" ]
}
Bridge readwrite modbusPukuhuone {
Thing read ph [ start=2, transform="default", trigger="*", valueType="uint16" ]
}
Bridge readwrite modbusMakuuhuone1 {
Thing read mh1 [ start=4, transform="default", trigger="*", valueType="uint16" ]
}
}
}
would transform to roughly something like below
Bridge modbus:tcp:endpointTCP [ host="192.168.1.100", port=502, id=2 ] {
Bridge poller coils [ start=2, length=4, refresh=5000, type="coil" ] {
Bridge readwrite DO2 [ readAddress=2, readValueType="bit", readType="coil", writeAddress=2, writeValueType="bit", writeType="coil" ]
Bridge readwrite DO3 [ readAddress=3, readValueType="bit", readType="coil", writeAddress=3, writeValueType="bit", writeType="coil" ]
}
Bridge poller holding [ start=0, length=5, refresh=5000, type="holding" ] {
Bridge readwrite modbusMakuuhuone4 [ readAddress=0, readValueType="uint16" ]
Bridge readwrite modbusPukuhuone [ readAddress=2, readValueType="uint16" ]
Bridge readwrite modbusMakuuhuone1 [ readAddress=4, readValueType="uint16" ]
}
}
writing an openHAB command to multiple registers (use case 5)
In openhab1 binding
Switch Light "Roller1" (ALL)
{modbus=">[slave2:0:trigger=UP,transformation=256],>[slave2:0:trigger=STOP,transformation=512],>[slave2:0:trigger=DOWN,transformation=512],>[slave2:1:trigger=UP,transformation=512],>[slave2:1:trigger=STOP,transformation=512],>[slave2:1:trigger=DOWN,transformation=256]"}
(plus some modbus.cfg
)
With the new proposed config:
Bridge modbus:tcp:endpointTCP [ host="192.168.1.100", port=502, id=2 ] {
Bridge poller coils [ start=0, length=4, refresh=0, type="holding" ] { // refresh=0 -> no polling
Bridge readwrite Roller1 [ writeTransform="JS(myroller.js)", writeValueType="uint", writeType="holding" ]
}
}
with the following transformation myroller.js
(assuming we are writing holding registers)
// Wrap everything in a function
(function(cmd) {
var cmdToValue = {"UP":256, "STOP":512, "DOWN":512, "UP":512, "STOP":512, "DOWN":256};
var cmdToAddress = {"UP":0, "STOP":0, "DOWN":0, "UP":1, "STOP":1, "DOWN":1};
var value = cmdToValue[cmd];
var address = cmdToAddress[cmd];
if(value === undefined || address === undefined) {
// unknown command, do not write anything
return "[]";
} else {
return [
"[",
"{\"functionCode\":6, \"index\":" + address.toString() + ", \"value\":" + value + "}",
"]",
].join("\n")
}
})(input)
// input variable contains data passed by openhab
The transformation transforms UP
to
[ {"functionCode":6, "index":1, "value":512} ]
and DOWN
to
[ {"functionCode":6, "index":1, "value":256} ]
Hopefully this makes sense, it’s always a bit hard to put word to early ideas. I think the above approach would be much more streamlined way of configuring the binding for simple and typical use cases but still allow enough flexibility for some of the “harder” cases.
We could also get rid of the child->parent thing interaction (e.g. read
things affect channels of readwrite
things), which has felt like a wrong design choice. It seems somehow counterintuitive.
Best
Sami