diff --git a/plugins/inputs/modbus/README.md b/plugins/inputs/modbus/README.md index 1e042deba..2c73ba5ec 100644 --- a/plugins/inputs/modbus/README.md +++ b/plugins/inputs/modbus/README.md @@ -58,7 +58,7 @@ The Modbus plugin collects Discrete Inputs, Coils, Input Registers and Holding R ## |---BA, DCBA - Little Endian ## |---BADC - Mid-Big Endian ## |---CDAB - Mid-Little Endian - ## data_type - UINT16, INT16, INT32, UINT32, FLOAT32, FLOAT32-IEEE (the IEEE 754 binary representation) + ## data_type - INT16, UINT16, INT32, UINT32, INT64, UINT64, FLOAT32, FLOAT32-IEEE (the IEEE 754 binary representation) ## scale - the final numeric variable representation ## address - variable address diff --git a/plugins/inputs/modbus/modbus.go b/plugins/inputs/modbus/modbus.go index e96b2f117..46dc748c7 100644 --- a/plugins/inputs/modbus/modbus.go +++ b/plugins/inputs/modbus/modbus.go @@ -73,17 +73,17 @@ const sampleConfig = ` ## ## Device name name = "Device" - + ## Slave ID - addresses a MODBUS device on the bus ## Range: 0 - 255 [0 = broadcast; 248 - 255 = reserved] slave_id = 1 - + ## Timeout for each request timeout = "1s" - + # TCP - connect via Modbus/TCP controller = "tcp://localhost:502" - + # Serial (RS485; RS232) #controller = "file:///dev/ttyUSB0" #baud_rate = 9600 @@ -91,15 +91,15 @@ const sampleConfig = ` #parity = "N" #stop_bits = 1 #transmission_mode = "RTU" - - + + ## Measurements ## - + ## Digital Variables, Discrete Inputs and Coils ## name - the variable name ## address - variable address - + discrete_inputs = [ { name = "start", address = [0]}, { name = "stop", address = [1]}, @@ -111,7 +111,7 @@ const sampleConfig = ` { name = "motor1_jog", address = [1]}, { name = "motor1_stop", address = [2]}, ] - + ## Analog Variables, Input Registers and Holding Registers ## name - the variable name ## byte_order - the ordering of bytes @@ -119,10 +119,10 @@ const sampleConfig = ` ## |---BA, DCBA - Little Endian ## |---BADC - Mid-Big Endian ## |---CDAB - Mid-Little Endian - ## data_type - UINT16, INT16, INT32, UINT32, FLOAT32, FLOAT32-IEEE (the IEEE 754 binary representation) + ## data_type - INT16, UINT16, INT32, UINT32, INT64, UINT64, FLOAT32, FLOAT32-IEEE (the IEEE 754 binary representation) ## 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]}, @@ -328,7 +328,7 @@ func validateFieldContainers(t []fieldContainer, n string) error { if n == cInputRegisters || n == cHoldingRegisters { // search byte order switch item.ByteOrder { - case "AB", "BA", "ABCD", "CDAB", "BADC", "DCBA": + case "AB", "BA", "ABCD", "CDAB", "BADC", "DCBA", "ABCDEFGH", "HGFEDCBA", "BADCFEHG", "GHEFCDAB": break default: return fmt.Errorf("invalid byte order '%s' in '%s' - '%s'", item.ByteOrder, n, item.Name) @@ -336,7 +336,7 @@ func validateFieldContainers(t []fieldContainer, n string) error { // search data type switch item.DataType { - case "UINT16", "INT16", "UINT32", "INT32", "FLOAT32-IEEE", "FLOAT32": + case "UINT16", "INT16", "UINT32", "INT32", "UINT64", "INT64", "FLOAT32-IEEE", "FLOAT32": break default: return fmt.Errorf("invalid data type '%s' in '%s' - '%s'", item.DataType, n, item.Name) @@ -349,10 +349,12 @@ func validateFieldContainers(t []fieldContainer, n string) error { } // check address - if len(item.Address) == 0 || len(item.Address) > 2 { + if len(item.Address) != 1 && len(item.Address) != 2 && len(item.Address) != 4 { return fmt.Errorf("invalid address '%v' length '%v' in '%s' - '%s'", item.Address, len(item.Address), n, item.Name) - } else if n == cInputRegisters || n == cHoldingRegisters { - if (len(item.Address) == 1 && len(item.ByteOrder) != 2) || (len(item.Address) == 2 && len(item.ByteOrder) != 4) { + } + + if n == cInputRegisters || n == cHoldingRegisters { + if 2*len(item.Address) != len(item.ByteOrder) { return fmt.Errorf("invalid byte order '%s' and address '%v' in '%s' - '%s'", item.ByteOrder, item.Address, n, item.Name) } @@ -360,8 +362,7 @@ func validateFieldContainers(t []fieldContainer, n string) error { if len(item.Address) > len(removeDuplicates(item.Address)) { return fmt.Errorf("duplicate address '%v' in '%s' - '%s'", item.Address, n, item.Name) } - - } else if len(item.Address) > 1 || (n == cInputRegisters || n == cHoldingRegisters) { + } else if len(item.Address) != 1 { return fmt.Errorf("invalid address'%v' length'%v' in '%s' - '%s'", item.Address, len(item.Address), n, item.Name) } } @@ -480,6 +481,14 @@ func convertDataType(t fieldContainer, bytes []byte) interface{} { e32 := convertEndianness32(t.ByteOrder, bytes) f32 := int32(e32) return scaleInt32(t.Scale, f32) + case "UINT64": + e64 := convertEndianness64(t.ByteOrder, bytes) + f64 := format64(t.DataType, e64).(uint64) + return scaleUint64(t.Scale, f64) + case "INT64": + e64 := convertEndianness64(t.ByteOrder, bytes) + f64 := format64(t.DataType, e64).(int64) + return scaleInt64(t.Scale, f64) case "FLOAT32-IEEE": e32 := convertEndianness32(t.ByteOrder, bytes) f32 := math.Float32frombits(e32) @@ -488,9 +497,12 @@ func convertDataType(t fieldContainer, bytes []byte) interface{} { if len(bytes) == 2 { e16 := convertEndianness16(t.ByteOrder, bytes) return scale16toFloat32(t.Scale, e16) - } else { + } else if len(bytes) == 4 { e32 := convertEndianness32(t.ByteOrder, bytes) return scale32toFloat32(t.Scale, e32) + } else { + e64 := convertEndianness64(t.ByteOrder, bytes) + return scale64toFloat32(t.Scale, e64) } default: return 0 @@ -523,6 +535,21 @@ func convertEndianness32(o string, b []byte) uint32 { } } +func convertEndianness64(o string, b []byte) uint64 { + switch o { + case "ABCDEFGH": + return binary.BigEndian.Uint64(b) + case "HGFEDCBA": + return binary.LittleEndian.Uint64(b) + case "BADCFEHG": + return uint64(binary.LittleEndian.Uint16(b[0:]))<<48 | uint64(binary.LittleEndian.Uint16(b[2:]))<<32 | uint64(binary.LittleEndian.Uint16(b[4:]))<<16 | uint64(binary.LittleEndian.Uint16(b[6:])) + case "GHEFCDAB": + return uint64(binary.BigEndian.Uint16(b[6:]))<<48 | uint64(binary.BigEndian.Uint16(b[4:]))<<32 | uint64(binary.BigEndian.Uint16(b[2:]))<<16 | uint64(binary.BigEndian.Uint16(b[0:])) + default: + return 0 + } +} + func format16(f string, r uint16) interface{} { switch f { case "UINT16": @@ -547,6 +574,17 @@ func format32(f string, r uint32) interface{} { } } +func format64(f string, r uint64) interface{} { + switch f { + case "UINT64": + return r + case "INT64": + return int64(r) + default: + return r + } +} + func scale16toFloat32(s float64, v uint16) float64 { return float64(v) * s } @@ -555,6 +593,10 @@ func scale32toFloat32(s float64, v uint32) float64 { return float64(float64(v) * float64(s)) } +func scale64toFloat32(s float64, v uint64) float64 { + return float64(float64(v) * float64(s)) +} + func scaleInt16(s float64, v int16) int16 { return int16(float64(v) * s) } @@ -575,6 +617,14 @@ func scaleFloat32(s float64, v float32) float32 { return float32(float64(v) * s) } +func scaleUint64(s float64, v uint64) uint64 { + return uint64(float64(v) * float64(s)) +} + +func scaleInt64(s float64, v int64) int64 { + return int64(float64(v) * float64(s)) +} + // Gather implements the telegraf plugin interface method for data accumulation func (m *Modbus) Gather(acc telegraf.Accumulator) error { if !m.isConnected { diff --git a/plugins/inputs/modbus/modbus_test.go b/plugins/inputs/modbus/modbus_test.go index e346ece17..9a8c46382 100644 --- a/plugins/inputs/modbus/modbus_test.go +++ b/plugins/inputs/modbus/modbus_test.go @@ -349,6 +349,106 @@ func TestHoldingRegisters(t *testing.T) { write: []byte{0xAA, 0xBB, 0xCC, 0xDD}, read: float32(-3.3360025e-12), }, + { + name: "register140_to_register143_abcdefgh_int64_scaled", + address: []uint16{140, 141, 142, 143}, + quantity: 4, + byteOrder: "ABCDEFGH", + dataType: "INT64", + scale: 10, + write: []byte{0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xAB, 0xCD}, + read: int64(10995116717570), + }, + { + name: "register140_to_register143_abcdefgh_int64", + address: []uint16{140, 141, 142, 143}, + quantity: 4, + byteOrder: "ABCDEFGH", + dataType: "INT64", + scale: 1, + write: []byte{0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xAB, 0xCD}, + read: int64(1099511671757), + }, + { + name: "register150_to_register153_hgfedcba_int64", + address: []uint16{150, 151, 152, 153}, + quantity: 4, + byteOrder: "HGFEDCBA", + dataType: "INT64", + scale: 1, + write: []byte{0x84, 0xF6, 0x45, 0xF9, 0xBC, 0xFE, 0xFF, 0xFF}, + read: int64(-1387387292028), + }, + { + name: "register160_to_register163_badcfehg_int64", + address: []uint16{160, 161, 162, 163}, + quantity: 4, + byteOrder: "BADCFEHG", + dataType: "INT64", + scale: 1, + write: []byte{0xFF, 0xFF, 0xBC, 0xFE, 0x45, 0xF9, 0x84, 0xF6}, + read: int64(-1387387292028), + }, + { + name: "register170_to_register173_ghefcdab_int64", + address: []uint16{170, 171, 172, 173}, + quantity: 4, + byteOrder: "GHEFCDAB", + dataType: "INT64", + scale: 1, + write: []byte{0xF6, 0x84, 0xF9, 0x45, 0xFE, 0xBC, 0xFF, 0xFF}, + read: int64(-1387387292028), + }, + { + name: "register180_to_register183_abcdefgh_uint64_scaled", + address: []uint16{180, 181, 182, 183}, + quantity: 4, + byteOrder: "ABCDEFGH", + dataType: "UINT64", + scale: 10, + write: []byte{0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xAB, 0xCD}, + read: uint64(10995116717570), + }, + { + name: "register180_to_register183_abcdefgh_uint64", + address: []uint16{180, 181, 182, 183}, + quantity: 4, + byteOrder: "ABCDEFGH", + dataType: "UINT64", + scale: 1, + write: []byte{0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xAB, 0xCD}, + read: uint64(1099511671757), + }, + { + name: "register190_to_register193_hgfedcba_uint64", + address: []uint16{190, 191, 192, 193}, + quantity: 4, + byteOrder: "HGFEDCBA", + dataType: "UINT64", + scale: 1, + write: []byte{0x84, 0xF6, 0x45, 0xF9, 0xBC, 0xFE, 0xFF, 0xFF}, + read: uint64(18446742686322259968), + }, + { + name: "register200_to_register203_badcfehg_uint64", + address: []uint16{200, 201, 202, 203}, + quantity: 4, + byteOrder: "BADCFEHG", + dataType: "UINT64", + scale: 1, + write: []byte{0xFF, 0xFF, 0xBC, 0xFE, 0x45, 0xF9, 0x84, 0xF6}, + read: uint64(18446742686322259968), + }, + { + name: "register210_to_register213_ghefcdab_uint64", + address: []uint16{210, 211, 212, 213}, + quantity: 4, + byteOrder: "GHEFCDAB", + dataType: "UINT64", + scale: 1, + write: []byte{0xF6, 0x84, 0xF9, 0x45, 0xFE, 0xBC, 0xFF, 0xFF}, + read: uint64(18446742686322259968), + }, } serv := mbserver.NewServer()