2

I am trying to use Python (PyCharm) to read a register on a modbus device. I have confirmed the COM port, Baud rate and other communication settings and I can use the devices application to read the value (it is a water level logger). I am getting no response from the instrument.

Register is readable in mbpoll using -

mbpoll -B -m RTU  -t 4:float -a 1 -b 19200 -r 46 -c 2 /dev/ttyUSB0

enter image description here (Address different as running on Pi not PC)

And MBPOLL - enter image description here enter image description here enter image description here

My code is as follows -

import minimalmodbus
import serial

instrument = minimalmodbus.Instrument('COM5', 1)  # port name, slave address (in decimal)
instrument.serial.port = 'COM5'                     # this is the serial port name
instrument.serial.baudrate = 19200         # Baud
instrument.serial.bytesize = 8
instrument.serial.parity   = serial.PARITY_EVEN
instrument.serial.stopbits = 1
instrument.serial.timeout  = 3          # seconds
instrument.address = 1                         # this is the slave address number
instrument.mode = minimalmodbus.MODE_RTU   # rtu or ascii mode
instrument.clear_buffers_before_each_transaction = True

temperature = instrument.read_float(registeraddress=40046, functioncode=3, number_of_registers=2, byteorder=0)  # Registernumber, number of decimals
print(temperature)
 

Error Received - enter image description here

import minimalmodbus
import serial

instrument = minimalmodbus.Instrument('COM5', 1)  # port name, slave address (in decimal)
instrument.serial.port = 'COM5'                     # this is the serial port name
instrument.serial.baudrate = 19200         # Baud
instrument.serial.bytesize = 8
instrument.serial.parity   = serial.PARITY_EVEN
instrument.serial.stopbits = 1
instrument.serial.timeout  = 0.1        # seconds
instrument.address = 1                         # this is the slave address number
instrument.mode = minimalmodbus.MODE_RTU   # rtu or ascii mode
#nstrument.clear_buffers_before_each_transaction = True

temperature = instrument.read_float(registeraddress=45, functioncode=4, number_of_registers=2, byteorder=0)  # Registernumber, number of decimals

try:
    print(temperature)
except:
    print(temperature)

Edit to include a try - except

Any help appreciated!

EDIT: Link to device manual - https://in-situ.com/en/pub/media/support/documents/Modbus_Manual.pdf Device is a Level Troll 400 Connected to PC via manufactures cable

EDIT 2: I have tried to incorporate minimal modbus structure but to no avail.

EDIT 3: I am able to read a register using Modbus Poll. Register is 40046, so I understand this to be register 45 of the holding registers? How do I translate this to minimalmodbus?

EDIT 4: I am not married to minimal modbus - I am happy to use any tool to get this done

EDIT 5: I have also tried depth = instrument.read_long(x, x) with different values

Joshua Patterson
  • 433
  • 1
  • 4
  • 15
  • 1
    Please provide a link to the device manual. – Tagli May 24 '21 at 09:43
  • Done, thank you! FYI - https://in-situ.com/en/pub/media/support/documents/Modbus_Manual.pdf – Joshua Patterson May 24 '21 at 09:57
  • 2
    `19600` is an unusual baud rate; did you mean `9600` or `19200`? (looks like `19200` is the default). – Brits May 24 '21 at 10:07
  • 19600 may have been a typo on my part but I have tried a variety of baud rates in my testing including 9600 and I believe 19200. I will retry to confirm this isn’t the cause however. Thanks! – Joshua Patterson May 24 '21 at 12:58
  • You will also need to set the serial parameters using the [minimial modbus structure](https://minimalmodbus.readthedocs.io/en/stable/usage.html#default-values); e.g. `instrument.serial.parity = serial.PARITY_EVEN`. – Brits May 24 '21 at 19:55
  • 2
    What is the slave address? 2 or 1? `minimalmodbus.Instrument('COM5', 2) # portname, slaveaddr` vs. `instrument.address = 1` – VPfB May 27 '21 at 06:00
  • In these situations, physical inspection of data on the wire using a logic analyzer greatly simplifies the debugging process. Without one, you're blind. – Tagli May 27 '21 at 06:13
  • Slave is at address 1. As per edits I have connected and read register via Modbus Poll, if that helps? – Joshua Patterson May 27 '21 at 06:14
  • What is the error you get? Is it timeout or some other error? – Tagli May 27 '21 at 18:53
  • Please provide full information re the working Mod Poll configuration (ideally in text but a screenshot is better than nothing) and full details of the results (both in ModPoll and from your application). – Brits May 27 '21 at 19:45
  • 1
    Hopefully all requested edits made! – Joshua Patterson May 28 '21 at 10:45
  • Maybe `Instrument(...., debug=True)` will print something usefull. – VPfB May 29 '21 at 07:51
  • Exception are caused by `read_float`, not by `print`. And you need `functioncode=3` because the manual doesn't mention a distinction between holding and input registers. Most devices just use holding registers. Personally, I would use input registers for read-only data when I design my own Modbus slaves. – Tagli May 29 '21 at 11:03

2 Answers2

3

Just updating with the solution if anyone happens to stumble upon this and has the same problem. As semi-alluded to in other suggestions the device I am connecting to has a 'sleep' period or the like and needs to be polled once, unsuccessfully, before successfully returning values for any subsequent polls. I apologies for my armature code but the solution that works for me is the below -

import minimalmodbus
import serial

instrument = minimalmodbus.Instrument('COM5', 1)  # port name, slave address (in decimal)
instrument.serial.port = 'COM5'                     # this is the serial port name
instrument.serial.baudrate = 19200         # Baud
instrument.serial.bytesize = 8
instrument.serial.parity   = serial.PARITY_EVEN
instrument.serial.stopbits = 1
instrument.serial.timeout  = 1        # seconds
instrument.address = 1                         # this is the slave address number
instrument.mode = minimalmodbus.MODE_RTU   # rtu or ascii mode
#nstrument.clear_buffers_before_each_transaction = True
try:
    temperature = instrument.read_long(registeraddress=9001, functioncode=3,
                                        byteorder=0)  # Registernumber, number of decimals

    print(temperature)


except:
    pass

try:
    temperature = instrument.read_long(registeraddress=9001, functioncode=3,
                                        byteorder=0)  # Registernumber, number of decimals

    print(temperature)


except:
    pass

Joshua Patterson
  • 433
  • 1
  • 4
  • 15
2

The device manual isn't clear about the register start address, but the first register it mentions has the address of 1.

Similarly, the mbpoll command-line utility (not the one with GUI) isn't very clear about the start address. But its documentation mentions that the default value for -r parameter is 1.

I think it's safe to assume that both use the same addressing which starts from 1, as the command-line tool has no problems accessing the value.

But MinimalModbus API clearly mentions that its register start address is 0. So when using this library, you need to use registeraddress = 45 for accessing the temperature, not 46 or 40046.

But why won't 46 work? Normally, one would expect it to grab data starting from the next register and print some garbage, but not timeout. But we can't know how the device works internally. Maybe a request to access the temperature register actually triggers some measurement function and then returns a value. A request to access an unaligned data (with a wrong register value) can be simply rejected by the firmware.

If you still get timeouts with registeraddress = 45, your Python runtime may have some problems accessing the serial port. As I stated in my comment, I recommend using a logic analyzer to see what's going on on the wire. Without such a tool, you're doing blind-debugging.

Sam Joshua
  • 310
  • 6
  • 17
Tagli
  • 2,412
  • 2
  • 11
  • 14
  • Thanks for the advice. Unfortunately ```registeraddress = 45``` did not work. I am still in the process of trying to locate a logic analyzer. Just a thought - with MBPOLL it seems the first request times out each time and then only work on subsequent requests (see first line of above screenshot). Is there a way for me to tell python to keep trying until it does manage to get a value rather than end after the first failure? Really appreciate all the efforts here! – Joshua Patterson May 29 '21 at 09:46
  • 1
    You may try reducing the timeout (0.1 seconds maybe), surrounding the read call with a *try - except* block for catching & ignoring `NoResponseError` in a loop. I have once designed a device which wakes up and responds Modbus quarries once in a second. But the manual of your device doesn't mention any sleep & wakeup mechanism. – Tagli May 29 '21 at 10:05
  • It might be time for me to start looking at other solutions whilst I get to the bottom of this. As a MODBUS expert would you have any advice on where I can start? I need to log data from these devices using a raspberry pi with the long term goal of being able to display it live – Joshua Patterson May 29 '21 at 10:52
  • Well, I don't consider myself as a Modbus expert, but I have some experince debugging communications, including USB FS. Again, the first thing you need to do is obtaining a logic analyzer. Even the cheapest ones will do. It saved me from countless hours (probably even days) of debugging time. – Tagli May 29 '21 at 11:06