hivemq / hivemq-edge

HiveMQ Edge is an MQTT gateway that enables interoperability between OT devices and IT systems. It translates diverse protocols into MQTT for streamlined communication and helps organize data into a unified namespace, making managing and streaming data across your infrastructure easier.
http://hivemq.com
Apache License 2.0
90 stars 20 forks source link

Modbus protocol adapter fails with multiple subscriptions #328

Open prairiesnpr opened 3 months ago

prairiesnpr commented 3 months ago

Expected behavior

I'm trying to poll multiple non-sequential holding registers from a PLC. I would expect to be able to poll multiple registers and publish values to the broker.

Actual behavior

Adding multiple subscriptions fails with:

java.util.concurrent.ExecutionException: java.lang.RuntimeException: com.hivemq.edge.modules.adapters.ProtocolAdapterException: java.util.concurrent.ExecutionException: com.digitalpetri.modbus.ModbusResponseException: functionCode=ReadHoldingRegisters, exceptionCode=IllegalDataValue
    at java.base/java.util.concurrent.CompletableFuture.reportGet(Unknown Source)
    at java.base/java.util.concurrent.CompletableFuture.get(Unknown Source)
    at com.hivemq.edge.modules.adapters.impl.ProtocolAdapterPollingServiceImpl$MonitoredPollingJob.run(ProtocolAdapterPollingServiceImpl.java:241)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
    at java.base/java.util.concurrent.FutureTask.runAndReset(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.RuntimeException: com.hivemq.edge.modules.adapters.ProtocolAdapterException: java.util.concurrent.ExecutionException: com.digitalpetri.modbus.ModbusResponseException: functionCode=ReadHoldingRegisters, exceptionCode=IllegalDataValue
    at com.hivemq.edge.adapters.modbus.ModbusProtocolAdapter.readRegisters(ModbusProtocolAdapter.java:180)
    at com.hivemq.edge.adapters.modbus.ModbusProtocolAdapter.lambda$onSamplerInvoked$3(ModbusProtocolAdapter.java:158)
    at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(Unknown Source)
    at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(Unknown Source)
    at java.base/java.util.concurrent.ForkJoinTask.doExec(Unknown Source)
    at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(Unknown Source)
    at java.base/java.util.concurrent.ForkJoinPool.scan(Unknown Source)
    at java.base/java.util.concurrent.ForkJoinPool.runWorker(Unknown Source)
    at java.base/java.util.concurrent.ForkJoinWorkerThread.run(Unknown Source)
Caused by: com.hivemq.edge.modules.adapters.ProtocolAdapterException: java.util.concurrent.ExecutionException: com.digitalpetri.modbus.ModbusResponseException: functionCode=ReadHoldingRegisters, exceptionCode=IllegalDataValue
    at com.hivemq.edge.adapters.modbus.impl.ModbusClient.readHoldingRegisters(ModbusClient.java:132)
    at com.hivemq.edge.adapters.modbus.ModbusProtocolAdapter.readRegisters(ModbusProtocolAdapter.java:171)
    ... 8 more
Caused by: java.util.concurrent.ExecutionException: com.digitalpetri.modbus.ModbusResponseException: functionCode=ReadHoldingRegisters, exceptionCode=IllegalDataValue
    at java.base/java.util.concurrent.CompletableFuture.reportGet(Unknown Source)
    at java.base/java.util.concurrent.CompletableFuture.get(Unknown Source)
    at com.hivemq.edge.adapters.modbus.impl.ModbusClient.readHoldingRegisters(ModbusClient.java:129)
    ... 9 more
Caused by: com.digitalpetri.modbus.ModbusResponseException: functionCode=ReadHoldingRegisters, exceptionCode=IllegalDataValue
    at com.digitalpetri.modbus.master.ModbusTcpMaster.handleResponse(ModbusTcpMaster.java:181)
    at com.digitalpetri.modbus.master.ModbusTcpMaster.lambda$onChannelRead$5(ModbusTcpMaster.java:165)
    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.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.base/java.lang.Thread.run(Unknown Source)

To Reproduce

<protocol-adapters>
        <modbus>
            <maxPollingErrorsBeforeRemoval>10</maxPollingErrorsBeforeRemoval>
            <pollingIntervalMillis>1000</pollingIntervalMillis>
            <publishChangedDataOnly>true</publishChangedDataOnly>
            <subscriptions>
                <subscription>
                    <addressRange>
                        <startIdx>36</startIdx>
                        <endIdx>39</endIdx>
                    </addressRange>
                    <includeTagNames>true</includeTagNames>
                    <includeTimestamp>true</includeTimestamp>
                    <messageHandlingOptions>MQTTMessagePerSubscription</messageHandlingOptions>
                    <qos>0</qos>
                    <destination>company/location/department/machine</destination>
                </subscription>
                <subscription>
                    <addressRange>
                        <startIdx>54</startIdx>
                        <endIdx>57</endIdx>
                    </addressRange>
                    <includeTagNames>true</includeTagNames>
                    <includeTimestamp>true</includeTimestamp>
                    <messageHandlingOptions>MQTTMessagePerSubscription</messageHandlingOptions>
                    <qos>0</qos>
                    <destination>company/location/department/machine</destination>
                </subscription>
            </subscriptions>
            <timeout>5000</timeout>
            <port>502</port>
            <host>192.168.200.10</host>
            <id>test_sub</id>
        </modbus>
 </protocol-adapters>

Either subscription by itself will work, just not multiple subscriptions.

I also tried setting unique destinations for each subscription, no change.

Details

DC2-DanielKrueger commented 4 weeks ago

Hallo @prairiesnpr, thanks for raising this issue. I tested it with the current master state and I did not run into that exception. There were quite some changes in the adapter handling and one might have resolved that problem. Is the bug still happening on your setup?

Best regards, Daniel