Add support for 64-bit integer types to modbus input (#7225)

This commit is contained in:
Sven Rebhan 2020-03-27 01:05:43 +01:00 committed by GitHub
parent cb42f610f4
commit f882b8f94f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 170 additions and 20 deletions

View File

@ -58,7 +58,7 @@ The Modbus plugin collects Discrete Inputs, Coils, Input Registers and Holding R
## |---BA, DCBA - Little Endian ## |---BA, DCBA - Little Endian
## |---BADC - Mid-Big Endian ## |---BADC - Mid-Big Endian
## |---CDAB - Mid-Little 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 ## scale - the final numeric variable representation
## address - variable address ## address - variable address

View File

@ -73,17 +73,17 @@ const sampleConfig = `
## ##
## Device name ## Device name
name = "Device" name = "Device"
## Slave ID - addresses a MODBUS device on the bus ## Slave ID - addresses a MODBUS device on the bus
## Range: 0 - 255 [0 = broadcast; 248 - 255 = reserved] ## Range: 0 - 255 [0 = broadcast; 248 - 255 = reserved]
slave_id = 1 slave_id = 1
## Timeout for each request ## Timeout for each request
timeout = "1s" timeout = "1s"
# TCP - connect via Modbus/TCP # TCP - connect via Modbus/TCP
controller = "tcp://localhost:502" controller = "tcp://localhost:502"
# Serial (RS485; RS232) # Serial (RS485; RS232)
#controller = "file:///dev/ttyUSB0" #controller = "file:///dev/ttyUSB0"
#baud_rate = 9600 #baud_rate = 9600
@ -91,15 +91,15 @@ const sampleConfig = `
#parity = "N" #parity = "N"
#stop_bits = 1 #stop_bits = 1
#transmission_mode = "RTU" #transmission_mode = "RTU"
## Measurements ## Measurements
## ##
## Digital Variables, Discrete Inputs and Coils ## Digital Variables, Discrete Inputs and Coils
## name - the variable name ## name - the variable name
## address - variable address ## address - variable address
discrete_inputs = [ discrete_inputs = [
{ name = "start", address = [0]}, { name = "start", address = [0]},
{ name = "stop", address = [1]}, { name = "stop", address = [1]},
@ -111,7 +111,7 @@ const sampleConfig = `
{ name = "motor1_jog", address = [1]}, { name = "motor1_jog", address = [1]},
{ name = "motor1_stop", address = [2]}, { name = "motor1_stop", address = [2]},
] ]
## Analog Variables, Input Registers and Holding Registers ## Analog Variables, Input Registers and Holding Registers
## name - the variable name ## name - the variable name
## byte_order - the ordering of bytes ## byte_order - the ordering of bytes
@ -119,10 +119,10 @@ const sampleConfig = `
## |---BA, DCBA - Little Endian ## |---BA, DCBA - Little Endian
## |---BADC - Mid-Big Endian ## |---BADC - Mid-Big Endian
## |---CDAB - Mid-Little 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 ## scale - the final numeric variable representation
## address - variable address ## address - variable address
holding_registers = [ holding_registers = [
{ name = "power_factor", byte_order = "AB", data_type = "FLOAT32", scale=0.01, address = [8]}, { 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 = "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 { if n == cInputRegisters || n == cHoldingRegisters {
// search byte order // search byte order
switch item.ByteOrder { switch item.ByteOrder {
case "AB", "BA", "ABCD", "CDAB", "BADC", "DCBA": case "AB", "BA", "ABCD", "CDAB", "BADC", "DCBA", "ABCDEFGH", "HGFEDCBA", "BADCFEHG", "GHEFCDAB":
break break
default: default:
return fmt.Errorf("invalid byte order '%s' in '%s' - '%s'", item.ByteOrder, n, item.Name) 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 // search data type
switch item.DataType { switch item.DataType {
case "UINT16", "INT16", "UINT32", "INT32", "FLOAT32-IEEE", "FLOAT32": case "UINT16", "INT16", "UINT32", "INT32", "UINT64", "INT64", "FLOAT32-IEEE", "FLOAT32":
break break
default: default:
return fmt.Errorf("invalid data type '%s' in '%s' - '%s'", item.DataType, n, item.Name) 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 // 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) 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) 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)) { if len(item.Address) > len(removeDuplicates(item.Address)) {
return fmt.Errorf("duplicate address '%v' in '%s' - '%s'", item.Address, n, item.Name) return fmt.Errorf("duplicate address '%v' in '%s' - '%s'", item.Address, n, item.Name)
} }
} else if len(item.Address) != 1 {
} else if len(item.Address) > 1 || (n == cInputRegisters || n == cHoldingRegisters) {
return fmt.Errorf("invalid address'%v' length'%v' in '%s' - '%s'", item.Address, len(item.Address), n, item.Name) 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) e32 := convertEndianness32(t.ByteOrder, bytes)
f32 := int32(e32) f32 := int32(e32)
return scaleInt32(t.Scale, f32) 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": case "FLOAT32-IEEE":
e32 := convertEndianness32(t.ByteOrder, bytes) e32 := convertEndianness32(t.ByteOrder, bytes)
f32 := math.Float32frombits(e32) f32 := math.Float32frombits(e32)
@ -488,9 +497,12 @@ func convertDataType(t fieldContainer, bytes []byte) interface{} {
if len(bytes) == 2 { if len(bytes) == 2 {
e16 := convertEndianness16(t.ByteOrder, bytes) e16 := convertEndianness16(t.ByteOrder, bytes)
return scale16toFloat32(t.Scale, e16) return scale16toFloat32(t.Scale, e16)
} else { } else if len(bytes) == 4 {
e32 := convertEndianness32(t.ByteOrder, bytes) e32 := convertEndianness32(t.ByteOrder, bytes)
return scale32toFloat32(t.Scale, e32) return scale32toFloat32(t.Scale, e32)
} else {
e64 := convertEndianness64(t.ByteOrder, bytes)
return scale64toFloat32(t.Scale, e64)
} }
default: default:
return 0 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{} { func format16(f string, r uint16) interface{} {
switch f { switch f {
case "UINT16": 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 { func scale16toFloat32(s float64, v uint16) float64 {
return float64(v) * s return float64(v) * s
} }
@ -555,6 +593,10 @@ func scale32toFloat32(s float64, v uint32) float64 {
return float64(float64(v) * float64(s)) 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 { func scaleInt16(s float64, v int16) int16 {
return int16(float64(v) * s) return int16(float64(v) * s)
} }
@ -575,6 +617,14 @@ func scaleFloat32(s float64, v float32) float32 {
return float32(float64(v) * s) 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 // Gather implements the telegraf plugin interface method for data accumulation
func (m *Modbus) Gather(acc telegraf.Accumulator) error { func (m *Modbus) Gather(acc telegraf.Accumulator) error {
if !m.isConnected { if !m.isConnected {

View File

@ -349,6 +349,106 @@ func TestHoldingRegisters(t *testing.T) {
write: []byte{0xAA, 0xBB, 0xCC, 0xDD}, write: []byte{0xAA, 0xBB, 0xCC, 0xDD},
read: float32(-3.3360025e-12), 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() serv := mbserver.NewServer()