Automation Professionals' Advanced Modbus Driver

Another BETA. Cleaned out some stray references to com.inductiveautomation.xopc, and made my generic shim a little more generic. (Discovered some conflicts when I started applying these classes to my EtherNet/IP driver.)

For Ignition v8.1.7+: v1.1.13.250341606

This one is solid. Seems more performant than v1.1.12. Will declare it production-grade soon, I think.

1 Like

Well, I didn't declare it production grade, as I found a couple minor items in the DriverAdapter to Device conversion. I also refactored my CSV versus binary serialization format in the internal database to make it easier to excise the latter in v8.3. Which enabled a feature I've been cogitating on for a while: mitigating the bad behavior of some devices when abutting register ranges cannot be spanned. My config will no longer merge configured ranges of addresses that abut without overlapping, and those boundaries will be honored by the underlying optimizer. This is another BETA.

For Ignition v8.1.7+: v1.1.13.250681940

{ Side note: I had to restart the service after upgrading due to Wicket weirdness. Die! }

2 Likes

Hey Phil,

We're trying to test out the ELAM mode, and when checking the Extended Lufkin box in the device configuration page, the status immediately goes to N/A and the following error appears in our logs

java.lang.ArrayIndexOutOfBoundsException: Index 256 out of bounds for length 256
at com.automation_pros.modbus.driver.ModbusClient.(ModbusClient.java:83)
at com.automation_pros.modbus.config.ModbusClientDeviceType.createDevice(ModbusClientDeviceType.java:33)
at com.inductiveautomation.ignition.gateway.opcua.server.DeviceManager.createDevice(DeviceManager.kt:240)
at com.inductiveautomation.ignition.gateway.opcua.server.DeviceManager.createAndStartupDevice(DeviceManager.kt:259)
at com.inductiveautomation.ignition.gateway.opcua.server.DeviceManager.access$createAndStartupDevice(DeviceManager.kt:75)
at com.inductiveautomation.ignition.gateway.opcua.server.DeviceManager$DeviceSettingsRecordListener$recordUpdated$1.invokeSuspend(DeviceManager.kt:429)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:274)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at com.inductiveautomation.ignition.gateway.opcua.server.DeviceManager$DeviceSettingsRecordListener.recordUpdated(DeviceManager.kt:371)
at com.inductiveautomation.ignition.gateway.opcua.server.DeviceManager$DeviceSettingsRecordListener.recordUpdated(DeviceManager.kt:346)
at com.inductiveautomation.ignition.gateway.localdb.PersistenceInterfaceImpl.notifyRecordUpdated(PersistenceInterfaceImpl.java:177)
at com.inductiveautomation.ignition.gateway.redundancy.RedundantPersistenceInterfaceImpl.doNotifyRecordUpdated(RedundantPersistenceInterfaceImpl.java:111)
at com.inductiveautomation.ignition.gateway.redundancy.RedundantPersistenceInterfaceImpl$RecordUpdateListener.recordUpdated(RedundantPersistenceInterfaceImpl.java:407)
at com.inductiveautomation.ignition.gateway.redundancy.RedundantPersistenceInterfaceImpl$RecordUpdateMessage.notify(RedundantPersistenceInterfaceImpl.java:457)
at com.inductiveautomation.ignition.gateway.redundancy.RedundantPersistenceInterfaceImpl$RecordUpdateListener.receiveCall(RedundantPersistenceInterfaceImpl.java:390)
at com.inductiveautomation.ignition.gateway.redundancy.QueueableMessageReceiver.receiveCall(QueueableMessageReceiver.java:47)
at com.inductiveautomation.ignition.gateway.redundancy.RedundancyManagerImpl.dispatchMessage(RedundancyManagerImpl.java:1044)
at com.inductiveautomation.ignition.gateway.redundancy.RedundancyManagerImpl$ExecuteTask.run(RedundancyManagerImpl.java:1112)
at com.inductiveautomation.ignition.common.execution.impl.BasicExecutionEngine$ThrowableCatchingRunnable.run(BasicExecutionEngine.java:550)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)

Error is from version: 1.1.13 (b250681940)
But we saw essentially the same error on 1.1.12 (b250132149)

1 Like

Same problem here!!

Boy does that make me feel dumb. :man_facepalming:

Try this: v1.1.13.250991635

(I have no lab hardware to test ELAM against.)

2 Likes

So, good news is that it no longer errors out when checking that box :smiley:

Bad news is that after setting up the Device Configuration (I was trying to configure the address ranges for my unit in hopes it would let me pull data), now the module wont start/restart. I can't even remove my created device as it disappeared from the Device Connections page

java.lang.Exception: Exception while starting up module "com.automation_pros.modbus".
	at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl$LoadedModule.startup(ModuleManagerImpl.java:2452)
	at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl.startup(ModuleManagerImpl.java:423)
	at com.inductiveautomation.ignition.gateway.IgnitionGateway.startupInternal(IgnitionGateway.java:1334)
	at com.inductiveautomation.ignition.gateway.redundancy.RedundancyManagerImpl.startup(RedundancyManagerImpl.java:307)
	at com.inductiveautomation.ignition.gateway.IgnitionGateway.initRedundancy(IgnitionGateway.java:777)
	at com.inductiveautomation.ignition.gateway.IgnitionGateway.lambda$initInternal$1(IgnitionGateway.java:700)
	at com.inductiveautomation.ignition.common.execution.impl.BasicExecutionEngine$ThrowableCatchingRunnable.run(BasicExecutionEngine.java:550)
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
	at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
	at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
	at java.base/java.lang.Thread.run(Unknown Source)
Caused by: java.lang.ClassCastException: class com.automation_pros.modbus.config.ModbusUnitDetails cannot be cast to class com.automation_pros.modbus.config.ModbusClientUnitDetails (com.automation_pros.modbus.config.ModbusUnitDetails and com.automation_pros.modbus.config.ModbusClientUnitDetails are in unnamed module of loader com.inductiveautomation.ignition.gateway.modules.ModuleClassLoader @b9a400a)
	at com.automation_pros.modbus.driver.ModbusClient.<init>(ModbusClient.java:84)
	at com.automation_pros.modbus.config.ModbusClientDeviceType.createDevice(ModbusClientDeviceType.java:33)
	at com.inductiveautomation.ignition.gateway.opcua.server.DeviceManager.createDevice(DeviceManager.kt:240)
	at com.inductiveautomation.ignition.gateway.opcua.server.DeviceManager.createAndStartupDevice(DeviceManager.kt:259)
	at com.inductiveautomation.ignition.gateway.opcua.server.DeviceManager.registerDeviceType(DeviceManager.kt:204)
	at com.inductiveautomation.ignition.gateway.opcua.OpcUaExtensionManager.registerDeviceType(OpcUaExtensionManager.kt:35)
	at com.inductiveautomation.ignition.gateway.opcua.server.api.AbstractDeviceModuleHook.serviceReady(AbstractDeviceModuleHook.kt:50)
	at com.inductiveautomation.ignition.gateway.services.ModuleServicesManagerImpl.subscribe(ModuleServicesManagerImpl.java:96)
	at com.inductiveautomation.ignition.gateway.opcua.server.api.AbstractDeviceModuleHook.startup(AbstractDeviceModuleHook.kt:37)
	at com.automation_pros.modbus.GatewayHook.startup(GatewayHook.java:62)
	at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl$LoadedModule.startup(ModuleManagerImpl.java:2446)
	... 12 more

8.1.44 (b2024102210)
Azul Systems, Inc. 17.0.12

Yup. Too hasty.

Try this: v1.1.13.250991728

Thank you!

I've had this happen a few times now where editing the device will not seem to do anything because the threads are blocking one another, if interested I can send you a thread dump

Please do!

sent :slight_smile:

Classic AB-BA deadlock exposed in that thread dump. :frowning:

Try this: v1.1.13.250991801

(It has been in the code a long time.)

heh I seem to have a knack for breaking this, this was accomplished by disabling then re-enabling a device.

So far, I haven't been successful in pulling any data from any devices, I am able to pull valid data using modpoll, still testing the driver to see if i can figure out if it's a configuration issue or not though

java.lang.Exception: Exception while starting up module "com.automation_pros.modbus".
	at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl$LoadedModule.startup(ModuleManagerImpl.java:2452)
	at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl.startupModule(ModuleManagerImpl.java:1188)
	at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl$4.call(ModuleManagerImpl.java:847)
	at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl.executeModuleOperation(ModuleManagerImpl.java:913)
	at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl.restartModuleInternal(ModuleManagerImpl.java:827)
	at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl.restartModule(ModuleManagerImpl.java:819)
	at com.inductiveautomation.ignition.gateway.web.pages.config.ModulePage$RestartAction.execute(ModulePage.java:575)
	at com.inductiveautomation.ignition.gateway.web.components.ConfirmationPanel$1.onClick(ConfirmationPanel.java:49)
	at org.apache.wicket.markup.html.link.Link.onLinkClicked(Link.java:190)
	at jdk.internal.reflect.GeneratedMethodAccessor109.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:243)
	at org.apache.wicket.core.request.handler.ListenerInterfaceRequestHandler.respond(ListenerInterfaceRequestHandler.java:236)
	at org.apache.wicket.request.cycle.RequestCycle$HandlerExecutor.respond(RequestCycle.java:890)
	at org.apache.wicket.request.RequestHandlerStack.execute(RequestHandlerStack.java:64)
	at org.apache.wicket.request.cycle.RequestCycle.execute(RequestCycle.java:261)
	at org.apache.wicket.request.cycle.RequestCycle.processRequest(RequestCycle.java:218)
	at org.apache.wicket.request.cycle.RequestCycle.processRequestAndDetach(RequestCycle.java:289)
	at org.apache.wicket.protocol.http.WicketFilter.processRequestCycle(WicketFilter.java:259)
	at org.apache.wicket.protocol.http.WicketFilter.processRequest(WicketFilter.java:201)
	at org.apache.wicket.protocol.http.WicketFilter.doFilter(WicketFilter.java:282)
	at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:210)
	at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1635)
	at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:527)
	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:131)
	at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:598)
	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
	at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:223)
	at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1580)
	at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:221)
	at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1384)
	at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:176)
	at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:484)
	at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1553)
	at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:174)
	at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1306)
	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:129)
	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
	at com.inductiveautomation.catapult.handlers.RemoteHostNameLookupHandler.handle(RemoteHostNameLookupHandler.java:121)
	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
	at org.eclipse.jetty.rewrite.handler.RewriteHandler.handle(RewriteHandler.java:301)
	at org.eclipse.jetty.server.handler.HandlerList.handle(HandlerList.java:51)
	at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:141)
	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
	at org.eclipse.jetty.server.Server.handle(Server.java:563)
	at org.eclipse.jetty.server.HttpChannel$RequestDispatchable.dispatch(HttpChannel.java:1598)
	at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:753)
	at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:501)
	at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:287)
	at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:314)
	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
	at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
	at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.runTask(AdaptiveExecutionStrategy.java:421)
	at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.consumeTask(AdaptiveExecutionStrategy.java:390)
	at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.tryProduce(AdaptiveExecutionStrategy.java:277)
	at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.run(AdaptiveExecutionStrategy.java:199)
	at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:411)
	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:969)
	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1194)
	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1149)
	at java.base/java.lang.Thread.run(Unknown Source)
Caused by: java.lang.NumberFormatException: For input string: "true"
	at java.base/java.lang.NumberFormatException.forInputString(Unknown Source)
	at java.base/java.lang.Integer.parseInt(Unknown Source)
	at java.base/java.lang.Integer.parseUnsignedInt(Unknown Source)
	at java.base/java.lang.Integer.parseUnsignedInt(Unknown Source)
	at com.automation_pros.modbus.config.ModbusUnitDetails.parseUnsignedDefault(ModbusUnitDetails.java:308)
	at com.automation_pros.modbus.config.ModbusUnitDetails.applyCsvItem(ModbusUnitDetails.java:339)
	at com.automation_pros.modbus.config.ModbusClientUnitDetails.applyCsvItem(ModbusClientUnitDetails.java:171)
	at com.automation_pros.modbus.config.settings.AbstractModbusSettings.decodeUnitsCsv(AbstractModbusSettings.java:92)
	at com.automation_pros.modbus.config.settings.AbstractModbusSettings.getUnitDetails(AbstractModbusSettings.java:117)
	at com.automation_pros.modbus.driver.ModbusClient.<init>(ModbusClient.java:80)
	at com.automation_pros.modbus.config.ModbusClientDeviceType.createDevice(ModbusClientDeviceType.java:33)
	at com.inductiveautomation.ignition.gateway.opcua.server.DeviceManager.createDevice(DeviceManager.kt:240)
	at com.inductiveautomation.ignition.gateway.opcua.server.DeviceManager.createAndStartupDevice(DeviceManager.kt:259)
	at com.inductiveautomation.ignition.gateway.opcua.server.DeviceManager.registerDeviceType(DeviceManager.kt:204)
	at com.inductiveautomation.ignition.gateway.opcua.OpcUaExtensionManager.registerDeviceType(OpcUaExtensionManager.kt:35)
	at com.inductiveautomation.ignition.gateway.opcua.server.api.AbstractDeviceModuleHook.serviceReady(AbstractDeviceModuleHook.kt:50)
	at com.inductiveautomation.ignition.gateway.services.ModuleServicesManagerImpl.subscribe(ModuleServicesManagerImpl.java:96)
	at com.inductiveautomation.ignition.gateway.opcua.server.api.AbstractDeviceModuleHook.startup(AbstractDeviceModuleHook.kt:37)
	at com.automation_pros.modbus.GatewayHook.startup(GatewayHook.java:62)
	at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl$LoadedModule.startup(ModuleManagerImpl.java:2446)
	... 63 more

8.1.44 (b2024102210)
Azul Systems, Inc. 17.0.12

Hmmm. It's trying to retrieve the extended register count. Can you share the CSV export?

Never mind. ):

Try this: v1.1.13.250992013

You possibly lost part of the config for any device node list you edited. ):

So after installing the new module it still shows up as faulted with the same error, do i need to somehow delete the devices and re-add?

Yes. You might be able to disable it via script, then restart the module, then delete. Otherwise surgery on the config.idb will be needed.

Thank you!
I was able to disable with system.device.setDeviceEnabled('<DeviceName>', False)
then delete it by following your instructions

These settings cause it to break again (i get Internal Error, subsequent restart of module causes it to fault), giving the blanks a 0-0 range is successful.

I will play around with the configuration and see if i can pull any data, thanks again for your assistance

3 Likes

Hi Matthew, any luck?

I'm trying to communicate with a Lufkin SAM controller and I can only achieve it if I use a Modbus ID=2 (i.e. in ModbusRTU mode), but when I try to do it using ID=300 (ELAM Mode) I only get Bad quality on my TAGs.

I would be happy to look at wireshark captures of this traffic....

No I have not had any luck yet, I haven't had a chance to work on it today though

I'm debugging some wicket weirdness on module startup--it isn't just the empty fields. /: