Skip to content

Commit

Permalink
implemented 32bit float for all 4 types of endianess: LE, BE, MBE, M…
Browse files Browse the repository at this point in the history
…LE (#35)

* renamed 'endianess' macros. Added new: MBE, MLE
* implemented 32bit float for all 4 types of endianess: LE,BE, MBE, MLE with tests. 
* implemented LE,BE,MLE,MBE for 32bit unsigned int(long). Fixes #34
* implemented 64bit tests and BE,LE,MBE,MLE endianess for uint64, double
* renamed INT64 -> UINT 64
* Dropped INT64 support. Added INT32 support datatype.
  • Loading branch information
v-zhuravlev authored Sep 1, 2018
1 parent 7cd1e99 commit 1faf536
Show file tree
Hide file tree
Showing 7 changed files with 308 additions and 173 deletions.
27 changes: 12 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,28 +95,25 @@ and some optional params can be provided as well:
* **datatype(optional):**
provide datatype as single char:
`b` - for MODBUS_BIT
`i` - for MODBUS_INTEGER (unsigned)
`s` - for MODBUS_SIGNED_INT (NOTE: in Zabbix use 'Type of information' Numeric(float) )
`l` - for MODBUS_LONG, 32bit
`i` - for MODBUS_INTEGER, 16bit (unsigned)
`s` - for MODBUS_SIGNED_INT, 16bit (NOTE: in Zabbix use 'Type of information' Numeric(float) )
`l` - for MODBUS_LONG, 32bit (unsigned)
`f` - for MODBUS_FLOAT, 32bit

There is also experimental support added for 64bit Modbus datatypes:
`S` - for MODBUS_SIGNED_INT64 (NOTE: in Zabbix use 'Type of information' Numeric(float) )
`I` - for MODBUS_INT64 (unsigned) (NOTE: in Zabbix use 'Type of information' Numeric(unsigned) )
`d` - for MODBUS_FLOAT64, 64 bit
`S` - for MODBUS_SIGNED_INT32, 32bit (NOTE: in Zabbix use 'Type of information' Numeric(float) )
`I` - for MODBUS_UINT64, 64bit (unsigned) (NOTE: in Zabbix use 'Type of information' Numeric(unsigned) )
`d` - for MODBUS_FLOAT64, 64bit

otherwise, defaults will be used:
MODBUS_BIT if modbus function 1 or 2.
MODBUS_INTEGER if modbus_function 3 or 4.

Please also note Zabbix datatypes constraints when working with 64bit:
https://www.zabbix.com/documentation/3.4/manual/config/items

* **endianness(optional):**
Modbus endianness(word swap) for long and float 32bit/64bit datatypes:
0 - for MODBUS_16BIT_LE (16 bit little endian)
1 - for MODBUS_16BIT_BE (16 bit big endian)
default is BE
Modbus endianness for 32bit/64bit datatypes:
0 - for MODBUS_MLE_CDAB (Mid-Little Endian (CDAB))
1 - for MODBUS_BE_ABCD (Big Endian (ABCD))
2 - for MODBUS_MBE_BADC (Mid-Big Endian (BADC))
3 - for MODBUS_LE_DCBA (Little Endian (DCBA))
Default is BE(1). Normaly, you don't need to change this.

* **first_reg(optional):**
Modbus addressing scheme
Expand Down
218 changes: 138 additions & 80 deletions src/modbus.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
#include <stdlib.h>
#include <errno.h>

#include <byteswap.h>

#define MODBUS_READ_COIL_1 1
#define MODBUS_READ_DINPUTS_2 2
#define MODBUS_READ_H_REGISTERS_3 3
Expand All @@ -38,12 +40,32 @@
#define MODBUS_SIGNED_INT 's'
#define MODBUS_LONG 'l'
#define MODBUS_FLOAT 'f'
#define MODBUS_SIGNED_INT64 'S'
#define MODBUS_INT64 'I'
#define MODBUS_SIGNED_INT32 'S'
#define MODBUS_UINT64 'I'
#define MODBUS_FLOAT64 'd'

#define MODBUS_16BIT_LE 0
#define MODBUS_16BIT_BE 1
#define MODBUS_GET_BE_32BIT(tab_int16, index) (((uint32_t)tab_int16[(index)]) << 16) | tab_int16[(index) + 1]
#define MODBUS_GET_MLE_32BIT(tab_int16, index) (((uint32_t)tab_int16[(index) + 1]) << 16) | tab_int16[(index)]
#define MODBUS_GET_MBE_32BIT(tab_int16, index) (((uint32_t)bswap_16(tab_int16[(index)])) << 16) | bswap_16(tab_int16[(index) + 1])
#define MODBUS_GET_LE_32BIT(tab_int16, index) (((uint32_t)bswap_16(tab_int16[(index) + 1])) << 16) | bswap_16(tab_int16[(index)])


#define MODBUS_GET_BE_64BIT(tab_int16, index) (((uint64_t)tab_int16[index]) << 48) | (((uint64_t)tab_int16[index + 1]) << 32) | (((uint64_t)tab_int16[index + 2]) << 16) | tab_int16[index + 3]
#define MODBUS_GET_MLE_64BIT(tab_int16, index) (((uint64_t)tab_int16[index + 3]) << 48) | (((uint64_t)tab_int16[index + 2]) << 32) | (((uint64_t)tab_int16[index + 1]) << 16) | tab_int16[index]
#define MODBUS_GET_MBE_64BIT(tab_int16, index) (((uint64_t)bswap_16(tab_int16[index])) << 48) \
| (((uint64_t)bswap_16(tab_int16[index + 1])) << 32) \
| (((uint64_t)bswap_16(tab_int16[index + 2])) << 16) \
| bswap_16(tab_int16[index + 3])
#define MODBUS_GET_LE_64BIT(tab_int16, index) (((uint64_t)bswap_16(tab_int16[index + 3])) << 48) \
| (((uint64_t)bswap_16(tab_int16[index + 2])) << 32) \
| (((uint64_t)bswap_16(tab_int16[index + 1])) << 16) \
| bswap_16(tab_int16[index])


#define MODBUS_MLE_CDAB 0 //Mid-Little Endian (CDAB)
#define MODBUS_BE_ABCD 1 //Big Endian (ABCD)
#define MODBUS_MBE_BADC 2 //Mid-Big Endian (BADC)
#define MODBUS_LE_DCBA 3 //Little Endian (DCBA)

#define MODBUS_PDU_ADDRESS_0 0
#define MODBUS_PROTOCOL_ADDRESS_1 1
Expand Down Expand Up @@ -144,17 +166,6 @@ unsigned long hash(unsigned char *str)
return hash;
}

/* Get a float from 8 bytes (Modbus) with swapped words (GH EF CD AB) */
double modbus_get_double(const uint16_t *src)
{
double d;
uint64_t i;

i = (((uint64_t)src[3]) << 48) + (((uint64_t)src[2]) << 32) + (((uint64_t)src[1]) << 16) + src[0];
memcpy(&d, &i, sizeof(double));

return d;
}

/******************************************************************************
* *
Expand Down Expand Up @@ -254,7 +265,7 @@ int zbx_modbus_read_registers(AGENT_REQUEST *request, AGENT_RESULT *result)
}

char datatype;
int end = MODBUS_16BIT_BE; //<endianness> endianness LE(0) BE(1) default BE
int end = MODBUS_BE_ABCD; //<endianness> endianness LE(0) BE(1) MBE(2) MLE(3) default BE
if (request->nparam > 4) { //optional params provided

param5 = get_rparam(request, 4); //datatype
Expand All @@ -265,12 +276,15 @@ int zbx_modbus_read_registers(AGENT_REQUEST *request, AGENT_RESULT *result)
}

datatype = *param5; // set datatype
param6 = get_rparam(request, 5); //16 endiannes
param6 = get_rparam(request, 5); //32-64bit endiannes
if(param6) {
//endianness to use
errno = 0;
end = strtol(param6,&endptr, 0);
if ( (end != MODBUS_16BIT_LE && end != MODBUS_16BIT_BE) ||
if ( (end != MODBUS_LE_DCBA &&
end != MODBUS_BE_ABCD &&
end != MODBUS_MBE_BADC &&
end != MODBUS_MLE_CDAB ) ||
(errno!=0 || *endptr != '\0') ) {
SET_MSG_RESULT(result, strdup("Check endiannes used"));
modbus_free(ctx);
Expand Down Expand Up @@ -314,9 +328,10 @@ int zbx_modbus_read_registers(AGENT_REQUEST *request, AGENT_RESULT *result)

uint16_t tab_reg[64];//temp vars
uint8_t tab_reg_bits[64];

int regs_to_read = 1;
if (datatype == MODBUS_FLOAT || datatype == MODBUS_LONG) { regs_to_read=2;}
else if (datatype == MODBUS_SIGNED_INT64 || datatype == MODBUS_INT64 || datatype == MODBUS_FLOAT64) { regs_to_read=4;}
if (datatype == MODBUS_FLOAT || datatype == MODBUS_LONG || datatype == MODBUS_SIGNED_INT32) { regs_to_read=2;}
else if (datatype == MODBUS_UINT64 || datatype == MODBUS_FLOAT64) { regs_to_read=4;}



Expand Down Expand Up @@ -378,79 +393,122 @@ int zbx_modbus_read_registers(AGENT_REQUEST *request, AGENT_RESULT *result)
//use float type in zabbix item
SET_DBL_RESULT(result, (int16_t) tab_reg[0]);
break;

float f;
uint32_t i;
case MODBUS_FLOAT:
if (end == MODBUS_16BIT_LE) {
temp_arr[0] = tab_reg[0];
temp_arr[1] = tab_reg[1];
} else if (end == MODBUS_16BIT_BE) {
temp_arr[0] = tab_reg[1];
temp_arr[1] = tab_reg[0];
switch( end )
{
case MODBUS_LE_DCBA:
i = MODBUS_GET_LE_32BIT(tab_reg, 0);
break;
case MODBUS_BE_ABCD:
i = MODBUS_GET_BE_32BIT(tab_reg, 0);
break;
case MODBUS_MBE_BADC:
i = MODBUS_GET_MBE_32BIT(tab_reg, 0);
break;
case MODBUS_MLE_CDAB:
i = MODBUS_GET_MLE_32BIT(tab_reg, 0);
break;
default:
return SYSINFO_RET_FAIL;
break;
}
SET_DBL_RESULT(result, modbus_get_float(temp_arr));
memcpy(&f, &i, sizeof(float));
SET_DBL_RESULT(result, f);
break;

case MODBUS_LONG:
//MODBUS_GET_INT32_FROM_INT16 is doing BIG_ENDIAN for register pair, so inverse registers (sort of hack)
if (end == MODBUS_16BIT_LE) {//word swap
temp_arr[0] = tab_reg[1];
temp_arr[1] = tab_reg[0];
} else if (end == MODBUS_16BIT_BE) {
temp_arr[0] = tab_reg[0];
temp_arr[1] = tab_reg[1];
}
SET_UI64_RESULT(result, MODBUS_GET_INT32_FROM_INT16(temp_arr, 0));
break;

case MODBUS_SIGNED_INT64:
if (end == MODBUS_16BIT_LE) {
temp_arr[0] = tab_reg[3];
temp_arr[1] = tab_reg[2];
temp_arr[2] = tab_reg[1];
temp_arr[3] = tab_reg[0];
}
if (end == MODBUS_16BIT_BE) {
temp_arr[0] = tab_reg[0];
temp_arr[1] = tab_reg[1];
temp_arr[2] = tab_reg[2];
temp_arr[3] = tab_reg[3];
switch( end )
{
case MODBUS_LE_DCBA:
SET_UI64_RESULT(result, (uint32_t)MODBUS_GET_LE_32BIT(tab_reg, 0));
break;
case MODBUS_BE_ABCD:
SET_UI64_RESULT(result, (uint32_t)MODBUS_GET_BE_32BIT(tab_reg, 0));
break;
case MODBUS_MBE_BADC:
SET_UI64_RESULT(result, (uint32_t)MODBUS_GET_MBE_32BIT(tab_reg, 0));
break;
case MODBUS_MLE_CDAB:
SET_UI64_RESULT(result, (uint32_t)MODBUS_GET_MLE_32BIT(tab_reg, 0));
break;
default:
return SYSINFO_RET_FAIL;
break;
}
SET_DBL_RESULT(result, ((int64_t)MODBUS_GET_INT64_FROM_INT16(temp_arr,0)));
break;
break;

case MODBUS_INT64:
//INT64
if (end == MODBUS_16BIT_LE) {
temp_arr[0] = tab_reg[3];
temp_arr[1] = tab_reg[2];
temp_arr[2] = tab_reg[1];
temp_arr[3] = tab_reg[0];
case MODBUS_SIGNED_INT32:
switch( end )
{
case MODBUS_BE_ABCD:
temp_arr[0] = tab_reg[0];
temp_arr[1] = tab_reg[1];
break;
case MODBUS_LE_DCBA:
temp_arr[0] = bswap_16(tab_reg[1]);
temp_arr[1] = bswap_16(tab_reg[0]);
break;
case MODBUS_MBE_BADC:
temp_arr[0] = bswap_16(tab_reg[0]);
temp_arr[1] = bswap_16(tab_reg[1]);
break;
case MODBUS_MLE_CDAB:
temp_arr[0] = tab_reg[1];
temp_arr[1] = tab_reg[0];
break;
default:
return SYSINFO_RET_FAIL;
break;
}
if (end == MODBUS_16BIT_BE) {
temp_arr[0] = tab_reg[0];
temp_arr[1] = tab_reg[1];
temp_arr[2] = tab_reg[2];
temp_arr[3] = tab_reg[3];
SET_DBL_RESULT(result, ((int32_t)MODBUS_GET_INT32_FROM_INT16(temp_arr,0)));
break;
case MODBUS_UINT64:
switch( end )
{
case MODBUS_LE_DCBA:
SET_UI64_RESULT(result, ((uint64_t)MODBUS_GET_LE_64BIT(tab_reg,0)));
break;
case MODBUS_BE_ABCD:
SET_UI64_RESULT(result, ((uint64_t)MODBUS_GET_BE_64BIT(tab_reg,0)));
break;
case MODBUS_MBE_BADC:
SET_UI64_RESULT(result, ((uint64_t)MODBUS_GET_MBE_64BIT(tab_reg,0)));
break;
case MODBUS_MLE_CDAB:
SET_UI64_RESULT(result, ((uint64_t)MODBUS_GET_MLE_64BIT(tab_reg,0)));
break;
default:
return SYSINFO_RET_FAIL;
break;
}
break;

SET_UI64_RESULT(result, ((uint64_t)MODBUS_GET_INT64_FROM_INT16(temp_arr,0)));
break;

double d;
uint64_t i64;
case MODBUS_FLOAT64:

if (end == MODBUS_16BIT_LE) {
temp_arr[0] = tab_reg[3];
temp_arr[1] = tab_reg[2];
temp_arr[2] = tab_reg[1];
temp_arr[3] = tab_reg[0];
}
if (end == MODBUS_16BIT_BE) {
temp_arr[0] = tab_reg[0];
temp_arr[1] = tab_reg[1];
temp_arr[2] = tab_reg[2];
temp_arr[3] = tab_reg[3];
switch( end )
{
case MODBUS_LE_DCBA:
i64 = MODBUS_GET_LE_64BIT(tab_reg, 0);
break;
case MODBUS_BE_ABCD:
i64 = MODBUS_GET_BE_64BIT(tab_reg, 0);
break;
case MODBUS_MBE_BADC:
i64 = MODBUS_GET_MBE_64BIT(tab_reg, 0);
break;
case MODBUS_MLE_CDAB:
i64 = MODBUS_GET_MLE_64BIT(tab_reg, 0);
break;
default:
return SYSINFO_RET_FAIL;
break;
}
SET_DBL_RESULT(result, modbus_get_double(temp_arr));
memcpy(&d, &i64, sizeof(double));
SET_DBL_RESULT(result, d);
break;

default :
Expand Down
2 changes: 1 addition & 1 deletion tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
## Run local
You can run tests local:
1. Build libzbxmodbus `./configure --enable-zabbix-3.2 && make`
2. Build and run the rest with docker-compose `export ZBX_VERSION=3.4.12 ; pushd tests/docker && docker-compose up -d --force-recreate ; popd`
2. Build and run the rest with docker-compose `export ZBX_VERSION=3.4.12 ; pushd tests/docker && docker-compose build && docker-compose up -d --force-recreate ; popd`
3. Run sample test from shell `docker exec docker_zabbix-agent-modbus_1 sh -c "zabbix_get -s localhost -k modbus_read[172.16.238.2:5020,1,2,3,i,1,1]"`
45 changes: 39 additions & 6 deletions tests/docker/modbus-server/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,47 @@ def run_server(modbus_type,port):

#block = ModbusSequentialDataBlock(0x00, [16]*0xff)
block = ModbusSparseDataBlock({
#this block is to test 32bits
1:0xC28F,#PDU0 - BE
2:0xC20D,

3:0x8FC2,#PDU2 - MBE
4:0x0DC2,

5:0xC20D,#PDU4 - MLE
6:0xC28F,

7:0x0DC2,#PDU6 - LE
8:0x8FC2,

# test int32
13:0xFFFF,#PDU12
14:0xFDCE,

1:0xC28F,#PDU0
2:0xC20D,#PDU1
# this block is to test 64bits
#BE
15:0xBFBF,#PDU14
16:0x9A6B,
17:0x50B0,
18:0xF27C,

#LE
19:0x7CF2,#PDU18
20:0xB050,
21:0x6B9A,
22:0xBFBF,

#MLE
23:0xF27C,#PDU22
24:0x50B0,
25:0x9A6B,
26:0xBFBF,

11:0xFFFF,#PDU10
12:0xFFFF,
13:0xFFFF,
14:0xFDCE
#MBE
27:0xBFBF,#PDU26
28:0x6B9A,
29:0xB050,
30:0x7CF2
})
store = ModbusSlaveContext(di=block, co=block, hr=block, ir=block)

Expand Down
Loading

0 comments on commit 1faf536

Please sign in to comment.