telegraf/plugins/inputs/modbus/modbus_test.go

625 lines
14 KiB
Go

package modbus
import (
"testing"
m "github.com/goburrow/modbus"
"github.com/stretchr/testify/assert"
"github.com/tbrandon/mbserver"
"github.com/influxdata/telegraf/testutil"
)
func TestCoils(t *testing.T) {
var coilTests = []struct {
name string
address uint16
quantity uint16
write []byte
read uint16
}{
{
name: "coil0_turn_off",
address: 0,
quantity: 1,
write: []byte{0x00},
read: 0,
},
{
name: "coil0_turn_on",
address: 0,
quantity: 1,
write: []byte{0x01},
read: 1,
},
{
name: "coil1_turn_on",
address: 1,
quantity: 1,
write: []byte{0x01},
read: 1,
},
{
name: "coil2_turn_on",
address: 2,
quantity: 1,
write: []byte{0x01},
read: 1,
},
{
name: "coil3_turn_on",
address: 3,
quantity: 1,
write: []byte{0x01},
read: 1,
},
{
name: "coil1_turn_off",
address: 1,
quantity: 1,
write: []byte{0x00},
read: 0,
},
{
name: "coil2_turn_off",
address: 2,
quantity: 1,
write: []byte{0x00},
read: 0,
},
{
name: "coil3_turn_off",
address: 3,
quantity: 1,
write: []byte{0x00},
read: 0,
},
}
serv := mbserver.NewServer()
err := serv.ListenTCP("localhost:1502")
defer serv.Close()
assert.NoError(t, err)
handler := m.NewTCPClientHandler("localhost:1502")
err = handler.Connect()
assert.NoError(t, err)
defer handler.Close()
client := m.NewClient(handler)
for _, ct := range coilTests {
t.Run(ct.name, func(t *testing.T) {
_, err = client.WriteMultipleCoils(ct.address, ct.quantity, ct.write)
assert.NoError(t, err)
modbus := Modbus{
Name: "TestCoils",
Controller: "tcp://localhost:1502",
SlaveID: 1,
Coils: []fieldContainer{
{
Name: ct.name,
Address: []uint16{ct.address},
},
},
}
err = modbus.Init()
assert.NoError(t, err)
var acc testutil.Accumulator
err = modbus.Gather(&acc)
assert.NoError(t, err)
assert.NotEmpty(t, modbus.registers)
for _, coil := range modbus.registers {
assert.Equal(t, ct.read, coil.Fields[0].value)
}
})
}
}
func TestHoldingRegisters(t *testing.T) {
var holdingRegisterTests = []struct {
name string
address []uint16
quantity uint16
byteOrder string
dataType string
scale float64
write []byte
read interface{}
}{
{
name: "register0_ab_float32",
address: []uint16{0},
quantity: 1,
byteOrder: "AB",
dataType: "FLOAT32",
scale: 0.1,
write: []byte{0x08, 0x98},
read: float64(220),
},
{
name: "register0_register1_ab_float32",
address: []uint16{0, 1},
quantity: 2,
byteOrder: "ABCD",
dataType: "FLOAT32",
scale: 0.001,
write: []byte{0x00, 0x00, 0x03, 0xE8},
read: float64(1),
},
{
name: "register1_register2_abcd_float32",
address: []uint16{1, 2},
quantity: 2,
byteOrder: "ABCD",
dataType: "FLOAT32",
scale: 0.1,
write: []byte{0x00, 0x00, 0x08, 0x98},
read: float64(220),
},
{
name: "register3_register4_abcd_float32",
address: []uint16{3, 4},
quantity: 2,
byteOrder: "ABCD",
dataType: "FLOAT32",
scale: 0.1,
write: []byte{0x00, 0x00, 0x08, 0x98},
read: float64(220),
},
{
name: "register7_ab_float32",
address: []uint16{7},
quantity: 1,
byteOrder: "AB",
dataType: "FLOAT32",
scale: 0.1,
write: []byte{0x01, 0xF4},
read: float64(50),
},
{
name: "register10_ab_uint16",
address: []uint16{10},
quantity: 1,
byteOrder: "AB",
dataType: "UINT16",
scale: 1,
write: []byte{0xAB, 0xCD},
read: uint16(43981),
},
{
name: "register10_ab_uint16-scale_.1",
address: []uint16{10},
quantity: 1,
byteOrder: "AB",
dataType: "UINT16",
scale: .1,
write: []byte{0xAB, 0xCD},
read: uint16(4398),
},
{
name: "register10_ab_uint16_scale_10",
address: []uint16{10},
quantity: 1,
byteOrder: "AB",
dataType: "UINT16",
scale: 10,
write: []byte{0x00, 0x2A},
read: uint16(420),
},
{
name: "register20_ba_uint16",
address: []uint16{20},
quantity: 1,
byteOrder: "BA",
dataType: "UINT16",
scale: 1,
write: []byte{0xAB, 0xCD},
read: uint16(52651),
},
{
name: "register30_ab_int16",
address: []uint16{20},
quantity: 1,
byteOrder: "AB",
dataType: "INT16",
scale: 1,
write: []byte{0xAB, 0xCD},
read: int16(-21555),
},
{
name: "register40_ba_int16",
address: []uint16{40},
quantity: 1,
byteOrder: "BA",
dataType: "INT16",
scale: 1,
write: []byte{0xAB, 0xCD},
read: int16(-12885),
},
{
name: "register50_register51_abcd_int32_scaled",
address: []uint16{50, 51},
quantity: 2,
byteOrder: "ABCD",
dataType: "INT32",
scale: 10,
write: []byte{0x00, 0x00, 0xAB, 0xCD},
read: int32(439810),
},
{
name: "register50_register51_abcd_int32",
address: []uint16{50, 51},
quantity: 2,
byteOrder: "ABCD",
dataType: "INT32",
scale: 1,
write: []byte{0xAA, 0xBB, 0xCC, 0xDD},
read: int32(-1430532899),
},
{
name: "register60_register61_dcba_int32",
address: []uint16{60, 61},
quantity: 2,
byteOrder: "DCBA",
dataType: "INT32",
scale: 1,
write: []byte{0xAA, 0xBB, 0xCC, 0xDD},
read: int32(-573785174),
},
{
name: "register70_register71_badc_int32",
address: []uint16{70, 71},
quantity: 2,
byteOrder: "BADC",
dataType: "INT32",
scale: 1,
write: []byte{0xAA, 0xBB, 0xCC, 0xDD},
read: int32(-1146430004),
},
{
name: "register80_register81_cdab_int32",
address: []uint16{80, 81},
quantity: 2,
byteOrder: "CDAB",
dataType: "INT32",
scale: 1,
write: []byte{0xAA, 0xBB, 0xCC, 0xDD},
read: int32(-857888069),
},
{
name: "register90_register91_abcd_uint32",
address: []uint16{90, 91},
quantity: 2,
byteOrder: "ABCD",
dataType: "UINT32",
scale: 1,
write: []byte{0xAA, 0xBB, 0xCC, 0xDD},
read: uint32(2864434397),
},
{
name: "register100_register101_dcba_uint32",
address: []uint16{100, 101},
quantity: 2,
byteOrder: "DCBA",
dataType: "UINT32",
scale: 1,
write: []byte{0xAA, 0xBB, 0xCC, 0xDD},
read: uint32(3721182122),
},
{
name: "register110_register111_badc_uint32",
address: []uint16{110, 111},
quantity: 2,
byteOrder: "BADC",
dataType: "UINT32",
scale: 1,
write: []byte{0xAA, 0xBB, 0xCC, 0xDD},
read: uint32(3148537292),
},
{
name: "register120_register121_cdab_uint32",
address: []uint16{120, 121},
quantity: 2,
byteOrder: "CDAB",
dataType: "UINT32",
scale: 1,
write: []byte{0xAA, 0xBB, 0xCC, 0xDD},
read: uint32(3437079227),
},
{
name: "register130_register131_abcd_float32_ieee",
address: []uint16{130, 131},
quantity: 2,
byteOrder: "ABCD",
dataType: "FLOAT32-IEEE",
scale: 1,
write: []byte{0xAA, 0xBB, 0xCC, 0xDD},
read: float32(-3.3360025e-13),
},
{
name: "register130_register131_abcd_float32_ieee_scaled",
address: []uint16{130, 131},
quantity: 2,
byteOrder: "ABCD",
dataType: "FLOAT32-IEEE",
scale: 10,
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()
err := serv.ListenTCP("localhost:1502")
defer serv.Close()
assert.NoError(t, err)
handler := m.NewTCPClientHandler("localhost:1502")
err = handler.Connect()
assert.NoError(t, err)
defer handler.Close()
client := m.NewClient(handler)
for _, hrt := range holdingRegisterTests {
t.Run(hrt.name, func(t *testing.T) {
_, err = client.WriteMultipleRegisters(hrt.address[0], hrt.quantity, hrt.write)
assert.NoError(t, err)
modbus := Modbus{
Name: "TestHoldingRegisters",
Controller: "tcp://localhost:1502",
SlaveID: 1,
HoldingRegisters: []fieldContainer{
{
Name: hrt.name,
ByteOrder: hrt.byteOrder,
DataType: hrt.dataType,
Scale: hrt.scale,
Address: hrt.address,
},
},
}
err = modbus.Init()
assert.NoError(t, err)
var acc testutil.Accumulator
modbus.Gather(&acc)
assert.NotEmpty(t, modbus.registers)
for _, coil := range modbus.registers {
assert.Equal(t, hrt.read, coil.Fields[0].value)
}
})
}
}
func TestRetrySuccessful(t *testing.T) {
retries := 0
maxretries := 2
value := 1
serv := mbserver.NewServer()
err := serv.ListenTCP("localhost:1502")
assert.NoError(t, err)
defer serv.Close()
// Make read on coil-registers fail for some trials by making the device
// to appear busy
serv.RegisterFunctionHandler(1,
func(s *mbserver.Server, frame mbserver.Framer) ([]byte, *mbserver.Exception) {
data := make([]byte, 2)
data[0] = byte(1)
data[1] = byte(value)
except := &mbserver.SlaveDeviceBusy
if retries >= maxretries {
except = &mbserver.Success
}
retries += 1
return data, except
})
t.Run("retry_success", func(t *testing.T) {
modbus := Modbus{
Name: "TestRetry",
Controller: "tcp://localhost:1502",
SlaveID: 1,
Retries: maxretries,
Coils: []fieldContainer{
{
Name: "retry_success",
Address: []uint16{0},
},
},
}
err = modbus.Init()
assert.NoError(t, err)
var acc testutil.Accumulator
err = modbus.Gather(&acc)
assert.NoError(t, err)
assert.NotEmpty(t, modbus.registers)
for _, coil := range modbus.registers {
assert.Equal(t, uint16(value), coil.Fields[0].value)
}
})
}
func TestRetryFail(t *testing.T) {
maxretries := 2
serv := mbserver.NewServer()
err := serv.ListenTCP("localhost:1502")
assert.NoError(t, err)
defer serv.Close()
// Make the read on coils fail with busy
serv.RegisterFunctionHandler(1,
func(s *mbserver.Server, frame mbserver.Framer) ([]byte, *mbserver.Exception) {
data := make([]byte, 2)
data[0] = byte(1)
data[1] = byte(0)
return data, &mbserver.SlaveDeviceBusy
})
t.Run("retry_fail", func(t *testing.T) {
modbus := Modbus{
Name: "TestRetryFail",
Controller: "tcp://localhost:1502",
SlaveID: 1,
Retries: maxretries,
Coils: []fieldContainer{
{
Name: "retry_fail",
Address: []uint16{0},
},
},
}
err = modbus.Init()
assert.NoError(t, err)
var acc testutil.Accumulator
err = modbus.Gather(&acc)
assert.Error(t, err)
})
// Make the read on coils fail with illegal function preventing retry
counter := 0
serv.RegisterFunctionHandler(1,
func(s *mbserver.Server, frame mbserver.Framer) ([]byte, *mbserver.Exception) {
counter += 1
data := make([]byte, 2)
data[0] = byte(1)
data[1] = byte(0)
return data, &mbserver.IllegalFunction
})
t.Run("retry_fail", func(t *testing.T) {
modbus := Modbus{
Name: "TestRetryFail",
Controller: "tcp://localhost:1502",
SlaveID: 1,
Retries: maxretries,
Coils: []fieldContainer{
{
Name: "retry_fail",
Address: []uint16{0},
},
},
}
err = modbus.Init()
assert.NoError(t, err)
var acc testutil.Accumulator
err = modbus.Gather(&acc)
assert.Error(t, err)
assert.Equal(t, counter, 1)
})
}