Pymodbus : Wrong byte count in response - python

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).

Related

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()

Python minimalmodbus, how to read MODBUS RTU

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?

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()
'''

ARP request without reply raw sockets python

I'm trying to get a arp reply after send ARP request but the reply is not comming.
I had a look to wireshark for the results and i think he does the broadcast to the network, but no reply show up...
In results of wireshark the MAC addr of sender and receiver is do not correspond to the real MAC addr, im bealive i'm not packing this right but i dont understand why.
need help...
#!/usr/bin/env python3
import struct
import socket
raw = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(0x0806))
raw.bind(("wlp3s0", socket.htons(0x0806)))
mac_local = b"ff:ff:ff:ff:ff:ff" # mac de quem envia request
ip_local = "192.168.1.7" # ip de quem envia request
mac_dest = b"00:00:00:00:00:00" # mac de quem recebe request
ip_dest = "192.168.1.2" # ip de quem recebe request
# Ethernet Header
protocol = 0x0806 # 0x0806 protocol to ARP
ethernet_header = struct.pack("!6s6sH", mac_dest, mac_local, protocol)
# ARP header
type_hardware = 1
type_protocol = 0x0800 # IPV4
size_addr_hardware = 6 # Refere ao tamanho do endereço do MAC que é
48 bits == 6 bytes
size_addr_protocol = 4 # Refere ao tamanho do endereço do ipv4 que é
32 bits == 4 bytes
operation = 1 # 1 = request / 2 = Reply
source_ip = socket.inet_aton(ip_local)
dest_ip = socket.inet_aton(ip_dest)
arp_addr = struct.pack("!HHBBH6s4s6s4s", type_hardware, type_protocol,
size_addr_hardware, size_addr_protocol, operation,
mac_local, source_ip, mac_dest, dest_ip)
pkt = ethernet_header + arp_addr
cont = 0
while cont < 6:
raw.send(pkt)
cont +=1
enter image description here
enter image description here
mac_dest and mac_local are definitely not right. You've just created a byte string with the ASCII value. Each of those is 17 bytes long. And you're just taking the first 6 of those 17 bytes for each of the addresses.
They should be something like this instead:
mac_dest = b'\x00\x00\x00\x00\x00\x00'
mac_local = b'\xff\xff\xff\xff\xff\xff'
Check that the length of the byte string before the struct.pack call is exactly six bytes.
Also, not sure what you're trying to do, but I doubt it makes sense to use the all-zero hardware address as a destination address. Pretty sure no one will receive that as it would be a unicast to an address that no one has. The opposite might be helpful (send to the broadcast address from all-zero) -- I think that's standard for ARP probes.
I'm somehow confused about the accepted answer.
Is MAC 00:00:00: 00:00:00 not a valid MAC from XEROX?
https://hwaddress.com/company/xerox-corporation/
When I see ARP-request captured with e.g wireshark
then mac adresses in requests are always like
MAC_DST: FF:FF:FF:FF:FF:FF //mac broadcast
MAC_SRC: MAC-address of requester
the mac adresses in reply would then be
MAC_DST: MAC-adress of requester
MAC_SRC: MAC-address of replier

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