Python minimalmodbus, how to read MODBUS RTU - python

Sorry for my bad English.
I am trying to read data by the Modbus RTU method (library: minimalmodbus) but have a problem.
This is my 'Modbus Poll' display.
I would like to read data by using minimalmodbus.
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 = 9600 # Baud
instrument.serial.bytesize = 8
instrument.serial.parity = serial.PARITY_NONE
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
result = instrument.read_float(0, 4, 20)
print(result)
But, I keep failing.
Can please someone help me with how to read data?

Related

Raspberry: USB to RS232 (FT4232H chip) can't communicate with Device via Python

I want to set up my serial connection to a pump with a USB to 4xRS232 (FT4232H chip). However, my serial connection can't write output from the pump. The whole setup works with a single usb to rs232 converter but not with my USB to 4xRS232 converter. My raspberry recognizes all 4 USB ports:
/dev/ttyAMA0: ttyAMA0 [fe201000.serial]
/dev/ttyUSB1: FT4232H Device
/dev/ttyUSB2: FT4232H Device
/dev/ttyUSB3: FT4232H Device
/dev/ttyUSB4: FT4232H Device
My code to write and read information with my external device looks as following:
import serial
import time
global ser
ser = serial.Serial()
ser.port = '/dev/ttyUSB1'
#ser.baudrate = 9600
ser.bytesize = serial.EIGHTBITS
ser.parity = serial.PARITY_NONE
ser.stopbits = serial.STOPBITS_ONE
ser.open()
encoding='utf-8'
def pump_loop():
if ser.inWaiting() == 0:
out_press=''
ser.write(b'PRESSURE?\r')
time.sleep(.1)
#print(ser.in_waiting)
#print(ser.read())
while int(ser.in_waiting) > 0:
out_press += str(ser.read(1), encoding)
print(out_press)
pump_loop()
Edit: Cable set-up is the following: raspberry-usb--> FT4232H --> 4 RS232 --> female-to-female coupling --> pump
Closed: had to swap PIN 2 and 3 before using the female-female coupler, as they are swapped inside.

no communication with the instrument (no answer) Minimalmodbus

I'm having problem with minimalmodbus library. The slave does not respond to the master's request, I want to request a read. I'm using the Raspberry Pi 3 Model B+, with Python 3.10.1, it's the minimalmodbus library with version 2.0. I'm using the Arduino Mega as a slave and I'm also using a Mini Adapter Serial Converter USB to RS485 is a Converter Module RS485 for Arduino.
import serial
import minimalmodbus
instrument = minimalmodbus.Instrument('COM6',1)
instrument.serial.baudrate = 9600
instrument.serial.timeout = 10
instrument.clear_buffers_before_each_transaction = True
instrument.debug = True
temperature = instrument.read_register(1,1)
print(temperature)
MinimalModbus debug mode. Will write to instrument (expecting 7 bytes back): 01 03 00 01 00 01 D5 CA (8 bytes)
MinimalModbus debug mode. Clearing serial buffers for port COM6
MinimalModbus debug mode. No sleep required before write. Time since previous read: 87898406.00 ms, minimum silent period: 4.01 ms.
MinimalModbus debug mode. Response from instrument: (0 bytes), roundtrip time:10.0 ms. Timeout for reading: 0 ms.
I have a similar issue on Win10 python 3.7.9. When I write
import minimalmodbus
instrument = minimalmodbus.Instrument('COM3', 2)
instrument.serial.baudrate = 9600
instrument.clear_buffers_before_each_transaction = True
reg_0 = instrument.read_register(0, 0)
reg_1 = instrument.read_register(1, 0)
print(reg_0)
print(reg_1)
instrument.serial.close()
I have error message "minimalmodbus.NoResponseError: No communication with the instrument (no answer)". But, when I write
import minimalmodbus
instrument = minimalmodbus.Instrument('COM3', 2)
instrument.serial.baudrate = 9600
instrument.clear_buffers_before_each_transaction = True
instrument.debug = True
reg_0 = instrument.read_register(0, 0)
reg_1 = instrument.read_register(1, 0)
print(reg_0)
print(reg_1)
instrument.serial.close()
all works correct.
I use Arduino UNO as a slave. In ModbusPoll both registrers read correct
Upd. When I use construction "try... except..." all works correctly
import minimalmodbus
instrument = minimalmodbus.Instrument('COM3', 2)
instrument.serial.baudrate = 9600
instrument.clear_buffers_before_each_transaction = True
try:
reg_0 = instrument.read_register(0, 0)
except minimalmodbus.NoResponseError:
reg_0 = instrument.read_register(0, 0)
reg_1 = instrument.read_register(1, 0)
print(reg_0)
print(reg_1)
instrument.serial.close()

Pyserial package doesnot read all the data from the COM port ( reads only 6000 to 6150 bytes always)

I wrote a small pyserial interface to read the data from the COM port after issuing a command. For eg : in my case my system has a lot of network interface so i need to validate whether all the interfaces are up using ifconfig command. But when i gave this command , the output of the command is getting truncated at the last few lines. The approximate size of the output in bytes would be 6500-7000 bytes but i am receiving only around 6000-6150 bytes all the time. Please find my code below
'''
import serial
import time
com_serial = serial.Serial("COM6", 115200, timeout = 10)
com_serial.reset_input_buffer()
com_serial.write(b"ifconfig\n")
data_all = b" "
time.sleep(5)
while True:
bytetoread = com_serial.inWaiting()
time.sleep(2)
print ("Bytetoread: " , bytetoread)
data = com_serial.read(bytetoread)
data_all += data
if bytetoread < 1:
break
print ("Data:", data_all)
com_serial.close()
'''
**Output:
Bytetoread: 3967
Bytetoread: 179
Bytetoread: 2049
Bytetoread: 0
**
Data: *********with missing data at the end.
I am not sure why the logs are missing?
I have tried another approach.
'''
import serial
import time
com_serial = serial.Serial("COM6", 115200, timeout = 10)
com_serial.reset_input_buffer()
com_serial.write(b"ifconfig\n")
time.sleep(5)
data_all = b" "
data_all = com_serial.read(100000000)
print (data_all)
com_serial.close()
'''
Here also the last few logs are getting truncated.
The root cause seems to be inadequate buffer size of the Tx and Rx serial buffer. By increasing the buffer size using .set_buffer_size() resolved the issue.
'''
import serial
import time
com_serial = serial.Serial("COM6", 115200, timeout = 10)
com_serial.set_buffer_size(rx_size = 12800, tx_size = 12800)
com_serial.reset_input_buffer()
com_serial.write(b"ifconfig\n")
data_all = b" "
data_all = com_serial.read(100000000)
print (data_all)
com_serial.close()
'''

Pymodbus : Wrong byte count in response

We are requesting 14 responses from an RS485 device and sometimes the reply that we got doesn't have the 9 bytes that it is set-up. That is because it's replying sometimes in 3 arguments.
Normal:
CALL-> 01 04 00 00 00 02 71 CB
RESPONSE-> 01 04 04 43 59 E6 66 F4 59
Error:
CALL-> 01 04 00 00 00 02 71 CB
RESPONSE -> 01 04 04 43
59 CC CD AA 86
When the error happens i get this msg from pymodbus:
DEBUG:pymodbus.transaction: Incomplete message received, Expected 9 bytes Recieved 4 bytes !!!!
DEBUG:pymodbus.transaction:Changing transaction state from 'WAITING FOR REPLY' to 'PROCESSING REPLY'
DEBUG:pymodbus.transaction:RECV: 0x1 0x4 0x4 0x3e
DEBUG:pymodbus.framer.rtu_framer:Frame check failed, ignoring!!
DEBUG:pymodbus.framer.rtu_framer:Resetting frame - Current Frame in buffer - 0x1 0x4 0x4 0x3e
DEBUG:pymodbus.transaction:Getting transaction 1
DEBUG:pymodbus.transaction:Changing transaction state from 'PROCESSING REPLY' to 'TRANSACTION_COMPLETE'
i've tried putting a sleep into the for so it dosn't colapse the device with calls but i get them either way. i've also read https://wingpath.co.uk/docs/modtest/troubleshoot.html
and they say this:
"Wrong byte count in response: XXX when expecting XXX"
The byte count in the response sent by the slave is not what was expected for the count that ModTest sent in the request.
Turn on tracing to get more information.
Check that your slave is functioning correctly.
If you want ModTest to accept the response even though it is incorrect, you could deselect Strict Checking.
But i don't know how to active tracing on PYMODBUS, the function is correct and the other one is for a lib that i am not using i think
THE CODE LOOKS LIKE THIS
from __future__ import division
import pymodbus
import serial
from pymodbus.pdu import ModbusRequest
from pymodbus.client.sync import ModbusSerialClient as ModbusClient #initialize a serial RTU client instance
from pymodbus.transaction import ModbusRtuFramer
from time import sleep
from pymodbus.constants import Endian # Nodig voor 32-bit float getallen (2 registers / 4 bytes)
from pymodbus.payload import BinaryPayloadDecoder # Nodig voor 32-bit float getallen (2 registers / 4 bytes)
from pymodbus.payload import BinaryPayloadBuilder # Nodig om 32-bit floats te schrijven naar register
import logging
logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)
#
method = "rtu"
port = "COM1"
baudrate = 2400
stopbits = 1
bytesize = 8
parity = "N"
timeout = 10 # I SET THIS TO 10 MAYBE IT WOULD HELP BUT DIDN'T
retries = 5 # SAME THING WITH THIS ONE
#
try:
client = ModbusClient(method = method, port = port, stopbits = stopbits, bytesize = bytesize, parity = parity, baudrate = baudrate, timeout = timeout, retries = retries)
connection = client.connect()
print (connection)
except:
print ("Modbus connectie error")
#
def 420 (y):
variables = [0,6,12,18,24,30,36,70,72,74,76,78,342,344]
labels = ["Voltage","Corriente","Potencia_Activa","Potencia_Aparente","Potencia_Reactiva","Factor_Potencia","Angulo_Fase","Frecuencia","Potencial_Activa_Consumida","Potencia_Activa_Inyectada","Potencia_Reactiva_Consumida","Potencia_Reactiva_Inyectada","Energia_Activa_Total","Energia_Reactiva_Total"]
unidades = ["V","A","W","VA","VAr","%","Grados","HZ","kWh","kWh","kVArh","kVArh","kWh","kVArh"]
LISTA = []
h = 0
hh = 0
for xx in variables:
try:
data = client.read_input_registers(xx, 2, unit=1)
decoder = BinaryPayloadDecoder.fromRegisters(data.registers, Endian.Big)
eastron = round(decoder.decode_32bit_float(), 3)
weaito = str(labels[h]) + " = " + str(eastron) + " " + str(unidades[hh])
LISTA.append(weaito)
h = h + 1
hh = hh + 1
sleep(0.5)
except:
print ("PICO")
sleep(1)
print(LISTA)
I Would love a way around the problem, maybe just consulting again until i get the right answer. I am not good with the try and except ones maybe there is the answer.
You seem to be experiencing a known issue with interchar spacing.
There is an easy workaround though. First, make sure you're on pymodbus version 2.2.0 (you can do that opening a command line terminal on Windows and typing pip list if you have the path setup correctly, otherwise you have to move to your scripts Python folder where pip.exe is stored).
Then change your code to add the strict argument declared to False:
....
client = ModbusClient(method = method, port = port, stopbits = stopbits, bytesize = bytesize, parity = parity, baudrate = baudrate, timeout = timeout, retries = retries)
client.strict = False #Use Modbus interchar spacing as timeout to prevent missing data for low baudrates
connection = client.connect()
...
This will define the character spacing according to the Modbus spec, to 1.5 times the bit time at the selected baudrate, instead of using the default from socket.interCharTimeout:
self._t0 = float((1 + 8 + 2)) / self.baudrate
self.inter_char_timeout = 1.5 * self._t0
If you can fix your issue with this solution, you should be able to reduce the overhead on your device reading the 28 registers you want in one go.
If your issue is not solved, I think you might have a hardware issue. I would advise you to put your code aside for a while and try reading registers with QModMaster or something similar to make sure your hardware is performing as intended. (you might be getting noise or grounding issues that cut your frames short, if you want some pointers on that front please edit your question to include more details on your hardware, i.e.: kind of devices and how you're connecting them).

Receive UDP packet from specific source

I am trying to measure the responses back from DNS servers. Making a sniffer for a typical DNS response that is less than 512 bytes is no big deal. My issue is receiving large 3000+ byte responses - in some cases 5000+ bytes. I haven't been able to get a socket working that can receive that data reliably. Is there a way with Python sockets to receive from a specific source address?
Here is what I have so far:
import socket
import struct
def craft_dns(Qdns):
iden = struct.pack('!H', randint(0, 65535))
QR_thru_RD = chr(int('00000001', 2)) # '\x01'
RA_thru_RCode = chr(int('00100000', 2)) # '\x00'
Qcount = '\x00\x01' # question count is 1
ANcount = '\x00\x00'
NScount = '\x00\x00'
ARcount = '\x00\x01' # additional resource count is 1
pad = '\x00' #
Rtype_ANY = '\x00\xff' # Request ANY record
PROtype = '\x00\x01' # Protocol IN || '\x00\xff' # Protocol ANY
DNSsec_do = chr(int('10000000', 2)) # flips DNSsec bit to enable
edns0 = '\x00\x00\x29\x10\x00\x00\x00\x00\x00\x00\x00' # DNSsec disabled
domain = Qdns.split('.')
quest = ''
for x in domain:
quest += struct.pack('!B', len(x)) + x
packet = (iden+QR_thru_RD+RA_thru_RCode+Qcount+ANcount+NScount+ARcount+
quest+pad+Rtype_ANY+PROtype+edns0) # remove pad if asking <root>
return packet
def craft_ip(target, resolv):
ip_ver_len = int('01000101', 2) # IPvers: 4, 0100 | IP_hdr len: 5, 0101 = 69
ipvers = 4
ip_tos = 0
ip_len = 0 # socket will put in the right length
iden = randint(0, 65535)
ip_frag = 0 # off
ttl = 255
ip_proto = socket.IPPROTO_UDP # dns, brah
chksm = 0 # socket will do the checksum
s_addr = socket.inet_aton(target)
d_addr = socket.inet_aton(resolv)
ip_hdr = struct.pack('!BBHHHBBH4s4s', ip_ver_len, ip_tos, ip_len, iden,
ip_frag, ttl, ip_proto, chksm, s_addr, d_addr)
return ip_hdr
def craft_udp(sport, dest_port, packet):
#sport = randint(0, 65535) # not recommended to do a random port generation
udp_len = 8 + len(packet) # calculate length of UDP frame in bytes.
chksm = 0 # socket fills in
udp_hdr = struct.pack('!HHHH', sport, dest_port, udp_len, chksm)
return udp_hdr
def get_len(resolv, domain):
target = "10.0.0.3"
d_port = 53
s_port = 5353
ip_hdr = craft_ip(target, resolv)
dns_payload = craft_dns(domain) # '\x00' for root
udp_hdr = craft_udp(s_port, d_port, dns_payload)
packet = ip_hdr + udp_hdr + dns_payload
buf = bytearray("-" * 60000)
recvSock = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.ntohs(0x0800))
recvSock.settimeout(1)
sendSock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW)
sendSock.settimeout(1)
sendSock.connect((resolv, d_port))
sendSock.send(packet)
msglen = 0
while True:
try:
pkt = recvSock.recvfrom(65535)
msglen += len(pkt[0])
print repr(pkt[0])
except socket.timeout as e:
break
sendSock.close()
recvSock.close()
return msglen
result = get_len('75.75.75.75', 'isc.org')
print result
For some reason doing
pkt = sendSock.recvfrom(65535)
Recieves nothing at all. Since I'm using SOCK_RAW the above code is less than ideal, but it works - sort of. If the socket is extremely noisy (like on a WLAN), I could end up receiving well beyond the DNS packets, because I have no way to know when to stop receiving packets when receiving a multipacket DNS answer. For a quiet network, like a lab VM, it works.
Is there a better way to use a receiving socket in this case?
Obviously from the code, I'm not that strong with Python sockets.
I have to send with SOCK_RAW because I am constructing the packet in a raw format. If I use SOCK_DGRAM the custom packet will be malformed when sending to a DNS resolver.
The only way I could see is to use the raw sockets receiver (recvSock.recv or recvfrom) and unpack each packet, look if the source and dest address match within what is supplied in get_len(), then look to see if the fragment bit is flipped. Then record the byte length of each packet with len(). I'd rather not do that. It just seems there is a better way.
Ok I was stupid and didn't look at the protocol for the receiving socket. Socket gets kind of flaky when you try to receive packets on a IPPROTO_RAW protocol, so we do need two sockets. By changing to IPPROTO_UDP and then binding it, the socket was able to follow the complete DNS response over multiple requests. I got rid of the try/catch and the while loop, as it was no longer necessary and I'm able to pull the response length with this block:
recvSock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_UDP)
recvSock.settimeout(.3)
recvSock.bind((target, s_port))
sendSock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW)
#sendSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sendSock.settimeout(.3)
sendSock.bind((target, s_port))
sendSock.connect((resolv, d_port))
sendSock.send(packet)
pkt = recvSock.recvfrom(65535)
msglen = len(pkt[0])
Now the method will return the exact bytes received from a DNS query. I'll leave this up in case anyone else needs to do something similar :)

Categories