Automation Professionals' Advanced Modbus Driver

Automation Professionals is pleased to announce the first beta for its Advanced Modbus Driver, for Ignition version 7.9.

Key features:

  • Server mode, emulating one or more slave devices, using any of the three typical connection types, including simultaneous connections of multiple types.
  • Client mode, incorporating all three connection modes in one driver.
  • Support for Modbus Function Codes 20 and 21, File Records.
  • Configure protocol tweaks for troublesome devices per slave unit, enabling support for devices with different tweaks on the same multi-drop channel.
  • Configure supported memory addresses, including support for multiple discontinuous ranges, per memory area and per slave unit address. This allows gap spanning optimizations while still avoiding errors from unsupported device addresses.
  • CSV Export and Import of per-unit configurations. Including moving partial configurations between server and client drivers.
  • Browsing configured units and their addresses, with data type options for 16-bit registers exposed as sub-trees under each browsed register.
  • Support treating consecutive bits in any memory area as integer bit fields. (Long integers–63 bits–in bit memory, and short integers–15 bits–in word register memory.)
  • Support for gateway local serial ports without the Gateway Serial Support module (different underlying library).

All of the new addressing and data type features are supported in both Client and Server device types. All memory areas and data types and formats are writable in the Server device type. Writing to booleans and bit fields in word registers is limited to Holding Registers in the Client device type, and only if Masked Writes is enabled.

Due to the browsable architecture, it was not practical to support the address mapping features of the IA driver. Nor has it been practical to support one-based addressing. Since that is the default in IA’s driver, be sure to turn that off when inter-operating. IA’s manual address formats are fully supported in this driver. They are extended to include the new datatype features.

Beta notes:

  • Redundancy is planned and stubs are in place, but it doesn’t work yet. ):
  • Alternate data type prefixes are documented but not implemented.
  • v8.1 support isn’t quite ready yet (maybe this weekend).
  • v8.0 support is not planned at all.

Get the module here.

Preview the documentation here.

{ Edit 4: Latest links are below. }

9 Likes

Phil,

I am getting an “Internal Error” web page when I click on “More->Configuration” with a new Modbus Advanced Client device.

Ignition: 7.9.14
Java: 1.8.0_261-b12
OS: Windows 10

error.txt (9.7 KB)

Updated link with fix. And with latest features: Client driver stats, untested but likely functional redundancy.

{ I have a suspicion why the export link bug didn’t show up on my test server after that entry was renamed… }

And Merry Christmas!

{Edit: I’m calling this afternoon’s build a release candidate. Onward to v8.1!}

Well… I get the same error with the newer release.

org.apache.wicket.WicketRuntimeException: Property 'enip1.Config.ExportLinkLabel' not found in property files. Markup: [markup = jar:file:/C:/Program%20Files/Inductive%20Automation/Ignition/data/jar-cache/com.automation_pros.modbus/__1781123134__modbus-Gateway.jar!/com/automation_pros/modbus/config/web/ModbusUnitsConfig.html Export CSV Configuration, index = 1, current = [Raw markup]]

at org.apache.wicket.markup.resolver.WicketMessageResolver$MessageContainer.onComponentTagBody(WicketMessageResolver.java:215)

With this type of resource error, you will have to reboot the gateway. Ignition caches much of this outside of module context and it usually won’t update with the module. ):

(Which explains how I missed this–My test gateway must have retained an early version that still had that string.)

Hi Phil
Any update news for 8.1.1?

1 Like

Beta for v8.1 is available here. Only very lightly tested.

@jay’s bug showed up in v8.1, and the fix I thought would work didn’t. Which means my v7.9 testbed is busted for an unknown reason, and I will have to apply the 8.1 fix to v7.9. /:

1 Like

FWIW, this was my easiest v7.9 to v8+ conversion. Largely due to the effort I put into a “GenericDriver” shim that abstracts much of the OPC differences between them. I expect to use it for other drivers, at least until v7.9 EOL.

Here’s the diffstat between the two git branches:

 Build/build.xml                                                             |   4 +-
 Build/doc/UserManual.fodt                                                   |   6 +-
 Build/module.xml                                                            |   5 +-
 Gateway/.classpath                                                          |  26 ++--
 Gateway/src/com/automation_pros/modbus/config/ModbusClientDriverType.java   |   2 +-
 Gateway/src/com/automation_pros/modbus/config/ModbusServerDriverType.java   |   2 +-
 .../com/automation_pros/modbus/config/settings/AbstractModbusSettings.java  |   2 +-
 .../com/automation_pros/modbus/config/settings/ModbusClientSettings.java    |   2 +-
 .../com/automation_pros/modbus/config/settings/ModbusServerSettings.java    |   2 +-
 Gateway/src/com/automation_pros/modbus/config/web/ModbusDriverSummary.java  |   2 +-
 Gateway/src/com/automation_pros/modbus/config/web/ModbusUnitsConfig.html    |   3 +-
 Gateway/src/com/automation_pros/modbus/config/web/ModbusUnitsConfig.java    |   2 +-
 Gateway/src/com/automation_pros/modbus/driver/GenericDriver.java            | 261 ++++++++++++++++++++----------------
 Gateway/src/com/automation_pros/modbus/driver/GenericModbusDriver.java      |   7 +-
 prep.py                                                                     |  16 +--
 15 files changed, 181 insertions(+), 161 deletions(-)

Latest v7.9 file eliminates the export link translatable title entirely. @jay, please check.

I can now get to the configuration page!

On to the next glitch…

image

You can see that I defined Unit 4 but can only browse Unit 1.

There’s a note in the manual that you have to restart the device yourself after editing the node list. So you can make multiple edits before any take effect. Open the regular device settings and just hit save.

Excellent Phil!
Both the server and client were able to read/write anything I threw at them. Running through codes 01-06, 15, 16, 22 yielded exactly what I'd expect.

A couple things (8.1):

  1. Double check your documentation section on supported function codes, I think your chart is misaligned.


  2. Is there a syntax to select "None" of a certain register for a client (for example, only Input/Holding registers and no coils)? If I try to leave the range blank, I get the following result:


DefaultExceptionMapper	05Jan2021 16:22:30	Unexpected error occurred
org.apache.wicket.WicketRuntimeException: Error calling method: public void com.automation_pros.modbus.config.settings.ModbusCommonUnitProps.setCoilsRangeString(java.lang.String) on object: com.automation_pros.modbus.config.settings.ModbusClientUnitProps@13ce2397

at org.apache.wicket.core.util.lang.PropertyResolver$MethodGetAndSet.setValue(PropertyResolver.java:1210)

at org.apache.wicket.core.util.lang.PropertyResolver$ObjectAndGetSetter.setValue(PropertyResolver.java:641)

at org.apache.wicket.core.util.lang.PropertyResolver.setValue(PropertyResolver.java:143)

at org.apache.wicket.model.AbstractPropertyModel.setObject(AbstractPropertyModel.java:132)

at org.apache.wicket.Component.setDefaultModelObject(Component.java:3056)

at org.apache.wicket.markup.html.form.FormComponent.setModelObject(FormComponent.java:1544)

at org.apache.wicket.markup.html.form.FormComponent.updateModel(FormComponent.java:1096)

at org.apache.wicket.markup.html.form.Form$FormModelUpdateVisitor.component(Form.java:227)

at org.apache.wicket.markup.html.form.Form$FormModelUpdateVisitor.component(Form.java:197)

at org.apache.wicket.util.visit.Visits.visitPostOrderHelper(Visits.java:274)

at org.apache.wicket.util.visit.Visits.visitPostOrderHelper(Visits.java:262)

at org.apache.wicket.util.visit.Visits.visitPostOrderHelper(Visits.java:262)

at org.apache.wicket.util.visit.Visits.visitPostOrderHelper(Visits.java:262)

at org.apache.wicket.util.visit.Visits.visitPostOrderHelper(Visits.java:262)

at org.apache.wicket.util.visit.Visits.visitPostOrderHelper(Visits.java:262)

at org.apache.wicket.util.visit.Visits.visitPostOrderHelper(Visits.java:262)

at org.apache.wicket.util.visit.Visits.visitPostOrderHelper(Visits.java:262)

at org.apache.wicket.util.visit.Visits.visitPostOrder(Visits.java:245)

at org.apache.wicket.markup.html.form.FormComponent.visitComponentsPostOrder(FormComponent.java:422)

at org.apache.wicket.markup.html.form.Form.internalUpdateFormComponentModels(Form.java:1792)

at org.apache.wicket.markup.html.form.Form.updateFormComponentModels(Form.java:1756)

at org.apache.wicket.markup.html.form.Form.process(Form.java:912)

at org.apache.wicket.markup.html.form.Form.onFormSubmitted(Form.java:769)

at org.apache.wicket.markup.html.form.Form.onFormSubmitted(Form.java:702)

at jdk.internal.reflect.GeneratedMethodAccessor57.invoke(Unknown Source)

at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)

at java.base/java.lang.reflect.Method.invoke(Unknown Source)

at org.apache.wicket.RequestListenerInterface.internalInvoke(RequestListenerInterface.java:258)

at org.apache.wicket.RequestListenerInterface.invoke(RequestListenerInterface.java:216)

at org.apache.wicket.core.request.handler.ListenerInterfaceRequestHandler.invokeListener(ListenerInterfaceRequestHandler.java:240)

at org.apache.wicket.core.request.handler.ListenerInterfaceRequestHandler.respond(ListenerInterfaceRequestHandler.java:226)

at org.apache.wicket.request.cycle.RequestCycle$HandlerExecutor.respond(RequestCycle.java:814)

at org.apache.wicket.request.RequestHandlerStack.execute(RequestHandlerStack.java:64)

at org.apache.wicket.request.cycle.RequestCycle.execute(RequestCycle.java:253)

at org.apache.wicket.request.cycle.RequestCycle.processRequest(RequestCycle.java:210)

at org.apache.wicket.request.cycle.RequestCycle.processRequestAndDetach(RequestCycle.java:281)

at org.apache.wicket.protocol.http.WicketFilter.processRequest(WicketFilter.java:188)

at org.apache.wicket.protocol.http.WicketFilter.doFilter(WicketFilter.java:245)

at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1596)

at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:545)

at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)

at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:590)

at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)

at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:235)

at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1607)

at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:233)

at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1297)

at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:188)

at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:485)

at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1577)

at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:186)

at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1212)

at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)

at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)

at org.eclipse.jetty.rewrite.handler.RewriteHandler.handle(RewriteHandler.java:322)

at org.eclipse.jetty.server.handler.HandlerList.handle(HandlerList.java:59)

at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:146)

at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)

at org.eclipse.jetty.server.Server.handle(Server.java:500)

at org.eclipse.jetty.server.HttpChannel.lambda$handle$1(HttpChannel.java:383)

at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:547)

at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:375)

at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:270)

at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311)

at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103)

at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:117)

at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:336)

at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:313)

at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:171)

at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:129)

at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:388)

at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:806)

at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:938)

at java.base/java.lang.Thread.run(Unknown Source)

java.lang.NullPointerException: null
  1. I'm having trouble deleting Unit configurations. Also pressing the cancel button seems to encounter the same problem.


DefaultExceptionMapper	05Jan2021 16:32:23	Unexpected error occurred
org.apache.wicket.WicketRuntimeException: Method onLinkClicked of interface org.apache.wicket.markup.html.link.ILinkListener targeted at [Link [Component id = confirm]] on component [Link [Component id = confirm]] threw an exception

at org.apache.wicket.RequestListenerInterface.internalInvoke(RequestListenerInterface.java:268)

at org.apache.wicket.RequestListenerInterface.invoke(RequestListenerInterface.java:216)

at org.apache.wicket.core.request.handler.ListenerInterfaceRequestHandler.invokeListener(ListenerInterfaceRequestHandler.java:240)

at org.apache.wicket.core.request.handler.ListenerInterfaceRequestHandler.respond(ListenerInterfaceRequestHandler.java:226)

at org.apache.wicket.request.cycle.RequestCycle$HandlerExecutor.respond(RequestCycle.java:814)

at org.apache.wicket.request.RequestHandlerStack.execute(RequestHandlerStack.java:64)

at org.apache.wicket.request.cycle.RequestCycle.execute(RequestCycle.java:253)

at org.apache.wicket.request.cycle.RequestCycle.processRequest(RequestCycle.java:210)

at org.apache.wicket.request.cycle.RequestCycle.processRequestAndDetach(RequestCycle.java:281)

at org.apache.wicket.protocol.http.WicketFilter.processRequest(WicketFilter.java:188)

at org.apache.wicket.protocol.http.WicketFilter.doFilter(WicketFilter.java:245)

at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1596)

at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:545)

at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)

at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:590)

at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)

at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:235)

at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1607)

at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:233)

at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1297)

at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:188)

at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:485)

at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1577)

at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:186)

at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1212)

at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)

at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)

at org.eclipse.jetty.rewrite.handler.RewriteHandler.handle(RewriteHandler.java:322)

at org.eclipse.jetty.server.handler.HandlerList.handle(HandlerList.java:59)

at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:146)

at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)

at org.eclipse.jetty.server.Server.handle(Server.java:500)

at org.eclipse.jetty.server.HttpChannel.lambda$handle$1(HttpChannel.java:383)

at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:547)

at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:375)

at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:270)

at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311)

at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103)

at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:117)

at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:336)

at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:313)

at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:171)

at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:129)

at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:388)

at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:806)

at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:938)

at java.base/java.lang.Thread.run(Unknown Source)

Caused by: java.lang.reflect.InvocationTargetException: null

at jdk.internal.reflect.GeneratedMethodAccessor51.invoke(Unknown Source)

at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)

at java.base/java.lang.reflect.Method.invoke(Unknown Source)

at org.apache.wicket.RequestListenerInterface.internalInvoke(RequestListenerInterface.java:258)

... 46 common frames omitted

Caused by: java.lang.IllegalArgumentException: Replacement component must have the same id as the component it will replace. Replacement id [[unitsPanel]], replaced id [[config-contents]].

at org.apache.wicket.Component.replaceWith(Component.java:2732)

at com.inductiveautomation.ignition.gateway.web.pages.Config.setConfigPanel(Config.java:227)

at com.inductiveautomation.ignition.gateway.web.components.ConfirmationPanel.onConfirm(ConfirmationPanel.java:102)

at com.inductiveautomation.ignition.gateway.web.components.ConfirmationPanel$1.onClick(ConfirmationPanel.java:58)

at org.apache.wicket.markup.html.link.Link.onLinkClicked(Link.java:189)

... 50 common frames omitted
  1. It'd be nice if you could apply edits from the configuration page directly.

Still, great work! Before getting my hands on this I was most excited about the server functionality, but it looks like there's a lot of improvements over the IA modbus client module too.

  1. Heh.
  2. Leaving it blank was supposed to work. ):
  3. Eww. Wicket.
  4. Good idea. I even had it, too. Just hadn’t worked out the incantation, yet.

Will work on these later this month (in monster client outage at the moment.)

2 Likes

New release candidates:

For v7.9: here.

For v8.1: here.

Be aware that some wicket resources changed, so a gateway restart is required after upgrade if a prior version was installed.

All of Ben’s items have been addressed, and the alternate address formats have been implemented as described in the manual.

3 Likes

Couple more bugs fixed. Links above updated, broken RCs removed.

1 Like

Forgot to post links to updates I made back in April. ):

Above comment updated.

Did you fix CRC problem in serial mode?

1 Like

Yes.

Another pair of release candidates:

For v7.9: here.

For v8.1: here.

As before, some wicket resources changed, so a gateway restart is required after upgrade if a prior version was installed. (New configuration fields for extra RTU framing time.)

{ Removing links to prior RCs. }

Another pair of release candidates. I was playing with an Automation Direct P1000’s RS485 port this past week and discovered another CRC bug. ):

In my prior testing, I was using registers that were steadily counting. The bug I found was that a CRC with bit 15 set was erroneously sign-extended to 32 bits, and then rejected. But the register was changing fast enough the half that were getting through were hiding the half that were being discarded.

RTU must not be very popular with any of y’all, as no-one else noticed. /:

Anyways:

For v7.9: here.

For v8.1 here.

Edit: Darn it! Server mode had the same problem.
Edit2: Removed links. See below.

1 Like