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).
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.)
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. /:
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:
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):
Double check your documentation section on supported function codes, I think your chart is misaligned.
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
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
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.
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.)
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.