Skip to content

Commit

Permalink
Fix/extend support of fixed point values on input (modbus plugin) (#7869
Browse files Browse the repository at this point in the history
)

* Add input float types FIXED/UFIXED (#7317)
  • Loading branch information
sensor-freak authored and ssoroka committed Aug 28, 2020
1 parent 26c2f61 commit d2ea3b1
Show file tree
Hide file tree
Showing 3 changed files with 200 additions and 37 deletions.
80 changes: 58 additions & 22 deletions plugins/inputs/modbus/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Registers via Modbus TCP or Modbus RTU/ASCII.
[[inputs.modbus]]
## Connection Configuration
##
## The module supports connections to PLCs via MODBUS/TCP or
## The plugin supports connections to PLCs via MODBUS/TCP or
## via serial line communication in binary (RTU) or readable (ASCII) encoding
##
## Device name
Expand Down Expand Up @@ -43,45 +43,47 @@ Registers via Modbus TCP or Modbus RTU/ASCII.
##

## Digital Variables, Discrete Inputs and Coils
## name - the variable name
## address - variable address
## measurement - the (optional) measurement name, defaults to "modbus"
## name - the variable name
## address - variable address

discrete_inputs = [
{ name = "Start", address = [0]},
{ name = "Stop", address = [1]},
{ name = "Reset", address = [2]},
{ name = "EmergencyStop", address = [3]},
{ name = "start", address = [0]},
{ name = "stop", address = [1]},
{ name = "reset", address = [2]},
{ name = "emergency_stop", address = [3]},
]
coils = [
{ name = "Motor1-Run", address = [0]},
{ name = "Motor1-Jog", address = [1]},
{ name = "Motor1-Stop", address = [2]},
{ name = "motor1_run", address = [0]},
{ name = "motor1_jog", address = [1]},
{ name = "motor1_stop", address = [2]},
]

## Analog Variables, Input Registers and Holding Registers
## measurement - the (optional) measurement name, defaults to "modbus"
## name - the variable name
## byte_order - the ordering of bytes
## name - the variable name
## byte_order - the ordering of bytes
## |---AB, ABCD - Big Endian
## |---BA, DCBA - Little Endian
## |---BADC - Mid-Big Endian
## |---CDAB - Mid-Little Endian
## data_type - INT16, UINT16, INT32, UINT32, INT64, UINT64, FLOAT32, FLOAT32-IEEE (the IEEE 754 binary representation)
## data_type - INT16, UINT16, INT32, UINT32, INT64, UINT64, FLOAT32-IEEE (the IEEE 754 binary representation)
## FLOAT32 (deprecated), FIXED, UFIXED (fixed-point representation on input)
## scale - the final numeric variable representation
## address - variable address

holding_registers = [
{ name = "PowerFactor", byte_order = "AB", data_type = "FLOAT32", scale=0.01, address = [8]},
{ name = "Voltage", byte_order = "AB", data_type = "FLOAT32", scale=0.1, address = [0]},
{ name = "Energy", byte_order = "ABCD", data_type = "FLOAT32", scale=0.001, address = [5,6]},
{ name = "Current", byte_order = "ABCD", data_type = "FLOAT32", scale=0.001, address = [1,2]},
{ name = "Frequency", byte_order = "AB", data_type = "FLOAT32", scale=0.1, address = [7]},
{ name = "Power", byte_order = "ABCD", data_type = "FLOAT32", scale=0.1, address = [3,4]},
{ name = "power_factor", byte_order = "AB", data_type = "FIXED", scale=0.01, address = [8]},
{ name = "voltage", byte_order = "AB", data_type = "FIXED", scale=0.1, address = [0]},
{ name = "energy", byte_order = "ABCD", data_type = "FIXED", scale=0.001, address = [5,6]},
{ name = "current", byte_order = "ABCD", data_type = "FIXED", scale=0.001, address = [1,2]},
{ name = "frequency", byte_order = "AB", data_type = "UFIXED", scale=0.1, address = [7]},
{ name = "power", byte_order = "ABCD", data_type = "UFIXED", scale=0.1, address = [3,4]},
]
input_registers = [
{ name = "TankLevel", byte_order = "AB", data_type = "INT16", scale=1.0, address = [0]},
{ name = "TankPH", byte_order = "AB", data_type = "INT16", scale=1.0, address = [1]},
{ name = "Pump1-Speed", byte_order = "ABCD", data_type = "INT32", scale=1.0, address = [3,4]},
{ name = "tank_level", byte_order = "AB", data_type = "INT16", scale=1.0, address = [0]},
{ name = "tank_ph", byte_order = "AB", data_type = "INT16", scale=1.0, address = [1]},
{ name = "pump1_speed", byte_order = "ABCD", data_type = "INT32", scale=1.0, address = [3,4]},
]
```

Expand All @@ -90,6 +92,40 @@ Registers via Modbus TCP or Modbus RTU/ASCII.
Metric are custom and configured using the `discrete_inputs`, `coils`,
`holding_register` and `input_registers` options.

### Usage of `data_type`

The field `data_type` defines the representation of the data value on input from the modbus registers.
The input values are then converted from the given `data_type` to a type that is apropriate when
sending the value to the output plugin. These output types are usually one of string,
integer or floating-point-number. The size of the output type is assumed to be large enough
for all supported input types. The mapping from the input type to the output type is fixed
and cannot be configured.

#### Integers: `INT16`, `UINT16`, `INT32`, `UINT32`, `INT64`, `UINT64`

These types are used for integer input values. Select the one that matches your modbus data source.

#### Floating Point: `FLOAT32-IEEE`

Use this type if your modbus registers contain a value that is encoded in this format. This type
always includes the sign and therefore there exists no variant.

#### Fixed Point: `FIXED`, `UFIXED` (`FLOAT32`)

These types are handled as an integer type on input, but are converted to floating point representation
for further processing (e.g. scaling). Use one of these types when the input value is a decimal fixed point
representation of a non-integer value.

Select the type `UFIXED` when the input type is declared to hold unsigned integer values, which cannot
be negative. The documentation of your modbus device should indicate this by a term like
'uint16 containing fixed-point representation with N decimal places'.

Select the type `FIXED` when the input type is declared to hold signed integer values. Your documentation
of the modbus device should indicate this with a term like 'int32 containing fixed-point representation
with N decimal places'.

(FLOAT32 is deprecated and should not be used any more. UFIXED provides the same conversion
from unsigned values).

### Example Output

Expand Down
57 changes: 42 additions & 15 deletions plugins/inputs/modbus/modbus.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,17 +132,18 @@ const sampleConfig = `
## |---BA, DCBA - Little Endian
## |---BADC - Mid-Big Endian
## |---CDAB - Mid-Little Endian
## data_type - INT16, UINT16, INT32, UINT32, INT64, UINT64, FLOAT32, FLOAT32-IEEE (the IEEE 754 binary representation)
## data_type - INT16, UINT16, INT32, UINT32, INT64, UINT64, FLOAT32-IEEE (the IEEE 754 binary representation)
## FLOAT32, FIXED, UFIXED (fixed-point representation on input)
## scale - the final numeric variable representation
## address - variable address
holding_registers = [
{ name = "power_factor", byte_order = "AB", data_type = "FLOAT32", scale=0.01, address = [8]},
{ name = "voltage", byte_order = "AB", data_type = "FLOAT32", scale=0.1, address = [0]},
{ name = "energy", byte_order = "ABCD", data_type = "FLOAT32", scale=0.001, address = [5,6]},
{ name = "current", byte_order = "ABCD", data_type = "FLOAT32", scale=0.001, address = [1,2]},
{ name = "frequency", byte_order = "AB", data_type = "FLOAT32", scale=0.1, address = [7]},
{ name = "power", byte_order = "ABCD", data_type = "FLOAT32", scale=0.1, address = [3,4]},
{ name = "power_factor", byte_order = "AB", data_type = "FIXED", scale=0.01, address = [8]},
{ name = "voltage", byte_order = "AB", data_type = "FIXED", scale=0.1, address = [0]},
{ name = "energy", byte_order = "ABCD", data_type = "FIXED", scale=0.001, address = [5,6]},
{ name = "current", byte_order = "ABCD", data_type = "FIXED", scale=0.001, address = [1,2]},
{ name = "frequency", byte_order = "AB", data_type = "UFIXED", scale=0.1, address = [7]},
{ name = "power", byte_order = "ABCD", data_type = "UFIXED", scale=0.1, address = [3,4]},
]
input_registers = [
{ name = "tank_level", byte_order = "AB", data_type = "INT16", scale=1.0, address = [0]},
Expand Down Expand Up @@ -354,7 +355,7 @@ func validateFieldContainers(t []fieldContainer, n string) error {

// search data type
switch item.DataType {
case "UINT16", "INT16", "UINT32", "INT32", "UINT64", "INT64", "FLOAT32-IEEE", "FLOAT32":
case "UINT16", "INT16", "UINT32", "INT32", "UINT64", "INT64", "FLOAT32-IEEE", "FLOAT32", "FIXED", "UFIXED":
break
default:
return fmt.Errorf("invalid data type '%s' in '%s' - '%s'", item.DataType, n, item.Name)
Expand Down Expand Up @@ -511,16 +512,30 @@ func convertDataType(t fieldContainer, bytes []byte) interface{} {
e32 := convertEndianness32(t.ByteOrder, bytes)
f32 := math.Float32frombits(e32)
return scaleFloat32(t.Scale, f32)
case "FLOAT32":
case "FIXED":
if len(bytes) == 2 {
e16 := convertEndianness16(t.ByteOrder, bytes)
return scale16toFloat32(t.Scale, e16)
f16 := int16(e16)
return scale16toFloat(t.Scale, f16)
} else if len(bytes) == 4 {
e32 := convertEndianness32(t.ByteOrder, bytes)
return scale32toFloat32(t.Scale, e32)
f32 := int32(e32)
return scale32toFloat(t.Scale, f32)
} else {
e64 := convertEndianness64(t.ByteOrder, bytes)
return scale64toFloat32(t.Scale, e64)
f64 := int64(e64)
return scale64toFloat(t.Scale, f64)
}
case "FLOAT32", "UFIXED":
if len(bytes) == 2 {
e16 := convertEndianness16(t.ByteOrder, bytes)
return scale16UtoFloat(t.Scale, e16)
} else if len(bytes) == 4 {
e32 := convertEndianness32(t.ByteOrder, bytes)
return scale32UtoFloat(t.Scale, e32)
} else {
e64 := convertEndianness64(t.ByteOrder, bytes)
return scale64UtoFloat(t.Scale, e64)
}
default:
return 0
Expand Down Expand Up @@ -603,15 +618,27 @@ func format64(f string, r uint64) interface{} {
}
}

func scale16toFloat32(s float64, v uint16) float64 {
func scale16toFloat(s float64, v int16) float64 {
return float64(v) * s
}

func scale32toFloat(s float64, v int32) float64 {
return float64(float64(v) * float64(s))
}

func scale64toFloat(s float64, v int64) float64 {
return float64(float64(v) * float64(s))
}

func scale16UtoFloat(s float64, v uint16) float64 {
return float64(v) * s
}

func scale32toFloat32(s float64, v uint32) float64 {
func scale32UtoFloat(s float64, v uint32) float64 {
return float64(float64(v) * float64(s))
}

func scale64toFloat32(s float64, v uint64) float64 {
func scale64UtoFloat(s float64, v uint64) float64 {
return float64(float64(v) * float64(s))
}

Expand Down
100 changes: 100 additions & 0 deletions plugins/inputs/modbus/modbus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,106 @@ func TestHoldingRegisters(t *testing.T) {
write: []byte{0x01, 0xF4},
read: float64(50),
},
{
name: "register0_ab_float32_msb",
address: []uint16{0},
quantity: 1,
byteOrder: "AB",
dataType: "FLOAT32",
scale: 0.1,
write: []byte{0x89, 0x65},
read: float64(3517.3),
},
{
name: "register0_register1_ab_float32_msb",
address: []uint16{0, 1},
quantity: 2,
byteOrder: "ABCD",
dataType: "FLOAT32",
scale: 0.001,
write: []byte{0xFF, 0xFF, 0xFF, 0xFF},
read: float64(4294967.295),
},
{
name: "register5_to_register8_abcdefgh_float32",
address: []uint16{5, 6, 7, 8},
quantity: 4,
byteOrder: "ABCDEFGH",
dataType: "FLOAT32",
scale: 0.000001,
write: []byte{0x00, 0x00, 0x00, 0x62, 0xC6, 0xD1, 0xA9, 0xB2},
read: float64(424242.424242),
},
{
name: "register6_to_register9_hgfedcba_float32_msb",
address: []uint16{6, 7, 8, 9},
quantity: 4,
byteOrder: "HGFEDCBA",
dataType: "FLOAT32",
scale: 0.0000000001,
write: []byte{0xEA, 0x1E, 0x39, 0xEE, 0x8E, 0xA9, 0x54, 0xAB},
read: float64(1234567890.9876544),
},
{
name: "register0_ab_float",
address: []uint16{0},
quantity: 1,
byteOrder: "AB",
dataType: "FIXED",
scale: 0.1,
write: []byte{0xFF, 0xD6},
read: float64(-4.2),
},
{
name: "register1_ba_ufloat",
address: []uint16{1},
quantity: 1,
byteOrder: "BA",
dataType: "UFIXED",
scale: 0.1,
write: []byte{0xD8, 0xFF},
read: float64(6549.6),
},
{
name: "register4_register5_abcd_float",
address: []uint16{4, 5},
quantity: 2,
byteOrder: "ABCD",
dataType: "FIXED",
scale: 0.1,
write: []byte{0xFF, 0xFF, 0xFF, 0xD6},
read: float64(-4.2),
},
{
name: "register5_register6_dcba_ufloat",
address: []uint16{5, 6},
quantity: 2,
byteOrder: "DCBA",
dataType: "UFIXED",
scale: 0.001,
write: []byte{0xD8, 0xFF, 0xFF, 0xFF},
read: float64(4294967.256),
},
{
name: "register5_to_register8_abcdefgh_float",
address: []uint16{5, 6, 7, 8},
quantity: 4,
byteOrder: "ABCDEFGH",
dataType: "FIXED",
scale: 0.000001,
write: []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD6},
read: float64(-0.000042),
},
{
name: "register6_to_register9_hgfedcba_ufloat",
address: []uint16{6, 7, 8, 9},
quantity: 4,
byteOrder: "HGFEDCBA",
dataType: "UFIXED",
scale: 0.000000001,
write: []byte{0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
read: float64(18441921395.520346504),
},
{
name: "register10_ab_uint16",
address: []uint16{10},
Expand Down

0 comments on commit d2ea3b1

Please sign in to comment.