From fe7321e35bfac1ebff52793e7826bee6a0551536 Mon Sep 17 00:00:00 2001 From: Sven Rebhan <36194019+srebhan@users.noreply.github.com> Date: Mon, 29 Jul 2024 18:48:05 +0200 Subject: [PATCH] feat(inputs.modbus): Allow reading single bits of input and holding registers (#15648) --- plugins/inputs/modbus/README.md | 14 +++- plugins/inputs/modbus/configuration.go | 2 +- plugins/inputs/modbus/configuration_metric.go | 20 ++++- .../modbus/configuration_metric_test.go | 49 +++++++++++- .../inputs/modbus/configuration_register.go | 75 +++++++++++-------- .../modbus/configuration_register_test.go | 22 ++++++ .../inputs/modbus/configuration_request.go | 20 ++++- .../modbus/configuration_request_test.go | 18 +++++ plugins/inputs/modbus/sample_metric.conf | 2 + plugins/inputs/modbus/sample_register.conf | 4 +- plugins/inputs/modbus/sample_request.conf | 2 + plugins/inputs/modbus/type_conversions.go | 7 +- plugins/inputs/modbus/type_conversions_bit.go | 14 ++++ 13 files changed, 204 insertions(+), 45 deletions(-) create mode 100644 plugins/inputs/modbus/type_conversions_bit.go diff --git a/plugins/inputs/modbus/README.md b/plugins/inputs/modbus/README.md index ce0e59764a994..8fd7b4bb6b2aa 100644 --- a/plugins/inputs/modbus/README.md +++ b/plugins/inputs/modbus/README.md @@ -101,12 +101,14 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details. ## |---BA, DCBA - Little Endian ## |---BADC - Mid-Big Endian ## |---CDAB - Mid-Little Endian - ## data_type - INT8L, INT8H, UINT8L, UINT8H (low and high byte variants) + ## data_type - BIT (single bit of a register) + ## INT8L, INT8H, UINT8L, UINT8H (low and high byte variants) ## INT16, UINT16, INT32, UINT32, INT64, UINT64, ## FLOAT16-IEEE, FLOAT32-IEEE, FLOAT64-IEEE (IEEE 754 binary representation) ## FIXED, UFIXED (fixed-point representation on input) ## FLOAT32 is a deprecated alias for UFIXED for historic reasons, should be avoided ## STRING (byte-sequence converted to string) + ## bit - (optional) bit of the register, ONLY valid for BIT type ## scale - the final numeric variable representation ## address - variable address @@ -176,11 +178,13 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details. ## address - address of the register to query. For coil and discrete inputs this is the bit address. ## name *1 - field name ## type *1,2 - type of the modbus field, can be + ## BIT (single bit of a register) ## INT8L, INT8H, UINT8L, UINT8H (low and high byte variants) ## INT16, UINT16, INT32, UINT32, INT64, UINT64 and ## FLOAT16, FLOAT32, FLOAT64 (IEEE 754 binary representation) ## STRING (byte-sequence converted to string) ## length *1,2 - (optional) number of registers, ONLY valid for STRING type + ## bit *1,2 - (optional) bit of the register, ONLY valid for BIT type ## scale *1,2,4 - (optional) factor to scale the variable with ## output *1,3,4 - (optional) type of resulting field, can be INT64, UINT64 or FLOAT64. ## Defaults to FLOAT64 for numeric fields if "scale" is provided. @@ -286,11 +290,13 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details. ## address - address of the register to query. For coil and discrete inputs this is the bit address. ## name - field name ## type *1 - type of the modbus field, can be + ## BIT (single bit of a register) ## INT8L, INT8H, UINT8L, UINT8H (low and high byte variants) ## INT16, UINT16, INT32, UINT32, INT64, UINT64 and ## FLOAT16, FLOAT32, FLOAT64 (IEEE 754 binary representation) ## STRING (byte-sequence converted to string) ## length *1 - (optional) number of registers, ONLY valid for STRING type + ## bit *1,2 - (optional) bit of the register, ONLY valid for BIT type ## scale *1,3 - (optional) factor to scale the variable with ## output *2,3 - (optional) type of resulting field, can be INT64, UINT64 or FLOAT64. Defaults to FLOAT64 if ## "scale" is provided and to the input "type" class otherwise (i.e. INT* -> INT64, etc). @@ -461,6 +467,12 @@ setting and convert the byte-sequence to a string. Please note, if the byte-sequence contains a `null` byte, the string is truncated at this position. You cannot use the `scale` setting for string fields. +##### Bit: `BIT` + +This type is used to query a single bit of a register specified in the `address` +setting and convert the value to an unsigned integer. This type __requires__ the +`bit` setting to be specified. + --- ### `request` configuration style diff --git a/plugins/inputs/modbus/configuration.go b/plugins/inputs/modbus/configuration.go index 0e40e79fbbd59..a47af18b5db90 100644 --- a/plugins/inputs/modbus/configuration.go +++ b/plugins/inputs/modbus/configuration.go @@ -31,7 +31,7 @@ func removeDuplicates(elements []uint16) []uint16 { func normalizeInputDatatype(dataType string) (string, error) { switch dataType { - case "INT8L", "INT8H", "UINT8L", "UINT8H", + case "BIT", "INT8L", "INT8H", "UINT8L", "UINT8H", "INT16", "UINT16", "INT32", "UINT32", "INT64", "UINT64", "FLOAT16", "FLOAT32", "FLOAT64", "STRING": return dataType, nil diff --git a/plugins/inputs/modbus/configuration_metric.go b/plugins/inputs/modbus/configuration_metric.go index 59e4473dbb4ec..7547ec13719cd 100644 --- a/plugins/inputs/modbus/configuration_metric.go +++ b/plugins/inputs/modbus/configuration_metric.go @@ -21,6 +21,7 @@ type metricFieldDefinition struct { InputType string `toml:"type"` Scale float64 `toml:"scale"` OutputType string `toml:"output"` + Bit uint8 `toml:"bit"` } type metricDefinition struct { @@ -116,6 +117,9 @@ func (c *ConfigurationPerMetric) Check() error { if f.Length != 0 { return fmt.Errorf("length option cannot be used for type %q of field %q", f.InputType, f.Name) } + if f.Bit != 0 { + return fmt.Errorf("bit option cannot be used for type %q of field %q", f.InputType, f.Name) + } if f.OutputType == "STRING" { return fmt.Errorf("cannot output field %q as string", f.Name) } @@ -123,12 +127,22 @@ func (c *ConfigurationPerMetric) Check() error { if f.Length < 1 { return fmt.Errorf("missing length for string field %q", f.Name) } + if f.Bit != 0 { + return fmt.Errorf("bit option cannot be used for type %q of field %q", f.InputType, f.Name) + } if f.Scale != 0.0 { return fmt.Errorf("scale option cannot be used for string field %q", f.Name) } if f.OutputType != "" && f.OutputType != "STRING" { return fmt.Errorf("invalid output type %q for string field %q", f.OutputType, f.Name) } + case "BIT": + if f.Length != 0 { + return fmt.Errorf("length option cannot be used for type %q of field %q", f.InputType, f.Name) + } + if f.OutputType == "STRING" { + return fmt.Errorf("cannot output field %q as string", f.Name) + } default: return fmt.Errorf("unknown register data-type %q for field %q", f.InputType, f.Name) } @@ -315,7 +329,7 @@ func (c *ConfigurationPerMetric) newField(def metricFieldDefinition, mdef metric return field{}, err } - f.converter, err = determineConverter(inType, order, outType, def.Scale, c.workarounds.StringRegisterLocation) + f.converter, err = determineConverter(inType, order, outType, def.Scale, def.Bit, c.workarounds.StringRegisterLocation) if err != nil { return field{}, err } @@ -353,7 +367,7 @@ func (c *ConfigurationPerMetric) determineOutputDatatype(input string) (string, switch input { case "INT8L", "INT8H", "INT16", "INT32", "INT64": return "INT64", nil - case "UINT8L", "UINT8H", "UINT16", "UINT32", "UINT64": + case "BIT", "UINT8L", "UINT8H", "UINT16", "UINT32", "UINT64": return "UINT64", nil case "FLOAT16", "FLOAT32", "FLOAT64": return "FLOAT64", nil @@ -366,7 +380,7 @@ func (c *ConfigurationPerMetric) determineOutputDatatype(input string) (string, func (c *ConfigurationPerMetric) determineFieldLength(input string, length uint16) (uint16, error) { // Handle our special types switch input { - case "INT8L", "INT8H", "UINT8L", "UINT8H": + case "BIT", "INT8L", "INT8H", "UINT8L", "UINT8H": return 1, nil case "INT16", "UINT16", "FLOAT16": return 1, nil diff --git a/plugins/inputs/modbus/configuration_metric_test.go b/plugins/inputs/modbus/configuration_metric_test.go index 390ea28db32c6..8829c89cfabb3 100644 --- a/plugins/inputs/modbus/configuration_metric_test.go +++ b/plugins/inputs/modbus/configuration_metric_test.go @@ -255,13 +255,43 @@ func TestMetricResult(t *testing.T) { }, }, }, + { + SlaveID: 1, + Measurement: "bitvalues", + Fields: []metricFieldDefinition{ + { + Name: "bit 0", + Address: uint16(1), + InputType: "BIT", + Bit: 0, + }, + { + Name: "bit 1", + Address: uint16(1), + InputType: "BIT", + Bit: 1, + }, + { + Name: "bit 2", + Address: uint16(1), + InputType: "BIT", + Bit: 2, + }, + { + Name: "bit 3", + Address: uint16(1), + InputType: "BIT", + Bit: 3, + }, + }, + }, } require.NoError(t, plugin.Init()) // Check the generated requests require.Len(t, plugin.requests, 1) require.NotNil(t, plugin.requests[1]) - require.Len(t, plugin.requests[1].holding, 1) + require.Len(t, plugin.requests[1].holding, 5) require.Empty(t, plugin.requests[1].coil) require.Empty(t, plugin.requests[1].discrete) require.Empty(t, plugin.requests[1].input) @@ -313,10 +343,25 @@ func TestMetricResult(t *testing.T) { map[string]interface{}{"pi": float64(3.1415927410125732421875)}, time.Unix(0, 0), ), + metric.New( + "bitvalues", + map[string]string{ + "name": "FAKEMETER", + "slave_id": "1", + "type": "holding_register", + }, + map[string]interface{}{ + "bit 0": uint64(0), + "bit 1": uint64(1), + "bit 2": uint64(0), + "bit 3": uint64(1), + }, + time.Unix(0, 0), + ), } actual := acc.GetTelegrafMetrics() - testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime()) + testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime(), testutil.SortMetrics()) } func TestMetricAddressOverflow(t *testing.T) { diff --git a/plugins/inputs/modbus/configuration_register.go b/plugins/inputs/modbus/configuration_register.go index 648928f8591b2..b86ff5181f0f6 100644 --- a/plugins/inputs/modbus/configuration_register.go +++ b/plugins/inputs/modbus/configuration_register.go @@ -18,6 +18,7 @@ type fieldDefinition struct { DataType string `toml:"data_type"` Scale float64 `toml:"scale"` Address []uint16 `toml:"address"` + Bit uint8 `toml:"bit"` } type ConfigurationOriginal struct { @@ -172,7 +173,7 @@ func (c *ConfigurationOriginal) newFieldFromDefinition(def fieldDefinition, type return f, err } - f.converter, err = determineConverter(inType, byteOrder, outType, def.Scale, c.workarounds.StringRegisterLocation) + f.converter, err = determineConverter(inType, byteOrder, outType, def.Scale, def.Bit, c.workarounds.StringRegisterLocation) if err != nil { return f, err } @@ -213,7 +214,7 @@ func (c *ConfigurationOriginal) validateFieldDefinitions(fieldDefs []fieldDefini if item.Scale == 0.0 { return fmt.Errorf("invalid scale '%f' in %q - %q", item.Scale, registerType, item.Name) } - case "STRING": + case "BIT", "STRING": default: return fmt.Errorf("invalid data type %q in %q - %q", item.DataType, registerType, item.Name) } @@ -226,42 +227,50 @@ func (c *ConfigurationOriginal) validateFieldDefinitions(fieldDefs []fieldDefini } } - // check address - if item.DataType != "STRING" { - if len(item.Address) != 1 && len(item.Address) != 2 && len(item.Address) != 4 { - return fmt.Errorf("invalid address '%v' length '%v' in %q - %q", item.Address, len(item.Address), registerType, item.Name) + // Special address checking for special types + switch item.DataType { + case "STRING": + continue + case "BIT": + if len(item.Address) != 1 { + return fmt.Errorf("address '%v' has length '%v' bit should be one in %q - %q", item.Address, len(item.Address), registerType, item.Name) } + continue + } - if registerType == cInputRegisters || registerType == cHoldingRegisters { - if 2*len(item.Address) != len(item.ByteOrder) { - return fmt.Errorf("invalid byte order %q and address '%v' in %q - %q", item.ByteOrder, item.Address, registerType, item.Name) - } + // Check address + if len(item.Address) != 1 && len(item.Address) != 2 && len(item.Address) != 4 { + return fmt.Errorf("invalid address '%v' length '%v' in %q - %q", item.Address, len(item.Address), registerType, item.Name) + } - // Check for the request size corresponding to the data-type - var requiredAddresses int - switch item.DataType { - case "INT8L", "INT8H", "UINT8L", "UINT8H", "UINT16", "INT16", "FLOAT16-IEEE": - requiredAddresses = 1 - case "UINT32", "INT32", "FLOAT32-IEEE": - requiredAddresses = 2 + if registerType == cInputRegisters || registerType == cHoldingRegisters { + if 2*len(item.Address) != len(item.ByteOrder) { + return fmt.Errorf("invalid byte order %q and address '%v' in %q - %q", item.ByteOrder, item.Address, registerType, item.Name) + } - case "UINT64", "INT64", "FLOAT64-IEEE": - requiredAddresses = 4 - } - if requiredAddresses > 0 && len(item.Address) != requiredAddresses { - return fmt.Errorf( - "invalid address '%v' length '%v'in %q - %q, expecting %d entries for datatype", - item.Address, len(item.Address), registerType, item.Name, requiredAddresses, - ) - } + // Check for the request size corresponding to the data-type + var requiredAddresses int + switch item.DataType { + case "INT8L", "INT8H", "UINT8L", "UINT8H", "UINT16", "INT16", "FLOAT16-IEEE": + requiredAddresses = 1 + case "UINT32", "INT32", "FLOAT32-IEEE": + requiredAddresses = 2 + case "UINT64", "INT64", "FLOAT64-IEEE": + requiredAddresses = 4 + } + if requiredAddresses > 0 && len(item.Address) != requiredAddresses { + return fmt.Errorf( + "invalid address '%v' length '%v'in %q - %q, expecting %d entries for datatype", + item.Address, len(item.Address), registerType, item.Name, requiredAddresses, + ) + } - // search duplicated - if len(item.Address) > len(removeDuplicates(item.Address)) { - return fmt.Errorf("duplicate address '%v' in %q - %q", item.Address, registerType, item.Name) - } - } else if len(item.Address) != 1 { - return fmt.Errorf("invalid address '%v' length '%v'in %q - %q", item.Address, len(item.Address), registerType, item.Name) + // search duplicated + if len(item.Address) > len(removeDuplicates(item.Address)) { + return fmt.Errorf("duplicate address '%v' in %q - %q", item.Address, registerType, item.Name) } + } else if len(item.Address) != 1 { + return fmt.Errorf("invalid address '%v' length '%v'in %q - %q", item.Address, len(item.Address), registerType, item.Name) } } return nil @@ -308,6 +317,8 @@ func (c *ConfigurationOriginal) normalizeInputDatatype(dataType string, words in return "FLOAT64", nil case "STRING": return "STRING", nil + case "BIT": + return "BIT", nil } return normalizeInputDatatype(dataType) } diff --git a/plugins/inputs/modbus/configuration_register_test.go b/plugins/inputs/modbus/configuration_register_test.go index 86e62b5ce186c..5114db81be0c3 100644 --- a/plugins/inputs/modbus/configuration_register_test.go +++ b/plugins/inputs/modbus/configuration_register_test.go @@ -215,12 +215,33 @@ func TestRegisterHoldingRegisters(t *testing.T) { 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}, @@ -888,6 +909,7 @@ func TestRegisterHoldingRegisters(t *testing.T) { DataType: hrt.dataType, Scale: hrt.scale, Address: hrt.address, + Bit: hrt.bit, }, } diff --git a/plugins/inputs/modbus/configuration_request.go b/plugins/inputs/modbus/configuration_request.go index 87645646a02cd..d266aba37c13c 100644 --- a/plugins/inputs/modbus/configuration_request.go +++ b/plugins/inputs/modbus/configuration_request.go @@ -23,6 +23,7 @@ type requestFieldDefinition struct { OutputType string `toml:"output"` Measurement string `toml:"measurement"` Omit bool `toml:"omit"` + Bit uint8 `toml:"bit"` } type requestDefinition struct { @@ -135,6 +136,9 @@ func (c *ConfigurationPerRequest) Check() error { if f.Length != 0 { return fmt.Errorf("length option cannot be used for type %q of field %q", f.InputType, f.Name) } + if f.Bit != 0 { + return fmt.Errorf("bit option cannot be used for type %q of field %q", f.InputType, f.Name) + } if f.OutputType == "STRING" { return fmt.Errorf("cannot output field %q as string", f.Name) } @@ -142,12 +146,22 @@ func (c *ConfigurationPerRequest) Check() error { if f.Length < 1 { return fmt.Errorf("missing length for string field %q", f.Name) } + if f.Bit != 0 { + return fmt.Errorf("bit option cannot be used for type %q of field %q", f.InputType, f.Name) + } if f.Scale != 0.0 { return fmt.Errorf("scale option cannot be used for string field %q", f.Name) } if f.OutputType != "" && f.OutputType != "STRING" { return fmt.Errorf("invalid output type %q for string field %q", f.OutputType, f.Name) } + case "BIT": + if f.Length != 0 { + return fmt.Errorf("length option cannot be used for type %q of field %q", f.InputType, f.Name) + } + if f.OutputType == "STRING" { + return fmt.Errorf("cannot output field %q as string", f.Name) + } default: return fmt.Errorf("unknown register data-type %q for field %q", f.InputType, f.Name) } @@ -361,7 +375,7 @@ func (c *ConfigurationPerRequest) newFieldFromDefinition(def requestFieldDefinit return field{}, err } - f.converter, err = determineConverter(inType, order, outType, def.Scale, c.workarounds.StringRegisterLocation) + f.converter, err = determineConverter(inType, order, outType, def.Scale, def.Bit, c.workarounds.StringRegisterLocation) if err != nil { return field{}, err } @@ -399,7 +413,7 @@ func (c *ConfigurationPerRequest) determineOutputDatatype(input string) (string, switch input { case "INT8L", "INT8H", "INT16", "INT32", "INT64": return "INT64", nil - case "UINT8L", "UINT8H", "UINT16", "UINT32", "UINT64": + case "BIT", "UINT8L", "UINT8H", "UINT16", "UINT32", "UINT64": return "UINT64", nil case "FLOAT16", "FLOAT32", "FLOAT64": return "FLOAT64", nil @@ -412,7 +426,7 @@ func (c *ConfigurationPerRequest) determineOutputDatatype(input string) (string, func (c *ConfigurationPerRequest) determineFieldLength(input string, length uint16) (uint16, error) { // Handle our special types switch input { - case "INT8L", "INT8H", "UINT8L", "UINT8H": + case "BIT", "INT8L", "INT8H", "UINT8L", "UINT8H": return 1, nil case "INT16", "UINT16", "FLOAT16": return 1, nil diff --git a/plugins/inputs/modbus/configuration_request_test.go b/plugins/inputs/modbus/configuration_request_test.go index 3f05a0238b5a5..5c69d203a4a30 100644 --- a/plugins/inputs/modbus/configuration_request_test.go +++ b/plugins/inputs/modbus/configuration_request_test.go @@ -458,6 +458,7 @@ func TestRequestTypesHoldingABCD(t *testing.T) { tests := []struct { name string address uint16 + bit uint8 length uint16 byteOrder string dataTypeIn string @@ -466,6 +467,22 @@ func TestRequestTypesHoldingABCD(t *testing.T) { write []byte read interface{} }{ + { + name: "register5_bit3", + address: 5, + dataTypeIn: "BIT", + bit: 3, + write: []byte{0x18, 0x0d}, + read: uint8(1), + }, + { + name: "register5_bit14", + address: 5, + dataTypeIn: "BIT", + bit: 14, + write: []byte{0x18, 0x0d}, + read: uint8(0), + }, { name: "register10_uint8L", address: 10, @@ -1035,6 +1052,7 @@ func TestRequestTypesHoldingABCD(t *testing.T) { Scale: hrt.scale, Address: hrt.address, Length: hrt.length, + Bit: hrt.bit, }, }, }, diff --git a/plugins/inputs/modbus/sample_metric.conf b/plugins/inputs/modbus/sample_metric.conf index 8f0de7aa6c989..09b23a8caad27 100644 --- a/plugins/inputs/modbus/sample_metric.conf +++ b/plugins/inputs/modbus/sample_metric.conf @@ -40,11 +40,13 @@ ## address - address of the register to query. For coil and discrete inputs this is the bit address. ## name - field name ## type *1 - type of the modbus field, can be + ## BIT (single bit of a register) ## INT8L, INT8H, UINT8L, UINT8H (low and high byte variants) ## INT16, UINT16, INT32, UINT32, INT64, UINT64 and ## FLOAT16, FLOAT32, FLOAT64 (IEEE 754 binary representation) ## STRING (byte-sequence converted to string) ## length *1 - (optional) number of registers, ONLY valid for STRING type + ## bit *1,2 - (optional) bit of the register, ONLY valid for BIT type ## scale *1,3 - (optional) factor to scale the variable with ## output *2,3 - (optional) type of resulting field, can be INT64, UINT64 or FLOAT64. Defaults to FLOAT64 if ## "scale" is provided and to the input "type" class otherwise (i.e. INT* -> INT64, etc). diff --git a/plugins/inputs/modbus/sample_register.conf b/plugins/inputs/modbus/sample_register.conf index 4da7aaffdc8a0..57c426d17f769 100644 --- a/plugins/inputs/modbus/sample_register.conf +++ b/plugins/inputs/modbus/sample_register.conf @@ -29,12 +29,14 @@ ## |---BA, DCBA - Little Endian ## |---BADC - Mid-Big Endian ## |---CDAB - Mid-Little Endian - ## data_type - INT8L, INT8H, UINT8L, UINT8H (low and high byte variants) + ## data_type - BIT (single bit of a register) + ## INT8L, INT8H, UINT8L, UINT8H (low and high byte variants) ## INT16, UINT16, INT32, UINT32, INT64, UINT64, ## FLOAT16-IEEE, FLOAT32-IEEE, FLOAT64-IEEE (IEEE 754 binary representation) ## FIXED, UFIXED (fixed-point representation on input) ## FLOAT32 is a deprecated alias for UFIXED for historic reasons, should be avoided ## STRING (byte-sequence converted to string) + ## bit - (optional) bit of the register, ONLY valid for BIT type ## scale - the final numeric variable representation ## address - variable address diff --git a/plugins/inputs/modbus/sample_request.conf b/plugins/inputs/modbus/sample_request.conf index 7ef998ce60b53..49d242110a37a 100644 --- a/plugins/inputs/modbus/sample_request.conf +++ b/plugins/inputs/modbus/sample_request.conf @@ -49,11 +49,13 @@ ## address - address of the register to query. For coil and discrete inputs this is the bit address. ## name *1 - field name ## type *1,2 - type of the modbus field, can be + ## BIT (single bit of a register) ## INT8L, INT8H, UINT8L, UINT8H (low and high byte variants) ## INT16, UINT16, INT32, UINT32, INT64, UINT64 and ## FLOAT16, FLOAT32, FLOAT64 (IEEE 754 binary representation) ## STRING (byte-sequence converted to string) ## length *1,2 - (optional) number of registers, ONLY valid for STRING type + ## bit *1,2 - (optional) bit of the register, ONLY valid for BIT type ## scale *1,2,4 - (optional) factor to scale the variable with ## output *1,3,4 - (optional) type of resulting field, can be INT64, UINT64 or FLOAT64. ## Defaults to FLOAT64 for numeric fields if "scale" is provided. diff --git a/plugins/inputs/modbus/type_conversions.go b/plugins/inputs/modbus/type_conversions.go index 5730284e78511..1e8dba367cb74 100644 --- a/plugins/inputs/modbus/type_conversions.go +++ b/plugins/inputs/modbus/type_conversions.go @@ -18,8 +18,9 @@ func determineUntypedConverter(outType string) (fieldConverterFunc, error) { return nil, fmt.Errorf("invalid output data-type: %s", outType) } -func determineConverter(inType, byteOrder, outType string, scale float64, strloc string) (fieldConverterFunc, error) { - if inType == "STRING" { +func determineConverter(inType, byteOrder, outType string, scale float64, bit uint8, strloc string) (fieldConverterFunc, error) { + switch inType { + case "STRING": switch strloc { case "", "both": return determineConverterString(byteOrder) @@ -28,6 +29,8 @@ func determineConverter(inType, byteOrder, outType string, scale float64, strloc case "upper": return determineConverterStringHigh(byteOrder) } + case "BIT": + return determineConverterBit(byteOrder, bit) } if scale != 0.0 { diff --git a/plugins/inputs/modbus/type_conversions_bit.go b/plugins/inputs/modbus/type_conversions_bit.go new file mode 100644 index 0000000000000..8ff47c7d7eec1 --- /dev/null +++ b/plugins/inputs/modbus/type_conversions_bit.go @@ -0,0 +1,14 @@ +package modbus + +func determineConverterBit(byteOrder string, bit uint8) (fieldConverterFunc, error) { + tohost, err := endiannessConverter16(byteOrder) + if err != nil { + return nil, err + } + + return func(b []byte) interface{} { + // Swap the bytes according to endianness + v := tohost(b) + return uint8(v >> bit & 0x01) + }, nil +}