Repository: stephane/libmodbus

Issue #358: modbus_get_float has inconsistent behavior
Created: 2016-09-23T22:52:05Z
libmodbus version
master(7acb7ea)
Operating system
Linux(Debian 8)
Expected behavior and Actual Behavior
Maybe I am missing how the modbus_get_float is supposed to work, but I was assuming that the 'ABCD' parts referred to the order in which they came in from the network.  My float comes in the following:
<0x07><0x7A><0x42><0x9B>
Assuming that byte 1 is the MSB, the device that I am talking to sends floats in the following format:
Byte 3, Byte 4, Byte 1, Byte 2
My assumption was that to convert this to a float that I could use, I would use modbus_get_float_cdab(), however this did not work properly.  modbus_get_float_badc() worked to get the bytes properly.
After more investigation, I was able to track this down to a combination of two factors: when libmodbus gets a word back from the device, it converts it to the proper processor-endianess by doing a bitwise-OR of the two bytes.  This also makes the modbus_get_float_XXX() methods non-portable because of how they convert the values from one format to another.  I was able to test this by running the following code in a Debian 8 MIPS chroot(which is in big-endian), and on my computer(x86).
FWIW, I am running libmodbus on ARM which is also little-endian.
The float value should be 77.514603
Steps to reproduce the behavior (commands or source code)
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <byteswap.h>

const int i = 1;
#define is_bigendian() ( (*(char*)&i) == 0 )

const uint8_t from_modbus[] = { 0x07, 0x7A, 0x42, 0x9B };

static void print_hex_data( const void* src ){
    const uint8_t* uintsrc = src;
    int x;

    for( x = 0; x < 4; x++ ){
        printf( "0x%02X ", uintsrc[ x ] );
    }
    printf( "\n" );
}

float modbus_get_float_abcd2(const uint16_t *src)
{
    float f; 
    uint32_t i;
printf( "abcd2 before conversion: \n" );
print_hex_data( src );

    i = ntohl(((uint32_t)src[0] << 16) + src[1]);
    memcpy(&f, &i, sizeof(float));

printf( "abcd2 after conversion: \n" );
print_hex_data( &f );

    return f;
}

/* Get a float from 4 bytes (Modbus) in inversed format (DCBA) */
float modbus_get_float_dcba2(const uint16_t *src)
{
    float f; 
    uint32_t i;
printf( "dcba2 before conversion: \n" );
print_hex_data( src );

    i = ntohl(bswap_32((((uint32_t)src[0]) << 16) + src[1]));
    memcpy(&f, &i, sizeof(float));
printf( "dcba2 after conversion: \n" );
print_hex_data( &f );

    return f;
}

/* Get a float from 4 bytes (Modbus) with swapped bytes (BADC) */
float modbus_get_float_badc2(const uint16_t *src)
{
    float f;
    uint32_t i;
printf( "badc2 before conversion: \n" );
print_hex_data( src );

    i = ntohl((uint32_t)(bswap_16(src[0]) << 16) + bswap_16(src[1]));
    memcpy(&f, &i, sizeof(float));
printf( "badc2 after conversion: \n" );
print_hex_data( &f );

    return f;
}

/* Get a float from 4 bytes (Modbus) with swapped words (CDAB) */
float modbus_get_float_cdab2(const uint16_t *src)
{
    float f;
    uint32_t i;
printf( "cdab2 before conversion: \n" );
print_hex_data( src );

    i = ntohl((((uint32_t)src[1]) << 16) + src[0]);
    memcpy(&f, &i, sizeof(float));
printf( "cdab2 after conversion: \n" );
print_hex_data( &f );

    return f;
}

float modbus_get_float_cdab_rm( const uint16_t* src ){
    float f;
    int32_t i;
    uint8_t a;
    uint8_t b;
    uint8_t c;
    uint8_t d;

    a = (src[ 0 ] >> 8 ) & 0xFF;
    b = (src[ 0 ] >> 0 ) & 0xFF;
    c = (src[ 1 ] >> 8 ) & 0xFF;
    d = (src[ 1 ] >> 0 ) & 0xFF;

    i = ( c << 24 ) |
        ( d << 16 ) |
        ( a << 8 ) |
        ( b << 0 );
    memcpy( &f, &i, 4 );

    return f;
}

int main( int argc, char** argv ){
    unsigned char endian[2] = {1,0};
    short x;
    uint16_t regs[ 2 ];

    x = *(short*)endian;
    printf( "is big? %d\n", is_bigendian() );
    printf( "x is %d\n", x );

    printf( "data in memory is: \n" );
    print_hex_data( from_modbus );

    regs[ 0 ] = (from_modbus[ 0 ] << 8) | from_modbus[ 1 ];
    regs[ 1 ] = (from_modbus[ 2 ] << 8) | from_modbus[ 3 ];

    printf( "data in memory after conversion is: \n" );
    print_hex_data( regs );

    printf( "regs[ 0 ] = %d\n", regs[ 0 ] );
    printf( "regs[ 1 ] = %d\n", regs[ 1 ] );

    printf( "\n" );
    printf( "float abcd2 = %f\n\n", modbus_get_float_abcd2( regs ) );
    printf( "float dcba2 = %f\n\n", modbus_get_float_dcba2( regs ) );
    printf( "float badc2 = %f\n\n", modbus_get_float_badc2( regs ) );
    printf( "float cdab2 = %f\n\n", modbus_get_float_cdab2( regs ) );

    printf( "float cdab_rm = %f\n\n", modbus_get_float_cdab_rm( regs ) );
}

Output(MIPS emulator):
is big? 1
x is 256
data in memory is: 
0x07 0x7A 0x42 0x9B 
data in memory after conversion is: 
0x07 0x7A 0x42 0x9B 
regs[ 0 ] = 1914
regs[ 1 ] = 17051

abcd2 before conversion: 
0x07 0x7A 0x42 0x9B 
abcd2 after conversion: 
0x07 0x7A 0x42 0x9B 
float abcd2 = 0.000000

dcba2 before conversion: 
0x07 0x7A 0x42 0x9B 
dcba2 after conversion: 
0x9B 0x42 0x7A 0x07 
float dcba2 = -0.000000

badc2 before conversion: 
0x07 0x7A 0x42 0x9B 
badc2 after conversion: 
0x7A 0x07 0x9B 0x42 
float badc2 = 176027269612373420053402500573691904.000000

cdab2 before conversion: 
0x07 0x7A 0x42 0x9B 
cdab2 after conversion: 
0x42 0x9B 0x07 0x7A 
float cdab2 = 77.514603

float cdab_rm = 77.514603

Output(x86_64):
is big? 0
x is 1
data in memory is: 
0x07 0x7A 0x42 0x9B 
data in memory after conversion is: 
0x7A 0x07 0x9B 0x42 
regs[ 0 ] = 1914
regs[ 1 ] = 17051

abcd2 before conversion: 
0x7A 0x07 0x9B 0x42 
abcd2 after conversion: 
0x07 0x7A 0x42 0x9B 
float abcd2 = -0.000000

dcba2 before conversion: 
0x7A 0x07 0x9B 0x42 
dcba2 after conversion: 
0x9B 0x42 0x7A 0x07 
float dcba2 = 0.000000

badc2 before conversion: 
0x7A 0x07 0x9B 0x42 
badc2 after conversion: 
0x7A 0x07 0x9B 0x42 
float badc2 = 77.514603

cdab2 before conversion: 
0x7A 0x07 0x9B 0x42 
cdab2 after conversion: 
0x42 0x9B 0x07 0x7A 
float cdab2 = 176027269612373420053402500573691904.000000

float cdab_rm = 77.514603

Potential fix: synexxus@a511768
(this is a fork which has other fixes in it, so I didn't create a PR)

Duplicate issue #378: typo in modbus_get_float documentation ?
Created: 2017-04-27T08:58:20Z
line 15 says that modbus_get_float_dcba() is the replacement for the
deprecated modbus_get_float(). at least on my Win7 32bit PC the above
modbus_get_float_dcba() function did not work, instead the function
modbus_get_float_badc() worked as a replacement for modbus_get_float().
i'm not sure if this is a type or a linux/windows byte-order thing ?
if it's a linux/windows thing, could be worth mentioning in the documentation,
that the replacement function is different for windows.

Closing comment (2017-06-21T04:55:36Z): Related to #358