package modbus import ( "fmt" "strconv" "testing" "time" mb "github.com/grid-x/modbus" "github.com/stretchr/testify/require" "github.com/tbrandon/mbserver" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/testutil" ) func TestRegister(t *testing.T) { modbus := Modbus{ Name: "TestRetryFailExhausted", Controller: "tcp://localhost:1502", ConfigurationType: "register", Log: testutil.Logger{}, } modbus.SlaveID = 1 modbus.Coils = []fieldDefinition{ { Name: "coil", Address: []uint16{0}, }, } modbus.DiscreteInputs = []fieldDefinition{ { Name: "discrete", Address: []uint16{0}, }, } modbus.HoldingRegisters = []fieldDefinition{ { Name: "holding", Address: []uint16{0}, DataType: "INT16", ByteOrder: "AB", Scale: 1.0, }, } modbus.InputRegisters = []fieldDefinition{ { Name: "input", Address: []uint16{0}, DataType: "INT16", ByteOrder: "AB", Scale: 1.0, }, } require.NoError(t, modbus.Init()) require.NotEmpty(t, modbus.requests) require.NotNil(t, modbus.requests[1]) require.Len(t, modbus.requests[1].coil, len(modbus.Coils)) require.Len(t, modbus.requests[1].discrete, len(modbus.DiscreteInputs)) require.Len(t, modbus.requests[1].holding, len(modbus.HoldingRegisters)) require.Len(t, modbus.requests[1].input, len(modbus.InputRegisters)) } func TestRegisterCoils(t *testing.T) { var coilTests = []struct { name string address uint16 dtype string quantity uint16 write []byte read interface{} }{ { name: "coil0_turn_off", address: 0, quantity: 1, write: []byte{0x00}, read: uint16(0), }, { name: "coil0_turn_on", address: 0, quantity: 1, write: []byte{0x01}, read: uint16(1), }, { name: "coil1_turn_on", address: 1, quantity: 1, write: []byte{0x01}, read: uint16(1), }, { name: "coil2_turn_on", address: 2, quantity: 1, write: []byte{0x01}, read: uint16(1), }, { name: "coil3_turn_on", address: 3, quantity: 1, write: []byte{0x01}, read: uint16(1), }, { name: "coil1_turn_off", address: 1, quantity: 1, write: []byte{0x00}, read: uint16(0), }, { name: "coil2_turn_off", address: 2, quantity: 1, write: []byte{0x00}, read: uint16(0), }, { name: "coil3_turn_off", address: 3, quantity: 1, write: []byte{0x00}, read: uint16(0), }, { name: "coil4_turn_off", address: 4, quantity: 1, write: []byte{0x00}, read: uint16(0), }, { name: "coil4_turn_on", address: 4, quantity: 1, write: []byte{0x01}, read: uint16(1), }, { name: "coil4_turn_off_bool", address: 4, quantity: 1, dtype: "BOOL", write: []byte{0x00}, read: false, }, { name: "coil4_turn_on_bool", address: 4, quantity: 1, dtype: "BOOL", write: []byte{0x01}, read: true, }, } serv := mbserver.NewServer() require.NoError(t, serv.ListenTCP("localhost:1502")) defer serv.Close() handler := mb.NewTCPClientHandler("localhost:1502") require.NoError(t, handler.Connect()) defer handler.Close() client := mb.NewClient(handler) for _, ct := range coilTests { t.Run(ct.name, func(t *testing.T) { _, err := client.WriteMultipleCoils(ct.address, ct.quantity, ct.write) require.NoError(t, err) modbus := Modbus{ Name: "TestCoils", Controller: "tcp://localhost:1502", Log: testutil.Logger{}, } modbus.SlaveID = 1 modbus.Coils = []fieldDefinition{ { Name: ct.name, Address: []uint16{ct.address}, DataType: ct.dtype, }, } expected := []telegraf.Metric{ testutil.MustMetric( "modbus", map[string]string{ "type": cCoils, "slave_id": strconv.Itoa(int(modbus.SlaveID)), "name": modbus.Name, }, map[string]interface{}{ct.name: ct.read}, time.Unix(0, 0), ), } var acc testutil.Accumulator require.NoError(t, modbus.Init()) require.NotEmpty(t, modbus.requests) require.NoError(t, modbus.Gather(&acc)) acc.Wait(len(expected)) testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime()) }) } } func TestRegisterHoldingRegisters(t *testing.T) { var holdingRegisterTests = []struct { name string address []uint16 quantity uint16 bit uint8 byteOrder string dataType string scale float64 write []byte read interface{} }{ { name: "register5_bit3", address: []uint16{5}, quantity: 1, byteOrder: "AB", dataType: "BIT", bit: 3, write: []byte{0x18, 0x0d}, read: uint8(1), }, { name: "register5_bit14", address: []uint16{5}, quantity: 1, byteOrder: "AB", dataType: "BIT", bit: 14, write: []byte{0x18, 0x0d}, read: uint8(0), }, { 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: "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: "register20_uint16", address: []uint16{10}, quantity: 1, byteOrder: "AB", dataType: "UINT8L", scale: 1, write: []byte{0x18, 0x0D}, read: uint8(13), }, { name: "register20_uint16-scale_.1", address: []uint16{10}, quantity: 1, byteOrder: "AB", dataType: "UINT8L", scale: .1, write: []byte{0x18, 0x0D}, read: uint8(1), }, { name: "register20_uint16_scale_10", address: []uint16{10}, quantity: 1, byteOrder: "AB", dataType: "UINT8L", scale: 10, write: []byte{0x18, 0x0D}, read: uint8(130), }, { name: "register11_uint8H", address: []uint16{11}, quantity: 1, byteOrder: "AB", dataType: "UINT8H", scale: 1, write: []byte{0x18, 0x0D}, read: uint8(24), }, { name: "register11_uint8L-scale_.1", address: []uint16{11}, quantity: 1, byteOrder: "AB", dataType: "UINT8H", scale: .1, write: []byte{0x18, 0x0D}, read: uint8(2), }, { name: "register11_uint8L_scale_10", address: []uint16{11}, quantity: 1, byteOrder: "AB", dataType: "UINT8H", scale: 10, write: []byte{0x18, 0x0D}, read: uint8(240), }, { name: "register12_int8L", address: []uint16{12}, quantity: 1, byteOrder: "AB", dataType: "INT8L", scale: 1, write: []byte{0x98, 0x8D}, read: int8(-115), }, { name: "register12_int8L-scale_.1", address: []uint16{12}, quantity: 1, byteOrder: "AB", dataType: "INT8L", scale: .1, write: []byte{0x98, 0x8D}, read: int8(-11), }, { name: "register12_int8L_scale_10", address: []uint16{12}, quantity: 1, byteOrder: "AB", dataType: "INT8L", scale: 10, write: []byte{0x98, 0xF8}, read: int8(-80), }, { name: "register13_int8H", address: []uint16{13}, quantity: 1, byteOrder: "AB", dataType: "INT8H", scale: 1, write: []byte{0x98, 0x8D}, read: int8(-104), }, { name: "register13_int8H-scale_.1", address: []uint16{13}, quantity: 1, byteOrder: "AB", dataType: "INT8H", scale: .1, write: []byte{0x98, 0x8D}, read: int8(-10), }, { name: "register13_int8H_scale_10", address: []uint16{13}, quantity: 1, byteOrder: "AB", dataType: "INT8H", scale: 10, write: []byte{0xFD, 0x8D}, read: int8(-30), }, { name: "register15_ab_uint16", address: []uint16{15}, quantity: 1, byteOrder: "AB", dataType: "UINT16", scale: 1, write: []byte{0xAB, 0xCD}, read: uint16(43981), }, { name: "register15_ab_uint16-scale_.1", address: []uint16{15}, quantity: 1, byteOrder: "AB", dataType: "UINT16", scale: .1, write: []byte{0xAB, 0xCD}, read: uint16(4398), }, { name: "register15_ab_uint16_scale_10", address: []uint16{15}, 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), }, { name: "register214_to_register217_abcdefgh_float64_ieee", address: []uint16{214, 215, 216, 217}, quantity: 4, byteOrder: "ABCDEFGH", dataType: "FLOAT64-IEEE", scale: 1, write: []byte{0xBF, 0x9C, 0x6A, 0x40, 0xC3, 0x47, 0x8F, 0x55}, read: float64(-0.02774907295123737), }, { name: "register214_to_register217_abcdefgh_float64_ieee_scaled", address: []uint16{214, 215, 216, 217}, quantity: 4, byteOrder: "ABCDEFGH", dataType: "FLOAT64-IEEE", scale: 0.1, write: []byte{0xBF, 0x9C, 0x6A, 0x40, 0xC3, 0x47, 0x8F, 0x55}, read: float64(-0.002774907295123737), }, { name: "register218_to_register221_abcdefgh_float64_ieee_pos", address: []uint16{218, 219, 220, 221}, quantity: 4, byteOrder: "ABCDEFGH", dataType: "FLOAT64-IEEE", scale: 1, write: []byte{0x3F, 0x9C, 0x6A, 0x40, 0xC3, 0x47, 0x8F, 0x55}, read: float64(0.02774907295123737), }, { name: "register222_to_register225_hgfecdba_float64_ieee", address: []uint16{222, 223, 224, 225}, quantity: 4, byteOrder: "HGFEDCBA", dataType: "FLOAT64-IEEE", scale: 1, write: []byte{0x55, 0x8F, 0x47, 0xC3, 0x40, 0x6A, 0x9C, 0xBF}, read: float64(-0.02774907295123737), }, { name: "register226_to_register229_badcfehg_float64_ieee", address: []uint16{226, 227, 228, 229}, quantity: 4, byteOrder: "BADCFEHG", dataType: "FLOAT64-IEEE", scale: 1, write: []byte{0x9C, 0xBF, 0x40, 0x6A, 0x47, 0xC3, 0x55, 0x8F}, read: float64(-0.02774907295123737), }, { name: "register230_to_register233_ghefcdab_float64_ieee", address: []uint16{230, 231, 232, 233}, quantity: 4, byteOrder: "GHEFCDAB", dataType: "FLOAT64-IEEE", scale: 1, write: []byte{0x8F, 0x55, 0xC3, 0x47, 0x6A, 0x40, 0xBF, 0x9C}, read: float64(-0.02774907295123737), }, { name: "register240_abcd_float16", address: []uint16{240}, quantity: 1, byteOrder: "AB", dataType: "FLOAT16-IEEE", scale: 1, write: []byte{0xb8, 0x14}, read: float64(-0.509765625), }, { name: "register240_dcba_float16", address: []uint16{240}, quantity: 1, byteOrder: "BA", dataType: "FLOAT16-IEEE", scale: 1, write: []byte{0x14, 0xb8}, read: float64(-0.509765625), }, { name: "register250_abcd_string", address: []uint16{250, 251, 252, 253, 254, 255, 256}, quantity: 7, byteOrder: "AB", dataType: "STRING", write: []byte{0x4d, 0x6f, 0x64, 0x62, 0x75, 0x73, 0x20, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x00}, read: "Modbus String", }, { name: "register250_dcba_string", address: []uint16{250, 251, 252, 253, 254, 255, 256}, quantity: 7, byteOrder: "BA", dataType: "STRING", write: []byte{0x6f, 0x4d, 0x62, 0x64, 0x73, 0x75, 0x53, 0x20, 0x72, 0x74, 0x6e, 0x69, 0x00, 0x67}, read: "Modbus String", }, } serv := mbserver.NewServer() require.NoError(t, serv.ListenTCP("localhost:1502")) defer serv.Close() handler := mb.NewTCPClientHandler("localhost:1502") require.NoError(t, handler.Connect()) defer handler.Close() client := mb.NewClient(handler) for _, hrt := range holdingRegisterTests { t.Run(hrt.name, func(t *testing.T) { _, err := client.WriteMultipleRegisters(hrt.address[0], hrt.quantity, hrt.write) require.NoError(t, err) modbus := Modbus{ Name: "TestHoldingRegisters", Controller: "tcp://localhost:1502", Log: testutil.Logger{}, } modbus.SlaveID = 1 modbus.HoldingRegisters = []fieldDefinition{ { Name: hrt.name, ByteOrder: hrt.byteOrder, DataType: hrt.dataType, Scale: hrt.scale, Address: hrt.address, Bit: hrt.bit, }, } expected := []telegraf.Metric{ testutil.MustMetric( "modbus", map[string]string{ "type": cHoldingRegisters, "slave_id": strconv.Itoa(int(modbus.SlaveID)), "name": modbus.Name, }, map[string]interface{}{hrt.name: hrt.read}, time.Unix(0, 0), ), } var acc testutil.Accumulator require.NoError(t, modbus.Init()) require.NotEmpty(t, modbus.requests) require.NoError(t, modbus.Gather(&acc)) acc.Wait(len(expected)) testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime()) }) } } func TestRegisterReadMultipleCoilWithHole(t *testing.T) { serv := mbserver.NewServer() require.NoError(t, serv.ListenTCP("localhost:1502")) defer serv.Close() handler := mb.NewTCPClientHandler("localhost:1502") require.NoError(t, handler.Connect()) defer handler.Close() client := mb.NewClient(handler) fcs := make([]fieldDefinition, 0, 26) expectedFields := make(map[string]interface{}) writeValue := uint16(0) readValue := uint16(0) for i := 0; i < 14; i++ { fc := fieldDefinition{} fc.Name = fmt.Sprintf("coil-%v", i) fc.Address = []uint16{uint16(i)} fcs = append(fcs, fc) _, err := client.WriteSingleCoil(fc.Address[0], writeValue) require.NoError(t, err) expectedFields[fc.Name] = readValue writeValue = 65280 - writeValue readValue = 1 - readValue } for i := 15; i < 18; i++ { fc := fieldDefinition{} fc.Name = fmt.Sprintf("coil-%v", i) fc.Address = []uint16{uint16(i)} fcs = append(fcs, fc) _, err := client.WriteSingleCoil(fc.Address[0], writeValue) require.NoError(t, err) expectedFields[fc.Name] = readValue writeValue = 65280 - writeValue readValue = 1 - readValue } for i := 24; i < 33; i++ { fc := fieldDefinition{} fc.Name = fmt.Sprintf("coil-%v", i) fc.Address = []uint16{uint16(i)} fcs = append(fcs, fc) _, err := client.WriteSingleCoil(fc.Address[0], writeValue) require.NoError(t, err) expectedFields[fc.Name] = readValue writeValue = 65280 - writeValue readValue = 1 - readValue } require.Len(t, expectedFields, len(fcs)) modbus := Modbus{ Name: "TestReadMultipleCoilWithHole", Controller: "tcp://localhost:1502", Log: testutil.Logger{Name: "modbus:MultipleCoilWithHole"}, } modbus.SlaveID = 1 modbus.Coils = fcs expected := []telegraf.Metric{ testutil.MustMetric( "modbus", map[string]string{ "type": cCoils, "slave_id": strconv.Itoa(int(modbus.SlaveID)), "name": modbus.Name, }, expectedFields, time.Unix(0, 0), ), } var acc testutil.Accumulator require.NoError(t, modbus.Init()) require.NotEmpty(t, modbus.requests) require.NoError(t, modbus.Gather(&acc)) acc.Wait(len(expected)) testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime()) } func TestRegisterReadMultipleCoilLimit(t *testing.T) { serv := mbserver.NewServer() require.NoError(t, serv.ListenTCP("localhost:1502")) defer serv.Close() handler := mb.NewTCPClientHandler("localhost:1502") require.NoError(t, handler.Connect()) defer handler.Close() client := mb.NewClient(handler) fcs := make([]fieldDefinition, 0, 4000) expectedFields := make(map[string]interface{}) writeValue := uint16(0) readValue := uint16(0) for i := 0; i < 4000; i++ { fc := fieldDefinition{} fc.Name = fmt.Sprintf("coil-%v", i) fc.Address = []uint16{uint16(i)} fcs = append(fcs, fc) _, err := client.WriteSingleCoil(fc.Address[0], writeValue) require.NoError(t, err) expectedFields[fc.Name] = readValue writeValue = 65280 - writeValue readValue = 1 - readValue } require.Len(t, expectedFields, len(fcs)) modbus := Modbus{ Name: "TestReadCoils", Controller: "tcp://localhost:1502", Log: testutil.Logger{}, } modbus.SlaveID = 1 modbus.Coils = fcs expected := []telegraf.Metric{ testutil.MustMetric( "modbus", map[string]string{ "type": cCoils, "slave_id": strconv.Itoa(int(modbus.SlaveID)), "name": modbus.Name, }, expectedFields, time.Unix(0, 0), ), } var acc testutil.Accumulator require.NoError(t, modbus.Init()) require.NotEmpty(t, modbus.requests) require.NoError(t, modbus.Gather(&acc)) acc.Wait(len(expected)) testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime()) } func TestRegisterReadMultipleHoldingRegisterWithHole(t *testing.T) { serv := mbserver.NewServer() require.NoError(t, serv.ListenTCP("localhost:1502")) defer serv.Close() handler := mb.NewTCPClientHandler("localhost:1502") require.NoError(t, handler.Connect()) defer handler.Close() client := mb.NewClient(handler) fcs := make([]fieldDefinition, 0, 20) expectedFields := make(map[string]interface{}) for i := 0; i < 10; i++ { fc := fieldDefinition{ Name: fmt.Sprintf("HoldingRegister-%v", i), ByteOrder: "AB", DataType: "INT16", Scale: 1.0, Address: []uint16{uint16(i)}, } fcs = append(fcs, fc) _, err := client.WriteSingleRegister(fc.Address[0], uint16(i)) require.NoError(t, err) expectedFields[fc.Name] = int64(i) } for i := 20; i < 30; i++ { fc := fieldDefinition{ Name: fmt.Sprintf("HoldingRegister-%v", i), ByteOrder: "AB", DataType: "INT16", Scale: 1.0, Address: []uint16{uint16(i)}, } fcs = append(fcs, fc) _, err := client.WriteSingleRegister(fc.Address[0], uint16(i)) require.NoError(t, err) expectedFields[fc.Name] = int64(i) } require.Len(t, expectedFields, len(fcs)) modbus := Modbus{ Name: "TestHoldingRegister", Controller: "tcp://localhost:1502", Log: testutil.Logger{}, } modbus.SlaveID = 1 modbus.HoldingRegisters = fcs expected := []telegraf.Metric{ testutil.MustMetric( "modbus", map[string]string{ "type": cHoldingRegisters, "slave_id": strconv.Itoa(int(modbus.SlaveID)), "name": modbus.Name, }, expectedFields, time.Unix(0, 0), ), } var acc testutil.Accumulator require.NoError(t, modbus.Init()) require.NotEmpty(t, modbus.requests) require.NoError(t, modbus.Gather(&acc)) acc.Wait(len(expected)) testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime()) } func TestRegisterReadMultipleHoldingRegisterLimit(t *testing.T) { serv := mbserver.NewServer() require.NoError(t, serv.ListenTCP("localhost:1502")) defer serv.Close() handler := mb.NewTCPClientHandler("localhost:1502") require.NoError(t, handler.Connect()) defer handler.Close() client := mb.NewClient(handler) fcs := make([]fieldDefinition, 0, 401) expectedFields := make(map[string]interface{}) for i := 0; i <= 400; i++ { fc := fieldDefinition{} fc.Name = fmt.Sprintf("HoldingRegister-%v", i) fc.ByteOrder = "AB" fc.DataType = "INT16" fc.Scale = 1.0 fc.Address = []uint16{uint16(i)} fcs = append(fcs, fc) _, err := client.WriteSingleRegister(fc.Address[0], uint16(i)) require.NoError(t, err) expectedFields[fc.Name] = int64(i) } modbus := Modbus{ Name: "TestHoldingRegister", Controller: "tcp://localhost:1502", Log: testutil.Logger{}, } modbus.SlaveID = 1 modbus.HoldingRegisters = fcs expected := []telegraf.Metric{ testutil.MustMetric( "modbus", map[string]string{ "type": cHoldingRegisters, "slave_id": strconv.Itoa(int(modbus.SlaveID)), "name": modbus.Name, }, expectedFields, time.Unix(0, 0), ), } var acc testutil.Accumulator require.NoError(t, modbus.Init()) require.NotEmpty(t, modbus.requests) require.NoError(t, modbus.Gather(&acc)) acc.Wait(len(expected)) testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime()) } func TestRegisterHighAddresses(t *testing.T) { // Test case for issue https://github.com/influxdata/telegraf/issues/15138 // Setup a server serv := mbserver.NewServer() require.NoError(t, serv.ListenTCP("localhost:1502")) defer serv.Close() handler := mb.NewTCPClientHandler("localhost:1502") require.NoError(t, handler.Connect()) defer handler.Close() client := mb.NewClient(handler) // Write the register values data := []byte{ 0x4d, 0x6f, 0x64, 0x62, 0x75, 0x73, 0x20, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x00, } _, err := client.WriteMultipleRegisters(65524, 10, data) require.NoError(t, err) _, err = client.WriteMultipleRegisters(65534, 1, []byte{0x10, 0x92}) require.NoError(t, err) modbus := Modbus{ Name: "Issue-15138", Controller: "tcp://localhost:1502", Log: testutil.Logger{}, } modbus.SlaveID = 1 modbus.HoldingRegisters = []fieldDefinition{ { Name: "DeviceName", ByteOrder: "AB", DataType: "STRING", Address: []uint16{65524, 65525, 65526, 65527, 65528, 65529, 65530, 65531, 65532, 65533}, }, { Name: "DeviceConnectionStatus", ByteOrder: "AB", DataType: "UINT16", Address: []uint16{65534}, Scale: 1, }, } expected := []telegraf.Metric{ testutil.MustMetric( "modbus", map[string]string{ "type": cHoldingRegisters, "slave_id": strconv.Itoa(int(modbus.SlaveID)), "name": modbus.Name, }, map[string]interface{}{ "DeviceName": "Modbus String Hello", "DeviceConnectionStatus": uint16(4242), }, time.Unix(0, 0), ), } var acc testutil.Accumulator require.NoError(t, modbus.Init()) require.NotEmpty(t, modbus.requests) require.Len(t, modbus.requests[1].holding, 1) require.NoError(t, modbus.Gather(&acc)) testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime()) }