Automation Professionals' Advanced Modbus Driver

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

Hi

We have been experimenting with this module and its running as a modbus server.

The module seems to run fine on windows, but on linux I get this error:

ServerTCPChannel 05Nov2021 14:48:26 [ModbusTCPServer] Listener Error TCP Channel ServerSocket[unbound]
java.net.BindException: Permission denied (Bind failed)

any tips on how to solve the permission issue? Im running Ubuntu 20.04.3 LTS and its bound to 0.0.0.0
ufw has open port 502.

Solved( linux require root access for ports below 1024)

1 Like

Instead of running as root, I recommend adding the following in your systemd configuration:

[Service]
AmbientCapabilities=CAP_NET_BIND_SERVICE
2 Likes

Hi @pturmel any chance you’re Modbus Driver supports function code 23? I don’t see it listed above. We’ve had to use UCON in the past to build this out but would prefer something of the shelf. There is an oven controller we’re working with that requires it:

No, sorry. I didn’t implement it because there’s no way in Ignition’s OPC server to signal that a read and a write belong together. If I added support for this, it would have to be through a scripting call.

Let me investigate.

It looks like it would be relatively easy to add a script function of the following form:

system.opc.rawModbus(devName, unit, functionCode, payload, responseLen)

devName would be a string, unit, functionCode, and responseLen would be integers, and payload would be a byte array or list of bytes.

It would return a CompletableFuture, from which you would get the result and response payload. (As a QualifiedValue.)

You would have to construct the byte payload to send and decode the byte payload response.

Give me a few days to work this in.

We already have it built out in Kepware so don’t go too crazy. it would be nice to have an alternative to our custom protocol within ucon but not that big a deal.

Too late. Already coded. Doing some testing. (:

Considering the approach allows supporting any exotic function desired, I think it is worth including.

The scripting function system.opc.rawModbus() has now been implemented with the signature shown above. The user manual hasn’t been updated yet, but y’all can play with it:

For Ignition v8.1

For Ignition v7.9

Note that this function is available in Gateway scope only. For use elsewhere, like in the Designer’s script console, I recommend a gateway message handler of this form:

def handleMessage(payload):
	from java.lang import Throwable
	import traceback

	dev, unit, func, pay, rsplen = [payload.get(x, None) for x in ['devName', 'unit', 'functionCode', 'payload', 'responseLen']]
	try:
		return system.opc.rawModbus(dev, unit, func, pay, rsplen).get()
	except Throwable, t:
		return serialization.stringException(t)
	except:
		return traceback.format_exc()

In a designer script console, @plarochelle’s desired use of function 23 would look like this:

from java.io import ByteArrayInputStream, ByteArrayOutputStream, DataInputStream, DataOutputStream
from com.inductiveautomation.ignition.common.model.values import QualifiedValue

# Raw implementation of write/read multiple registers, function 23.
# Start by constructing the payload of the Modbus PDU.  It is
# a UINT read starting address and a UINT number of read registers (<=125),
# then UINT write starting address and UINT number to write (<=121),
# then USINT bytes to follow, then INT/UINT per register.

baos = ByteArrayOutputStream()
dos = DataOutputStream(baos)
# Read start address and quantity
dos.writeShort(0x2006)
dos.writeShort(1)
# Write start address and quantity
dos.writeShort(0x2006)
dos.writeShort(11)
# Write registers bytes to follow
dos.writeByte(22)
# Write registers
dos.writeShort(0x5753)
dos.writeShort(1)
dos.writeShort(5)
dos.writeFloat(654.321)
dos.writeFloat(123.456)

# Ensure data is in the byte stream
dos.flush()

rpcPayload = {'devName': 'P540Rack485', 'unit': 1, 'functionCode': 23, 'payload': baos.toByteArray(), 'responseLen': 3}
retv = system.util.sendRequest('ModbusTest', 'RawModbus', rpcPayload)

if isinstance(retv, QualifiedValue):
	if retv.quality.good:
		bais = ByteArrayInputStream(retv.value)
		dis = DataInputStream(bais)
		bCount = dis.readByte()
		print "Bytes to follow=%d" % bCount
		while bCount > 0:
			bCount -= 2
			register = dis.readShort()
			print "Register = %d" % register
	else:
		print repr(retv)
else:
	print retv

I don’t actually have a device that accepts that function code, so I get a bad QV that indicates the function is unsupported. Testing from someone with such a device would be appreciated, of course.

If you wish to test with something more common, the following is a raw implementation of the common “read holding registers” function 3:

from java.io import ByteArrayInputStream, ByteArrayOutputStream, DataInputStream, DataOutputStream
from com.inductiveautomation.ignition.common.model.values import QualifiedValue

# Raw implementation of read multiple registers, function 3.
# Start by constructing the payload of the Modbus PDU.  It is
# a UINT starting address and a UINT number of registers (<=125).

baos = ByteArrayOutputStream()
dos = DataOutputStream(baos)
dos.writeShort(0)
dos.writeShort(5)
dos.flush()

rpcPayload = {'devName': 'P540Rack485', 'unit': 2, 'functionCode': 3, 'payload': baos.toByteArray(), 'responseLen': 11}
retv = system.util.sendRequest('ModbusTest', 'RawModbus', rpcPayload)

if isinstance(retv, QualifiedValue):
	if retv.quality.good:
		bais = ByteArrayInputStream(retv.value)
		dis = DataInputStream(bais)
		bCount = dis.readByte()
		print "Bytes to follow=%d" % bCount
		while bCount > 0:
			bCount -= 2
			register = dis.readShort()
			print "Register = %d" % register
	else:
		print repr(retv)
else:
	print retv

{ Edited to remove links. See below. }

1 Like

Ah, that message handler uses one of my common scripts to convert backtraces to strings so they can go through the network back to the designer or a Vision client. Available here.

Manual has been updated to describe the rawModbus script function. Release version 1.0.0:

For Ignition v8.1.

For Ignition v7.9.

Prior version links removed. Purchase through my Module Sales page.

2 Likes

Bugfix version 1.0.1. Fixes a null pointer exception when a given register is subscribed in multiple places at the same pace, and any of them are the bare IR, HR, or XR datatype. /:

For Ignition v8.1.

For Ignition v7.9.

2 Likes

Bugfix version 1.0.3. @Kevin.Herron enlightened me on the use of the zero slave address in Modbus TCP, and caused me to review my assumptions. This version stops ignoring address zero as it isn't just the broadcast address.

For Ignition v8.1.

For Ignition v7.9.

3 Likes

Hey @pturmel.

I’m trying to use your module and I think I’m doing something wrong.

I have one Ignition gateway running your Modbus Server device and it is bound to 127.0.0.1

It seems to have bound correctly to the port.

There is a separate gateway I’m trying to use to connect to that Modbus Server and it does get connected, but the values being read from the local gateway and the remote gateway are different.

Can you tell me if the Modbus Server, with unit 1 defined, will serve the same value to a local gateway tag as it does to an external Modbus client?

that is localhost. So it will only receive connections on the same server. I think you meant to use 0.0.0.0. That's the wildcard to listen on all interfaces. Remote server will then point at the local server's external IP address.