Pymodbus Read/Write Floats (REAL)

12,092

Solution 1

TLDR:

Use:

decoder = BinaryPayloadDecoder.fromRegisters(result.registers, Endian.Big, wordorder=Endian.Little)

instead of:

decoder = BinaryPayloadDecoder.fromRegisters(result.registers, endian=Endian.Little)

Explanation attempt:

Arguments for BinaryPayloadDecoder.fromRegisters for this package are described in docs.

In this case we should pay attention to:

byteorder – The Byte order of each word

wordorder – The endianess of the word (when wordcount is >= 2)

I belive for a value that fits in a modbus register (2 bytes) byteorder is always must be a Endian.Big

wordorder is something like byteorder but for modbus registers.

For Modbus TCP always must be that is byteorder=Endian.Big, wordorder=Endian.Little because byteorder for the values, consisting of more than 2 bytes are exactly specified in the protocol spec OPEN MODBUS/TCP SPECIFICATION(Appendix B. Data Encoding for non-word data) .

For Modbus RTU byteorder for the values, consisting of more than 2 bytes are not exactly specified in the protocol spec Somehow described here.

Most of the implementations takes the Modbus TCP approach and transmitting floats as [2, 1, 4, 3] bytes.

However, there are other possibilities:

  • [4, 3, 2, 1] - byteorder=Endian.Big, wordorder=Endian.Big
  • [3, 4, 1, 2] - byteorder=Endian.Little, wordorder=Endian.Big
  • [1, 2, 3, 4] - byteorder=Endian.Little, wordorder=Endian.Little

Solution 2

I got it to work with libmodbus in C++:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

#include <vector>
#include <iostream>

using namespace std;

extern "C"  // needed to integrate C code into C++
{
    #include "libmodbus/modbus.h"
}

int main (void)
{
    modbus_t *ctx;
    uint16_t read_reg[64];
    uint16_t write_float_holder[2];
    uint16_t read_float_holder[2];
    vector <float> write_reg = {77.77, 15.69, 42.78, 50153.33, -56.23};
    int rc, start_element = 0, j = 0;
    float rc_f[10];

    ctx = modbus_new_tcp("192.168.2.101", 502); // PLC IP address and default Modbus Port (502)
    if (modbus_connect(ctx) == -1) {
        fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
        modbus_free(ctx);
        return -1;
    }

    ///// Reading 402000
    /// REAL array in PLC
    // array fills up right to left with hex: 0x[1][0] >> 0x4174 0x28f6 >> read_from_plc[0] = 15.26
    if (1) 
    {
        rc = modbus_read_registers(ctx, 2000, 8, read_reg); // (connection, start position, how many to read, what read values go into)
        if (rc == -1) {
            fprintf(stderr, "%s\n", modbus_strerror(errno));
            return -1;
        }
        for (int i = 0; i < rc; i++) {
            printf("read_reg[%d]=%d (0x%x)\n", i, read_reg[i], read_reg[i]);
        }
        for (int i = 0; i < rc; i += 2)
        {
            read_float_holder[0] = read_reg[i];
            read_float_holder[1] = read_reg[i + 1];
            rc_f[j] = modbus_get_float(read_float_holder);      
            printf("rc_f[%d]: %f\n", j, rc_f[j]);
            j++;    
        }
    }

    ///// Writing 40000
    /// REAL array in PLC
    if (1)
    {
        for (size_t i = 0; i < write_reg.size(); i++)   // write two registers (32-bit float) at a time
        {
            modbus_set_float(write_reg[i], write_float_holder); // set float to hexadecimal
            rc = modbus_write_registers(ctx, start_element, 2, write_float_holder);
            start_element += 2;
            cout << "modbus_set_float: writing write_reg[" << i << "] = " << write_reg[i] << "\trc: " << rc << endl;
        }       
    }

    // close connection and free memory
    modbus_close(ctx);
    modbus_free(ctx);   

    return 0;
}
Share:
12,092
xinthose
Author by

xinthose

I like to work with C++ and Boost on hardware (card readers, PLC, LCD displays, kiosk printers, etc.) in Linux. I also love to work with Node.js, Angular 13, Express, TypeScript, Kendo UI, MDBootstrap, and Websockets. I use Ubuntu as my regular desktop. I am a Christian (Baptist) that enjoys going to church every Wednesday night and Sunday.

Updated on June 25, 2022

Comments

  • xinthose
    xinthose almost 2 years

    I have modbus mapping setup in my AB Micro820 PLC. I have an array in 40001 for writing, and one in 42001 for reading. Both are 200 elements and REAL type (32-bit float). I can write and read currently, so I know the code works, just incorrectly. The values are read/wrote as very small values (i.e. 4.58577478E-19). Can anyone point me in the right direction?

    #!/usr/bin/env python
    
    from pymodbus.constants import Endian
    from pymodbus.payload import BinaryPayloadDecoder
    from pymodbus.payload import BinaryPayloadBuilder
    from pymodbus.client.sync import ModbusTcpClient
    
    import logging
    logging.basicConfig()
    log = logging.getLogger()
    log.setLevel(logging.INFO)
    
    ip_address = "192.168.2.101"
    
    client = ModbusTcpClient(ip_address)
    if client.connect():    # connection is OK
        # write float
        builder = BinaryPayloadBuilder(endian=Endian.Little)
        builder.add_32bit_float(77.77)
        payload = builder.build()
        result  = client.write_registers(1, payload, skip_encode=True)
        # read floats
        result  = client.read_holding_registers(2001, 4)
        decoder = BinaryPayloadDecoder.fromRegisters(result.registers, endian=Endian.Little)
        print "read_holding_registers: " + str(decoder.decode_32bit_float())
    
        client.close()
    
    • Kevin Herron
      Kevin Herron almost 9 years
      Are you sure about the Endian-ness? Also it looks like you're reading 4 registers. You should only need to read 2. Maybe that's somehow contributing. When writing you need to write to 2 registers, not 1. I don't know if Pymodbus handles this for you or not, however.
    • xinthose
      xinthose almost 9 years
      @KevinHerron these are good points; when I write, it does write garbage to two array elements; reading with 2 still gives me garbage