0

I am using PySerial library to read an holding register value from a modbus rtu slave. I have an embedded gateway device with Unix O.S (so /dev/tty ports). and a modbus slave which could give me some metrics, such as frequency, temperature, etc. I tried to establish connection and read registers content with pymodbus library, using manufacturer specification on modbus connection on its device but it did not work. I got this error which requires further low level analysis:

AttributeError: 'ExceptionResponse' object has no attribute 'registers'

1st Step - Connection

I got easymodbus library, because it was easier to determine how connection is established and what fragments are delivered to the slave. Speaking of which, I noticed that on RS485 is necessary to configure further settings on pyserial. I made it with the following code

self.__ser.rs485_mode = serial.rs485.RS485Settings(rts_level_for_tx=self.rts_level_for_tx,rts_level_for_rx = self.rts_level_for_rx, delay_before_tx=self.delay_before_tx, delay_before_rx=self.delay_before_tx)

which reads the following values:

[RS485]
rts_level_for_tx = true
rts_level_for_rx = true
delay_before_tx = 2
delay_before_rx = 2

2nd Step - Reading holding register

Manufacturer parameters are reported below:

[ReadHoldingRegister]
deviceAddress = 1
memoryAddress = 7136
byteCount = 1
timeout = None
Multiply = 0
Divide = 0

The overall execution flow is reported below. As you can see, sender modbus message had been properly built. with CRC byte on the right but response on port raised a timeout error. It is raised when less byte data are received as expected.

2020-04-13 21:16:51,397 - ModbusClient - INFO - configuration ended
2020-04-13 21:16:51,403 - ModbusClient - INFO - connection on serial port with the following parameters established: {'dsrdtr': False, 'baudrate': 9600, 'parity': 'N', 'bytesize': 8, 'write_timeout': 10, 'timeout': 10.0, 'rtscts': False, 'stopbits': 1, 'inter_byte_timeout': None, 'xonxoff': False}
2020-04-13 21:16:51,404 - ModbusSerialReader - INFO - connection established
2020-04-13 21:16:51,406 - ModbusClient - INFO - data before Cyclic Redundancy Check
2020-04-13 21:16:51,408 - ModbusClient - INFO - data sent for read holding register function bytearray(b'\x01\x03\x1b\xdf\x00\x01\xb3\x14')
2020-04-13 21:17:01,416 - ModbusClient - INFO - data read from serial port bytearray(b'\x01\x83\x02\xc0\xf1')
2020-04-13 21:17:01,420 - ModbusSerialReader - ERROR - Exception Reading holding registers: 'TimeoutError'

Further details about this behaviour are shown on the below code.

def read_holdingregisters(self, starting_address, quantity):
        """
        Read Holding Registers from Master device (Function code 3)
        starting_address: First holding register to be read
        quantity:  Number of holding registers to be read
        returns:  Int Array [0..quantity-1] which contains the holding registers
        """
        self.__transactionIdentifier+=1
        if (self.__ser.closed):
            raise Exception.SerialPortNotOpenedException("serial port not opened")
        if ((starting_address > 65535) | (quantity >125)):
            raise ValueError("Starting address must be 0 - 65535; quantity must be 0 - 125");
        function_code = 3
        length = 6;
        transaction_identifier_lsb = self.__transactionIdentifier&0xFF
        transaction_identifier_msb = ((self.__transactionIdentifier&0xFF00) >> 8)
        length_lsb = length&0xFF
        length_msb = (length&0xFF00) >> 8
        starting_address_lsb = starting_address&0xFF
        starting_address_msb = (starting_address&0xFF00) >> 8
        quantity_lsb = quantity&0xFF
        quantity_msb = (quantity&0xFF00) >> 8

        data = bytearray([self.__unitIdentifier, function_code, starting_address_msb, starting_address_lsb, quantity_msb, quantity_lsb, 0, 0])

        self.logger.info('data before Cyclic Redundancy Check'.format(bytearray(data)))

        crc = self.__calculateCRC(data, len(data)-2, 0)
        crcLSB = crc&0xFF
        crcMSB = (crc&0xFF00) >> 8
        data[6] = crcLSB
        data[7] = crcMSB

        self.logger.info('data sent for read holding register function {0}'.format(bytearray(data)))

        self.__ser.write(data)
        bytes_to_read = 5+int(quantity*2)
        data = self.__ser.read(size=bytes_to_read)
        b=bytearray(data)
        data = b

        self.logger.info("data read from serial port", data)

        if (len(data) < bytes_to_read):
           self.logger.exception('Exception Reading holding registers: TimeoutError')
        ...

Please if you need any further details, do not hesitate to comment on this question. Thank you

johnny_kb
  • 694
  • 9
  • 27
  • Your modbus slave is returning an error (Illegal Data Address). It looks like easymodbus does not handle this well (it calculates the number of bytes expected if the command is successful and waits for that without checking for errors from the slave). – Brits Apr 14 '20 at 01:14
  • It is really awkward, because I used the same parameters in another manufacturer's gateway and it gets values. Retrieval is working with another modbus slave. I plugged on the serial port another modbus sensor type (a temperature and humidity meter) and it gave me temperature value. What should I do to properly analyse error read on the previous modbus sensor? – johnny_kb Apr 14 '20 at 07:58
  • The error means "The data address received in the query is not an allowable address for the server" [ref](http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf). So its likely that you are requesting registers that this specific device does not have (every device is different). I would need details of the device to comment further. – Brits Apr 15 '20 at 07:49
  • Please apologise my llate answer. Finally it was related to the modbus sensor. I changed it and it worked fine. I also changed the bytecount to 2, according to the vendor manual. Thank you for your help. – johnny_kb May 15 '20 at 13:50

0 Answers0