diff --git a/plugins/inputs/modbus/README.md b/plugins/inputs/modbus/README.md index 629c79027f471..2476f3d368eb5 100644 --- a/plugins/inputs/modbus/README.md +++ b/plugins/inputs/modbus/README.md @@ -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 @@ -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]}, ] ``` @@ -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 diff --git a/plugins/inputs/modbus/modbus.go b/plugins/inputs/modbus/modbus.go index c1ff56bab149e..ec68890c5eb91 100644 --- a/plugins/inputs/modbus/modbus.go +++ b/plugins/inputs/modbus/modbus.go @@ -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]}, @@ -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) @@ -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 @@ -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)) } diff --git a/plugins/inputs/modbus/modbus_test.go b/plugins/inputs/modbus/modbus_test.go index 97265769dc821..8c5241dc2aaee 100644 --- a/plugins/inputs/modbus/modbus_test.go +++ b/plugins/inputs/modbus/modbus_test.go @@ -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},