1

The idea of the project is to get the data from the RS485 devices and write it for visualisation later.

I have raspberry pi 4 with 4GB RAM with this hat and this sensor.

I'm not sure how should I send and receive the data from the sensor. From its documentation:

THT-02 follows the RTU information frame protocol. In order to ensure the integrity of the information
frame, a pause time of 3.5 characters or more is required at the beginning and end of each information
frame, each byte of the information frame needs to be transmitted continuously. If there is a pause time greater than 1.5
characters, the sensor will treat it as invalid information and will not respond.

The sensor allows the host to use the function code 03 to read the temperature and humidity measurement
value of the sensor and other information. The information frame format of the 03 code is as follows:

Field Description Example

Slave address               01
Function code               03
Register address high byte  00
Register address low byte   00
High byte of query quantity 00
Low byte of query quantity  08
CRC check code low byte     44
CRC check code high byte    0C

Sensor response information frame

Slave address               01
Function code               03
Return the number of bytes  10
Temperature data high byte  00
Temperature data low byte   FA
Humidity data high byte     02
Low byte of humidity data   58
1 high byte reserved        00
1 low byte reserved         00
2 high byte reserved        00
2 low byte reserved         00
Address code high byte      00
Address code low byte       01
Baud rate high byte         25
Baud rate low byte          80
Hardware version high byte  06
Hardware version low byte   00
Software version high byte  00
Software version low byte   0A
CRC check code low byte     D4
CRC check code high byte    64

As I don't really get the idea of the modbus communication I think that these values should be the request I send to the device to get a valid responce. This is the code I'm trying to use with pyserial:

import time
import sys
import serial

ser = serial.Serial(
    port='/dev/ttyS0', baudrate=9600,
    parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE,
    bytesize=serial.EIGHTBITS, xonxoff=False,
    rtscts=True, dsrdtr=True, timeout=3)

print(ser.name)
ser.write(br'\x01\x03\x00\x00\x00\x08\x44\x0C\r\n')
ret=str(ser.readlines())
print(ret)

This gets me an empty list. From here the message has a structure like 11 03 006B 0003 7687

How do I structure the messages to the device to get a proper response?

UPDATE So as I read the gpio pins on the pi don't support parity. For that reason I managet to get a hold on one of these. With that the working code is utilising the pymodbus library:

from pymodbus.client.sync import ModbusSerialClient as ModbusClient

client= ModbusClient(
    method = "RTU", 
    port = "/dev/ttyUSB0", 
    stopbits = 1, 
    bytesize = 8, 
    parity = 'N', 
    baudrate=9600)

client.strict = False
connection = client.connect()

FORMAT = ('%(asctime)-15s %(threadName)-15s'
' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s')
logging.basicConfig(format=FORMAT)
log = logging.getLogger()
log.setLevel(logging.DEBUG)

rr = client.read_holding_registers(0, 2, unit=5)
log.debug(rr)
print(rr.registers)

client.close()

Output is a list with all the data from the thermometer.

UPDATE 2 As stated in the comments the pymodbus serial is slow for a fast response like the device I have, so I managed to get a some response with this:

import time, serial
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BOARD)
GPIO.setup(12, GPIO.OUT)

s = serial.Serial(
    port = '/dev/ttyS0',
    baudrate = 9600,
    parity = serial.PARITY_NONE,
    stopbits = serial.STOPBITS_ONE,
    bytesize = serial.EIGHTBITS,
    xonxoff = False,
    rtscts = False, 
    dsrdtr =True,
    timeout = 1
)

temp = b'\x05\x03\x00\x00\x00\x01\x85\x8e' 
#temp response \x03\x02\x00\xea\xc8\x0b => \x00\xea = 234 => 23.4 C

humidity = b'\x05\x03\x00\x01\x00\x01\xd4\x4e' 
# humidity response  \x03\x02\x01\xda\xc9\x8f => \x01\xdd = 474 => 47.4 %

GPIO.output(12, 1)
time.sleep(0.01)
#s.write(temp)
s.write(humidity)
time.sleep(0.01)
GPIO.output(12, 0)
res = s.readlines()
print('reading')
print(res)

GPIO.cleanup()
s.close()

It's not the most elegant solution, but at least I get a responses from it. But again it's not always good for measurment - sometimes the response doesn't contain the whole message and I end up with unusable string.

UPDATE 3 After talking with @MarcosG for implementing the pymodbus and libmodbus method for communication:

from pylibmodbus import ModbusRtu


# For Python 3.x you have to explicitly indicate ASCII enconding
client=ModbusRtu(
    device="/dev/ttyS0".encode("ascii"), 
    baud=9600, 
    parity="N".encode("ascii"), 
    data_bit=8, 
    stop_bit=1
)

#Read and set timeout
timeout_sec = client.get_response_timeout()
print(timeout_sec)
client.set_response_timeout(timeout_sec+1)

#Connect
client.connect()

SERVER_ID=5
BCM_PIN_DE=18
BCM_PIN_RE=17

#Set Slave ID number
client.set_slave(SERVER_ID)

#Enable RPi GPIO Functions
client.enable_rpi(1)

#Define pin numbers to be used as Read Enable (RE) and Drive Enable (DE)
client.configure_rpi_bcm_pins(BCM_PIN_DE,BCM_PIN_RE)

#Export pin direction (set as outputs)
client.rpi_pin_export_direction()

#Write Modbus registers, 10 starting from 0
#client.write_registers(0, [0]*10)

#Read 10 input registers starting from number 0
result=(client.read_registers(0, 1))

#Show register values
print(result[0])

#Release pins and close connection
client.rpi_pin_unexport_direction()
client.close()

The problem with the pylibmodbus not working was a conflict between the new and the original libmodbus library. The device responds to the code (blinks) and in terminal I receive the value I'm looking for - 247 => 24.7C.

Tony
  • 618
  • 12
  • 27
  • 1
    Modbus RTU is a binary format (so you should not be sending `\r\n`) - the answers to [this question](https://stackoverflow.com/q/17589942/11810946) may help. If you are not sure of your wiring then starting with a known good application (such as [mbpoll](https://github.com/epsilonrt/mbpoll)) lets you confirm everything works before writing your own code (and there are a number of libraries that can handle the comms for you). – Brits Sep 30 '21 at 21:54
  • Thanks, but there seems to be something that I'm clearly missing - when I use `mbpoll -a 1 -b 9600 -m rtu -t 4 -P none -r 1 -c 1 /dev/ttyUSB0 ` I keep getting `connection timed out` the address of the device is set by DIP switches and is currently `1`. On the other hand when I try my code without the `\r\n` i get responce `\x00`. – Tony Oct 01 '21 at 08:25
  • 1
    The [example code](https://wiki.seeedstudio.com/RS-485_Shield_for_Raspberry_Pi/#getting-started) for the hat indicates that the appropriate port is `/dev/ttyS0` (I have not used one of these units) - running `dmesg` should provide some info. – Brits Oct 01 '21 at 19:55
  • 1
    Great to see that you have this working with the USB adapter. Took a further look at the hat documentation and it looks like this unit requires you to set it into send or receive mode (see `Tx_Enable = LED(18)` and related code in the demo). – Brits Oct 04 '21 at 19:20
  • @Brits yes I thought about that too, but in the example I need to have 2 rpis and 2 hats to make it work as I need it to - send a signal and wait for response. I'm not sure if it can be done with the hat. Other thing I noticed is that this hat can do `8N1` but throws an error when the device needs odd or even parity for serial connection. – Tony Oct 04 '21 at 19:42
  • 1
    The demo code uses two Pi's/Hats but the same technique applies when communicating with something else (set pin before sending then reset to receive). Pi Serial may be able to do this for you with something like `serial.rs485.RS485Settings(False,True)` ([docs](https://pyserial.readthedocs.io/en/latest/pyserial_api.html#serial.rs485.RS485Settings) - I have not tried this!). – Brits Oct 04 '21 at 22:27
  • @Brits I understand the concept but I am not sure how make it happen. So with `Tx_Enable = LED(18)` before the message the device blinks (indicating receiving and sending the message) but I can't make it so I receive the output. – Tony Oct 06 '21 at 10:38
  • 1
    Unfortunately your updated code will not work because `client.read_holding_registers` will both send the request and attempt to receive the response (so the `tx.off()` is too late). You might find the answers to [this question](https://stackoverflow.com/q/56036578/11810946) useful. – Brits Oct 07 '21 at 20:47
  • @Brits yes it was, but after compiling and installing the libmodbus with GPIO support and marcus pylibmodbus i get an error that I haven't seen before `AttributeError: function/symbol 'modbus_configure_rpi_bcm_pins' not found in library 'libmodbus.so.5': /lib/arm-linux-gnueabihf/libmodbus.so.5: undefined symbol: modbus_configure_rpi_bcm_pins` but the compiling went without errors. – Tony Oct 08 '21 at 06:31
  • 1
    hello T0ny, you have to install the modified version of *libmodbus* with GPIO support first. Follow the steps under the line *solution using libmodbus instead* in my answer [here](https://stackoverflow.com/a/56106253/11476836) – Marcos G. Oct 08 '21 at 09:43
  • @MarcosG. You mean from [this](https://github.com/dhruvvyas90/libmodbus) repo? I did, and I got the error in the upper comment. I followed your beautiful guide step by steb and the compiling went without errors. – Tony Oct 08 '21 at 09:46
  • 1
    strange... did you run the *libmodbus* test? After reading your question more carefully, I don't think you need this kind of software signaling at all. Why are you using GPIO pins with your USB serial port? As far as I can see your port does support hardware signaling... I think your problem is coming from somewhere else. Let me think about it for a minute... – Marcos G. Oct 08 '21 at 09:52
  • @MarcosG. the solutions are for two types of hardware - usb-to-rs485 and a pi rs485 hat. With the usb I don't have a problem. The hat is what annoys me. I did run the test and I get this `./test: symbol lookup error: ./test: undefined symbol: modbus_enable_rpi`. With changed baudrate and device ID. – Tony Oct 08 '21 at 10:06
  • 1
    OK, now I understand, sorry, I was not up to speed with your thinking... I see your point, you have your hat and you want to use it... As far as I can tell, looking at the schematic it looks it should work perfectly fine. I don't have any serial ports with me right now but I can help you debug your error as soon as I get home. – Marcos G. Oct 08 '21 at 10:06
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/237952/discussion-between-t0ny1234-and-marcos-g). – Tony Oct 08 '21 at 10:07
  • @MarcosG. So I got a response `` - what do I do with it? Do I decode it somehow? – Tony Oct 08 '21 at 11:54
  • something seems to be off, you should not get the cdata type – Marcos G. Oct 13 '21 at 06:15
  • @MarcosG. I don't know, this happens with `print(result)`, with `print(result[0])` I get the first register. – Tony Oct 13 '21 at 06:35
  • 1
    Ah, I see. Then you got it to work, right? – Marcos G. Oct 13 '21 at 07:14
  • Yes, but not without your help. Please submit the solution for the recompiling and reinstalling the libmodbus library as an answer to get your internet points. – Tony Oct 13 '21 at 07:46
  • OK, thanks, I will write a short answer. – Marcos G. Oct 13 '21 at 08:21

1 Answers1

1

This is a textbook example of the software vs. hardware signaling on Modbus.

Your USB-RS485 provides automatic hardware signaling and it works out of the box. On the other hand, your hat uses one of the serial ports on your RPi and a GPIO line for signaling so you need to either toggle the line yourself within your code (which is, as you have already noticed very inefficient and will not work reliably for most scenarios) or use libmodbus as explained in my answer on the link above.

Just follow the steps on my answer and make sure you remove any other standard version of the libmodbus library you might have installed before with:

sudo apt-get purge libmodbus*

You can also use my python wrapper for libmodbus if you want to keep working with Python.

As I explained in my answer, this trick (software signaling) should work reliably for at-home projects but I would not recommend it for mission-critical or any other safety-related applications.

Marcos G.
  • 3,371
  • 2
  • 8
  • 16