Modbus openHAB2 binding available for alpha testing

@AndrewZ thanks for reporting these, really appreciate the effort!

I tried something to fix the error you pasted, not sure really what’s happening exactly. Please try again with the new version (2017-08-28 16:23 UTC or later)

Also removed the warning message from ModbusWriteThingHandler related to REFRESH command. Is this what you referred to with REFRESH error?

Best

@ssalonen yes, you understand me :wink:
Updated and see no more errors on restart(s). Thank you!

1 Like

Hi @ssalonen,

Thanks for your reply and the fix!
This seems to fix the issue on that setup. I also disabled the echo.

Thanks a lot!

Hi @ssalonen,

While the issue has been fixed on my raspberry pi which is directly connected to the USB -> RS485.
I’m now trying to connect to the RS485 converter from my production machine (Windows machine) via a virtual serial port. It is working with the v1 modbus binding but when using the openHAB2 binding I get the following error(s):

2017-08-31 13:25:15.088 [INFO ] [el.core.internal.ModelRepositoryImpl] - Refreshing model 'modbus.things'
2017-08-31 13:25:15.089 [WARN ] [el.core.internal.ModelRepositoryImpl] - Configuration model 'modbus.things' is either empty or cannot be parsed correctly!
2017-08-31 13:25:15.101 [INFO ] [el.core.internal.ModelRepositoryImpl] - Loading model 'modbus.things'
2017-08-31 13:25:15.110 [ERROR] [ome.core.thing.internal.ThingManager] - Exception occurred while calling thing updated at ThingHandler 'org.openhab.binding.modbus.handler.ModbusSerialThingHandler@56df802f': java.lang.NullPointerException
java.util.concurrent.ExecutionException: java.lang.NullPointerException
	at java.util.concurrent.FutureTask.report(FutureTask.java:122) ~[?:?]
	at java.util.concurrent.FutureTask.get(FutureTask.java:206) ~[?:?]
	at org.eclipse.smarthome.core.common.SafeMethodCaller.callAsynchronous(SafeMethodCaller.java:194) ~[?:?]
	at org.eclipse.smarthome.core.common.SafeMethodCaller.call(SafeMethodCaller.java:83) ~[?:?]
	at org.eclipse.smarthome.core.common.SafeMethodCaller.call(SafeMethodCaller.java:67) ~[?:?]
	at org.eclipse.smarthome.core.thing.internal.ThingManager.thingUpdated(ThingManager.java:524) ~[?:?]
	at org.eclipse.smarthome.core.thing.internal.ThingRegistryImpl.notifyTrackers(ThingRegistryImpl.java:209) ~[?:?]
	at org.eclipse.smarthome.core.thing.internal.ThingRegistryImpl.notifyListenersAboutUpdatedElement(ThingRegistryImpl.java:132) ~[?:?]
	at org.eclipse.smarthome.core.thing.internal.ThingRegistryImpl.notifyListenersAboutUpdatedElement(ThingRegistryImpl.java:1) ~[?:?]
	at org.eclipse.smarthome.core.common.registry.AbstractRegistry.updated(AbstractRegistry.java:181) ~[?:?]
	at org.eclipse.smarthome.core.common.registry.AbstractRegistry.updated(AbstractRegistry.java:1) ~[?:?]
	at org.eclipse.smarthome.core.common.registry.AbstractProvider.notifyListeners(AbstractProvider.java:57) ~[?:?]
	at org.eclipse.smarthome.core.common.registry.AbstractProvider.notifyListenersAboutUpdatedElement(AbstractProvider.java:81) ~[?:?]
	at org.eclipse.smarthome.model.thing.internal.GenericThingProvider.lambda$21(GenericThingProvider.java:1018) ~[?:?]
	at java.util.ArrayList.forEach(ArrayList.java:1249) ~[?:?]
	at org.eclipse.smarthome.model.thing.internal.GenericThingProvider.createThingsFromModelForThingHandlerFactory(GenericThingProvider.java:1027) ~[?:?]
	at org.eclipse.smarthome.model.thing.internal.GenericThingProvider.lambda$3(GenericThingProvider.java:298) ~[?:?]
	at java.lang.Iterable.forEach(Iterable.java:75) [?:?]
	at org.eclipse.smarthome.model.thing.internal.GenericThingProvider.createThingsFromModel(GenericThingProvider.java:300) [133:org.eclipse.smarthome.model.thing:0.9.0.201708041325]
	at org.eclipse.smarthome.model.thing.internal.GenericThingProvider.modelChanged(GenericThingProvider.java:814) [133:org.eclipse.smarthome.model.thing:0.9.0.201708041325]
	at org.eclipse.smarthome.model.core.internal.ModelRepositoryImpl.notifyListeners(ModelRepositoryImpl.java:287) [122:org.eclipse.smarthome.model.core:0.9.0.201708041325]
	at org.eclipse.smarthome.model.core.internal.ModelRepositoryImpl.addOrRefreshModel(ModelRepositoryImpl.java:120) [122:org.eclipse.smarthome.model.core:0.9.0.201708041325]
	at org.eclipse.smarthome.model.core.internal.folder.FolderObserver.checkFile(FolderObserver.java:234) [122:org.eclipse.smarthome.model.core:0.9.0.201708041325]
	at org.eclipse.smarthome.model.core.internal.folder.FolderObserver.processWatchEvent(FolderObserver.java:297) [122:org.eclipse.smarthome.model.core:0.9.0.201708041325]
	at org.eclipse.smarthome.core.service.WatchQueueReader.run(WatchQueueReader.java:204) [98:org.eclipse.smarthome.core:0.9.0.201708041325]
	at java.lang.Thread.run(Thread.java:748) [?:?]
Caused by: java.lang.NullPointerException
	at org.openhab.binding.modbus.handler.ModbusSerialThingHandler.initialize(ModbusSerialThingHandler.java:60) ~[?:?]
	at org.eclipse.smarthome.core.thing.binding.BaseThingHandler.thingUpdated(BaseThingHandler.java:193) ~[?:?]
	at org.eclipse.smarthome.core.thing.internal.ThingManager$6.call(ThingManager.java:528) ~[?:?]
	at org.eclipse.smarthome.core.thing.internal.ThingManager$6.call(ThingManager.java:1) ~[?:?]
	at org.eclipse.smarthome.core.common.SafeMethodCaller$CallableWrapper.call(SafeMethodCaller.java:181) ~[?:?]
	at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[?:?]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) ~[?:?]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) ~[?:?]
	... 1 more
2017-08-31 13:25:15.120 [DEBUG] [handler.ModbusPollerThingHandlerImpl] - Bridge not initialized fully (no endpoint) -- aborting init for org.openhab.binding.modbus.handler.ModbusPollerThingHandlerImpl@16274ee8
2017-08-31 13:25:15.122 [DEBUG] [odbus.handler.ModbusReadThingHandler] - ReadWrite bridge 'Modbus read-write definition' of ReadThing 'Modbus read definition' is offline. Aborting config validation
2017-08-31 13:25:15.122 [DEBUG] [odbus.handler.ModbusReadThingHandler] - ReadWrite bridge 'Modbus read-write definition' of ReadThing 'Modbus read definition' is offline. Aborting config validation
2017-08-31 13:25:15.123 [DEBUG] [odbus.handler.ModbusReadThingHandler] - ReadWrite bridge 'Modbus read-write definition' of ReadThing 'Modbus read definition' is offline. Aborting config validation
2017-08-31 13:25:15.124 [DEBUG] [odbus.handler.ModbusReadThingHandler] - ReadWrite bridge 'Modbus read-write definition' of ReadThing 'Modbus read definition' is offline. Aborting config validation
2017-08-31 13:25:15.124 [DEBUG] [odbus.handler.ModbusReadThingHandler] - ReadWrite bridge 'Modbus read-write definition' of ReadThing 'Modbus read definition' is offline. Aborting config validation
2017-08-31 13:25:15.124 [DEBUG] [odbus.handler.ModbusReadThingHandler] - ReadWrite bridge 'Modbus read-write definition' of ReadThing 'Modbus read definition' is offline. Aborting config validation
2017-08-31 13:25:15.127 [DEBUG] [odbus.handler.ModbusReadThingHandler] - ReadWrite bridge 'Modbus read-write definition' of ReadThing 'Modbus read definition' is offline. Aborting config validation
2017-08-31 13:25:15.129 [DEBUG] [odbus.handler.ModbusReadThingHandler] - ReadWrite bridge 'Modbus read-write definition' of ReadThing 'Modbus read definition' is offline. Aborting config validation
2017-08-31 13:25:15.130 [DEBUG] [odbus.handler.ModbusReadThingHandler] - ReadWrite bridge 'Modbus read-write definition' of ReadThing 'Modbus read definition' is offline. Aborting config validation
2017-08-31 13:25:15.132 [DEBUG] [odbus.handler.ModbusReadThingHandler] - ReadWrite bridge 'Modbus read-write definition' of ReadThing 'Modbus read definition' is offline. Aborting config validation

This is my current test things config:

Bridge modbus:serial:endpoint [ port="COM3", baud=9600, id=11, stopBits="1.0", parity="even",dataBits=8, echo=false, encoding="rtu", flowControlIn="none", flowControlOut="none", receiveTimeoutMillis=1500 ] {
	Bridge poller holding [ start=4000, length=12, refresh=500, type="holding" ] {
		Bridge readwrite Slave1 { 
			Thing read DeviceType [ start=1, transform="default", trigger="*", valueType="int16" ]
			Thing read DeviceVersion [ start=3, transform="default", trigger="*", valueType="int16" ]
			Thing read OutsideTemperature [ start=8, transform="default", trigger="*", valueType="int16" ]
			Thing read IndoorTemperature [ start=9, transform="default", trigger="*", valueType="int16" ]
		}
	}
}

Do you have any idea what the issue could be?
I also used Qmodbus to connect to the virtual serial port and it can read and write successfully.

Thanks!

Greetings,
Frederic

Thanks @FredericMa for reporting this. I see the issue in code and try to have fixed version for you in the near future.

Btw, I noticed that you have many read things inside single readwrite thing. This is technically ok if you are fine with just reading from modbus, but not writing. Also note you need to link the items to the read things. If you would link it to readwrite thing, the value changes a bit randomly as the read things represent quite different things.

If you would like to write the data, you need to introduce readwrite things and have read and write thing inside those. Refer to other examples in this thread for that.

Best
Sami

UPDATE: Should be fixed now. Can you please try @FredericMa with the new version?

Hi @ssalonen! Sounds like Finnish lastname :wink: or am I completely on the wrong tracks here?

Anyhow I’m running latest stable Openhabian on RPi3 and modbus server is siemens S7-1200 and here is my findings so far:

Installing through markeplace gives errors

2017-09-04 18:10:00.444 [WARN ] [ace.internal.BindingExtensionHandler] - Installed bundle, but failed to start it: Could not resolve module: org.openhab.binding.modbus [233]
  Unresolved requirement: Import-Package: org.eclipse.jdt.annotation; resolution:="optional"
  Unresolved requirement: Import-Package: org.openhab.io.transport.modbus
2017-09-04 18:10:00.450 [ExtensionEvent            ] - Extension 'market:binding-3528471' has been installed.
2017-09-04 18:10:11.657 [WARN ] [ace.internal.BindingExtensionHandler] - Installed bundle, but failed to start it: Could not resolve module: org.openhab.io.transport.modbus [234]
  Unresolved requirement: Import-Package: gnu.io
2017-09-04 18:10:11.660 [ExtensionEvent            ] - Extension 'market:binding-3528475' has been installed.

PaperUI does not show anything under binding or services configuration.

Next i dropped the binding and transport .jar files to addons folder. Karaf shows they are installed but nothing in logs nor PaperUI. Restarted openhab -->

2017-09-04 19:10:12.592 [ERROR] [org.openhab.binding.modbus          ] - FrameworkEvent ERROR - org.openhab.binding.modbus
org.osgi.framework.BundleException: Could not resolve module: org.openhab.binding.modbus [235]
  Unresolved requirement: Import-Package: org.eclipse.jdt.annotation; resolution:="optional"
  Unresolved requirement: Import-Package: org.openhab.io.transport.modbus
    -> Export-Package: org.openhab.io.transport.modbus; bundle-symbolic-name="org.openhab.io.transport.modbus"; bundle-version="2.2.0.201709031748"; version="0.0.0"
       org.openhab.io.transport.modbus [236]
         Unresolved requirement: Import-Package: gnu.io
	at org.eclipse.osgi.container.Module.start(Module.java:434)[org.eclipse.osgi-3.10.101.v20150820-1432.jar:]
	at org.eclipse.osgi.container.ModuleContainer$ContainerStartLevel.incStartLevel(ModuleContainer.java:1582)[org.eclipse.osgi-3.10.101.v20150820-1432.jar:]
	at org.eclipse.osgi.container.ModuleContainer$ContainerStartLevel.incStartLevel(ModuleContainer.java:1561)[org.eclipse.osgi-3.10.101.v20150820-1432.jar:]
	at org.eclipse.osgi.container.ModuleContainer$ContainerStartLevel.doContainerStartLevel(ModuleContainer.java:1533)[org.eclipse.osgi-3.10.101.v20150820-1432.jar:]
	at org.eclipse.osgi.container.ModuleContainer$ContainerStartLevel.dispatchEvent(ModuleContainer.java:1476)[org.eclipse.osgi-3.10.101.v20150820-1432.jar:]
	at org.eclipse.osgi.container.ModuleContainer$ContainerStartLevel.dispatchEvent(ModuleContainer.java:1)[org.eclipse.osgi-3.10.101.v20150820-1432.jar:]
	at org.eclipse.osgi.framework.eventmgr.EventManager.dispatchEvent(EventManager.java:230)[org.eclipse.osgi-3.10.101.v20150820-1432.jar:]
	at org.eclipse.osgi.framework.eventmgr.EventManager$EventThread.run(EventManager.java:340)[org.eclipse.osgi-3.10.101.v20150820-1432.jar:]
2017-09-04 19:10:12.611 [ERROR] [org.openhab.io.transport.modbus     ] - FrameworkEvent ERROR - org.openhab.io.transport.modbus
org.osgi.framework.BundleException: Could not resolve module: org.openhab.io.transport.modbus [236]
  Unresolved requirement: Import-Package: gnu.io
	at org.eclipse.osgi.container.Module.start(Module.java:434)[org.eclipse.osgi-3.10.101.v20150820-1432.jar:]
	at org.eclipse.osgi.container.ModuleContainer$ContainerStartLevel.incStartLevel(ModuleContainer.java:1582)[org.eclipse.osgi-3.10.101.v20150820-1432.jar:]
	at org.eclipse.osgi.container.ModuleContainer$ContainerStartLevel.incStartLevel(ModuleContainer.java:1561)[org.eclipse.osgi-3.10.101.v20150820-1432.jar:]
	at org.eclipse.osgi.container.ModuleContainer$ContainerStartLevel.doContainerStartLevel(ModuleContainer.java:1533)[org.eclipse.osgi-3.10.101.v20150820-1432.jar:]
	at org.eclipse.osgi.container.ModuleContainer$ContainerStartLevel.dispatchEvent(ModuleContainer.java:1476)[org.eclipse.osgi-3.10.101.v20150820-1432.jar:]
	at org.eclipse.osgi.container.ModuleContainer$ContainerStartLevel.dispatchEvent(ModuleContainer.java:1)[org.eclipse.osgi-3.10.101.v20150820-1432.jar:]
	at org.eclipse.osgi.framework.eventmgr.EventManager.dispatchEvent(EventManager.java:230)[org.eclipse.osgi-3.10.101.v20150820-1432.jar:]
	at org.eclipse.osgi.framework.eventmgr.EventManager$EventThread.run(EventManager.java:340)[org.eclipse.osgi-3.10.101.v20150820-1432.jar:]

Stupid question. Does this binding create needed configuration files witch can be modified through ESH Desinger?

Hi!

You are getting the error because you are missing the serial feature. Follow these instructions for installing the serial feature.

I’m not sure how the Oh designer works. You can certainly create configurations using the things file (examples in this thread), or alternatively using the paper ui.

Interesting to find fellow s1200 user! I’m using it as well with my production installation.

P.s. Yeah I’m Finnish indeed :slight_smile:

I’ll give it a new try hopefully tomorrow.

Do you have any readme or wiki that i have missed because this was the first time i heard about installing serial feature?

S7 is quite rare in home automation I think. For now its only controlling few valves, gathering temperatures from 3x logo8 and serving all those through modbus. Planning to use it as kwh counter also. But this might be a topic to continue elsewhere.

There is still some documentation gap for this new experimental version, something that definitely needs to be done before proper release.

I think that features might be installed automatically with final binding version, not sure though…

Got your binding to work after installing that serial transport feature. To say the truth configuring through PaperUi was painful so i used only textual config. Here’s my config files if some other tester gets help from them. These are used to flip on/off Siemens S7-1200 digital outputs Q0.2 -> Q0.5 and read holding register. Are they ok or is there something to change?

modbus.things

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=2, valueType="bit", type="coil" ]
            Thing write writeTCP [ start=2, valueType="bit", type="coil" ]
        }
    	Bridge readwrite DO3 { 
            Thing read readTCP [ start=3, valueType="bit", type="coil" ]
            Thing write writeTCP [ start=3, valueType="bit", type="coil" ]
        }
    	Bridge readwrite DO4 { 
            Thing read readTCP [ start=4, valueType="bit", type="coil" ]
            Thing write writeTCP [ start=4, valueType="bit", type="coil" ]
        }
    	Bridge readwrite DO5 { 
            Thing read readTCP [ start=5, valueType="bit", type="coil" ]
            Thing write writeTCP [ start=5, valueType="bit", type="coil" ]
        }
    }
	Bridge poller holding [ start=0, length=5, refresh=5000, type="holding" ] {
        Bridge readwrite modbusMakuuhuone4 { 
            Thing read mh4 [ start=0, transform="default", trigger="*", valueType="uint16", type="holding" ]
        	}
        Bridge readwrite modbusPukuhuone {
        	Thing read ph [ start=2, transform="default", trigger="*", valueType="uint16", type="holding" ]
        	}
        Bridge readwrite modbusMakuuhuone1 {
        	Thing read mh1 [ start=4, transform="default", trigger="*", valueType="uint16", type="holding" ]
        
        }
    }
}

modbus.items

Switch ModbusTest2            "S7 Coil 2"    { channel="modbus:readwrite:endpointTCP:coils:DO2:switch" }
Switch ModbusTest3            "S7 Coil 3"    { channel="modbus:readwrite:endpointTCP:coils:DO3:switch" }
Switch ModbusTest4            "S7 Coil 4"    { channel="modbus:readwrite:endpointTCP:coils:DO4:switch" }
Switch ModbusTest5            "S7 Coil 5"    { channel="modbus:readwrite:endpointTCP:coils:DO5:switch" }

Number modbusMakuuhuone4	 "Makuuhuone 4 [%.1f °C]"	  <office> { channel="modbus:readwrite:endpointTCP:holding:modbusMakuuhuone4:number" }
Number modbusPukuhuone	 	 "Pukuhuone [%.1f °C]"   <temperature> { channel="modbus:readwrite:endpointTCP:holding:modbusPukuhuone:number" }
Number modbusMakuuhuone1	 "Makuuhuone 1 [%.1f °C]"	 <bedroom> { channel="modbus:readwrite:endpointTCP:holding:modbusMakuuhuone1:number" }

Only thing missing is the right transformation. I need to divide the holding register value by 10 before displaying it. How to use transform=“default” option?

Great to hear! Yes, Siemens s7 1200 maps outputs to coils in modbus.

The things config looks good to me. I think the type parameter is only applicable to write types, the read type gets it from the poller Bridge.

You can indeed use the transforms to divide/multiply values on read/write.

I suggest you try out the JS transform. You might need to install openhab-transformation-javascript feature. You can use transform examples from this post, pasted here for your convenenience:

transform/multiply10.js:

// Wrap everything in a function
(function(i) {
    return Math.round(parseFloat(i, 10) * 10);
})(input)
// input variable contains data passed by openhab

transform/divide10.js:

// Wrap everything in a function
(function(i) {
    return parseFloat(i) / 10;
})(input)
// input variable contains data passed by openhab

Similar to openhab1 modbus binding, where you would say

Number NumberItem "Number [%.1f]" <temperature> {modbus=">[slave1:0:transformation=JS(multiply10.js)],<[slave1:0:transformation=JS(divide10.js)]"}

With the openhab2 modbus binding you would have

Bridge readwrite modbusMakuuhuone4 { 
    Thing read mh4 [ start=0, transform="JS(divide10.js)", trigger="*", valueType="uint16", type="holding" ]
}

You can use transform also with write things, e.g. to multiply openhab commands by 10.

Btw, with Siemens S7 1200 you also have the option to use Siemens data type REAL, and read/write it directly with the binding using valueType=float32. The transformations work equally well of course.

Hope this helps to get you forward!

P.S. Yes I can see the Paper UI is quite much work with so many things… Any ideas how to improve it?

1 Like

That helped really much! Transformations work, all 9 different values are polled from holding register and show in my sitemap. I had the exact same transform script with the old modbus binding but didn’t think it was this obivious to use it same way with this binding.

I have logging on debug and there’s a lot of lines regarding transformations. Seems that they are for datetime and contact channels. You probably know that already but I’ll paste some logs if you want. I my case only switch and number channels are used. To me it seems wasted processing time. How are the channels created? Could some of them be disabled depending on different usage?

In fact I have been thinking about binding configuration. For average user including myself there’s too much things to create and link togerther. For example is it possible to combine connection and poller definitions as one thing? And according to thing configuration it would create channels? I my case one channel would be one holding register or one coil.

But that’s just for PaperUI. Configuration through text files was hard at first but with little help from you I got it working just fine. I think it’s doable for everybody if you just have good wiki or readme files.

PaperUI with simpler config for beginners and textual config for advanced users with advanced modbus devices?

Hey,

EDIT: as discussed below, the write and read things have addresses relative to poll start. The new data thing has absolute addresses.

actually the write start should be “absolute” addresses, description from Paper UI:

Address of the first holding register or coils in the write. Use zero based address, e.g. in place of 400001 (first holding register), use the address 0. This address is passed to data frame as is.

So in place of

  Bridge poller coils [ start=2, length=4, refresh=5000, type="coil" ] {
        Bridge readwrite DO2 { 
            Thing read readTCP [ start=2, valueType="bit", type="coil" ]
            Thing write writeTCP [ start=2, valueType="bit", type="coil" ]
        }

you would like to have

  Bridge poller coils [ start=2, length=4, refresh=5000, type="coil" ] {
        Bridge readwrite DO2 { 
            Thing read readTCP [ start=2, valueType="bit", type="coil" ]
            Thing write writeTCP [ start=6, valueType="bit", type="coil" ]
        }

Will respond with longer post for your other comments.

Best,
Sami

1 Like

Now something has went totally over my head and still have misunderstood the whole configuration. When I want to control one coil why should I read and write different coils?

I have to revise the config parameter names to avoid this misunderstanding.

The write things address is absolute while read things start address is relative to polled registers/bits.

For example if you have polled 3 coils starting from 2. Let’s call them c2, c3, c4. The two coils before these are called c0 and c1.

Read thing with start=0 refers to coil c2, start=1 to coil c3

Write thing with start=0 refers to coil c0, start=1 refers to c1.

I have tried to open this quite much in paper ui descriptions but obviously it’s of no use with textual config.

Would it make more sense to use absolute addresses with read as well? If we would implement support for entity numbers (eg 400001 for first holding register), much of confusion would be lost (with the old binding zero based indexing has been confusing for some with not much modbus experience)

The motivation for absolute address with write is that it obviously has no connection to read stuff, and you might actually want to write outside the polled items as well.

That post cleared a lot. Thank you.

I’d vote for absolute adresses for both. As in my posted modbus.thing file. If done like this and it’s allready defined as a readwrite thing, could we get rid of double lines separetly defining read and write?

Entity numbers? No. Setting type=“coil” or type=“holding” takes care of offset. You are right that using only numbers would be less confusion to beginners in modbus but I would suggest them to read and learn more. I have been studying http://www.simplymodbus.ca/faq.htm many times.

Tested your config example just now.

  Bridge poller coils [ start=2, length=4, refresh=5000, type="coil" ] {
        Bridge readwrite DO2 { 
            Thing read readTCP [ start=2, valueType="bit", type="coil" ]
            Thing write writeTCP [ start=6, valueType="bit", type="coil" ]
        }

It didn’t work.

Spent an hour testing different addressing and this is the one that’s working.

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", type="coil" ]
        }
    	Bridge readwrite DO3 { 
            Thing read readTCP [ start=1, valueType="bit" ]
            Thing write writeTCP [ start=1, valueType="bit", type="coil" ]
        }
    	Bridge readwrite DO4 { 
            Thing read readTCP [ start=2, valueType="bit" ]
            Thing write writeTCP [ start=2, valueType="bit", type="coil" ]
        }
    	Bridge readwrite DO5 { 
            Thing read readTCP [ start=3, valueType="bit" ]
            Thing write writeTCP [ start=3, valueType="bit", type="coil" ]
        }
    }
}

Both read and write are not absolute. Could there be some settings in my S7-1200 that’s messing this up?

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

  1. Same read/write address, same value type (e.g. reading/writing integer to coil/holding register) – the most common use case for sure
  2. “Set coil to true on any command”, writing to different address than reading, write transformed differently than read. example in old binding
  3. read & write different addresses. In new binding must point to same endpoint, though.
  4. Rollershutter example, mix-match of other use cases
  5. converting different commands to different register values (ON=256, OFF=512)
  6. writing an openHAB command to multiple registers (e.g. turning many devices off)
  7. 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, with writeType=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

  1. readAddress equals writeAddress, readValueType equals writeValueType
  2. transformation returning always 1
  3. just configure read index and write index
  4. more advanced case – would be solved by “mini binding” or “general purpose write” approaches
  5. solvable e.g. using JS transformation and switch-case
  6. more advanced case – would be solved by “general purpose write” approaches
  7. 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

  1. set coil 0 to on, using FC5 (write single coil)
  2. set holding register 0 to 256, using FC6 (write single holding register)
  3. 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 commands
  • up_down_type : coil or holding. What FC is used to write UP/DOWN
  • up_value: value to write with UP, e.g. 1
  • down_value: value to write with DOWN, e.g. -1
  • move_stop_write_index : in what (coil/holding) index to write MOVE and STOP commands
  • move_stop_write_type : coil or holding. What FC is used to write MOVE/STOP
  • move_value: value to write with UP, e.g. 1
  • stop_value: value to write with DOWN, e.g. 0
  • position_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

Hi @ssalonen,

I’m sorry for the late reply but I must have missed the update you’ve posted!

This indeed fixes the issue. Thanks a lot!

Greetings,
Frederic

First of all don’t be sorry. This is a binding in in alpha state for testing and i’m a tester :smile:

You have good point regarding entity numbers, perhaps we need to require the user to understand the minimum basics of modbus addressing.

This is absolutely true. Modbus isn’t the easiest to understand and this can’t be made “idiot proof” so requiring basic knowledge is more than ok.


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.

Sounds goo to me.


Hopefully this makes sense, it’s always a bit hard to put word to early ideas.

I’ve been reading on binding development for last couple of days and I think I got better idea of things, bridges and channels. I’m starting to agree with you about needing this much things and channels. Just the nature of modbus and making this binding to serve wide range of users and use cases. All in all your alternative idea seem good and justified. Especially I like your “Simple case” example as it is my case. That approach simplifies textual config and would say that in won’t get any easier. Or maybe few more config options: readAddress and writeAddress combined as rwAddress?


Last but not least that mini bindig and transformation idea about breaking advanced functionality into smaller and optional parts. I like it. And just as you said it:

… for simple and typical use cases but still allow enough flexibility for some of the “harder” cases.

1 Like