From e5fa165d9149ba4232ce0279a506905283cfc70b Mon Sep 17 00:00:00 2001 From: Timur Date: Fri, 27 May 2022 08:39:16 +0200 Subject: [PATCH 1/9] filter out totally empty requests update readme lintin conflict from master --- plugins/inputs/modbus/README.md | 90 +++++++++++++++++++++------- plugins/inputs/modbus/modbus_test.go | 48 +++++++++++++++ plugins/inputs/modbus/request.go | 15 ++++- 3 files changed, 127 insertions(+), 26 deletions(-) diff --git a/plugins/inputs/modbus/README.md b/plugins/inputs/modbus/README.md index 203d049e63822..a9c4815f81610 100644 --- a/plugins/inputs/modbus/README.md +++ b/plugins/inputs/modbus/README.md @@ -207,12 +207,18 @@ Registers via Modbus TCP or Modbus RTU/ASCII. ## Notes You can debug Modbus connection issues by enabling `debug_connection`. To see those debug messages, Telegraf has to be started with debugging enabled (i.e. with the `--debug` option). Please be aware that connection tracing will produce a lot of messages and should __NOT__ be used in production environments. +You can debug Modbus connection issues by enabling `debug_connection`. To see those debug messages, Telegraf has to be started +with debugging enabled (i.e. with the `--debug` option). Please be aware that connection tracing will produce a lot of messages +and should __NOT__ be used in production environments. -Please use `pause_between_requests` with care. Ensure the total gather time, including the pause(s), does not exceed the configured collection interval. Note that pauses add up if multiple requests are sent! +Please use `pause_between_requests` with care. Ensure the total gather time, including the pause(s), does not exceed the configured +collection interval. Note that pauses add up if multiple requests are sent! ## Configuration styles -The modbus plugin supports multiple configuration styles that can be set using the `configuration_type` setting. The different styles are described below. Please note that styles cannot be mixed, i.e. only the settings belonging to the configured `configuration_type` are used for constructing _modbus_ requests and creation of metrics. +The modbus plugin supports multiple configuration styles that can be set using the `configuration_type` setting. The different +styles are described below. Please note that styles cannot be mixed, i.e. only the settings belonging to the configured +`configuration_type` are used for constructing _modbus_ requests and creation of metrics. Directly jump to the styles: @@ -268,15 +274,20 @@ with N decimal places'. ### `request` configuration style -This sytle can be used to specify the modbus requests directly. It enables specifying multiple `[[inputs.modbus.request]]` sections including multiple slave-devices. This way, _modbus_ gateway devices can be queried. Please note that _requests_ might be split for non-consecutive addresses. If you want to avoid this behavior please add _fields_ with the `omit` flag set filling the gaps between addresses. +This sytle can be used to specify the modbus requests directly. It enables specifying multiple +`[[inputs.modbus.request]]` sections including multiple slave-devices. This way, _modbus_ gateway devices +can be queried. Please note that _requests_ might be split for non-consecutive addresses. If you want to avoid +this behavior please add _fields_ with the `omit` flag set filling the gaps between addresses. #### Slave device -You can use the `slave_id` setting to specify the ID of the slave device to query. It should be specified for each request, otherwise it defaults to zero. Please note, only one `slave_id` can be specified per request. +You can use the `slave_id` setting to specify the ID of the slave device to query. It should be specified for each request, + otherwise it defaults to zero. Please note, only one `slave_id` can be specified per request. #### Byte order of the register -The `byte_order` setting specifies the byte and word-order of the registers. It can be set to `ABCD` for _big endian (Motorola)_ or `DCBA` for _little endian (Intel)_ format as well as `BADC` and `CDAB` for _big endian_ or _little endian_ with _byte swap_. +The `byte_order` setting specifies the byte and word-order of the registers. It can be set to `ABCD` for _big endian (Motorola)_ +or `DCBA` for _little endian (Intel)_ format as well as `BADC` and `CDAB` for _big endian_ or _little endian_ with _byte swap_. #### Register type @@ -284,7 +295,8 @@ The `register` setting specifies the modbus register-set to query and can be set #### Per-request measurement setting -You can specify the name of the measurement for the following field definitions using the `measurement` setting. If the setting is omitted `modbus` is used. Furthermore, the measurement value can be overridden by each field individually. +You can specify the name of the measurement for the following field definitions using the `measurement` setting. If the setting +is omitted `modbus` is used. Furthermore, the measurement value can be overridden by each field individually. #### Field definitions @@ -292,45 +304,63 @@ Each `request` can contain a list of fields to collect from the modbus device. ##### address -A field is identified by an `address` that reflects the modbus register address. You can usually find the address values for the different datapoints in the datasheet of your modbus device. This is a mandatory setting. +A field is identified by an `address` that reflects the modbus register address. You can usually find the address values for +the different datapoints in the datasheet of your modbus device. This is a mandatory setting. For _coil_ and _discrete input_ registers this setting specifies the __bit__ containing the value of the field. ##### name -Using the `name` setting you can specify the field-name in the metric as output by the plugin. This setting is ignored if the field's `omit` is set to `true` and can be omitted in this case. +Using the `name` setting you can specify the field-name in the metric as output by the plugin. This setting is ignored if the +field's `omit` is set to `true` and can be omitted in this case. __Please note:__ There cannot be multiple fields with the same `name` in one metric identified by `measurement`, `slave_id` and `register`. ##### register datatype -The `register` setting specifies the datatype of the modbus register and can be set to `INT16`, `UINT16`, `INT32`, `UINT32`, `INT64` or `UINT64` for integer types or `FLOAT32` and `FLOAT64` for IEEE 754 binary representations of floating point values. Usually the datatype of the register is listed in the datasheet of your modbus device in relation to the `address` described above. +The `register` setting specifies the datatype of the modbus register and can be set to `INT16`, `UINT16`, `INT32`, `UINT32`, +`INT64` or `UINT64` for integer types or `FLOAT32` and `FLOAT64` for IEEE 754 binary representations of floating point values. +Usually the datatype of the register is listed in the datasheet of your modbus device in relation to the `address` described above. - This setting is ignored if the field's `omit` is set to `true` or if the `register` type is a bit-type (`coil` or `discrete`) and can be omitted in these cases. + This setting is ignored if the field's `omit` is set to `true` or if the `register` type is a bit-type (`coil` or `discrete`) + and can be omitted in these cases. ##### scaling -You can use the `scale` setting to scale the register values, e.g. if the register contains a fix-point values in `UINT32` format with two decimal places for example. To convert the read register value to the actual value you can set the `scale=0.01`. The scale is used as a factor e.g. `field_value * scale`. +You can use the `scale` setting to scale the register values, e.g. if the register contains a fix-point values in `UINT32` format +with two decimal places for example. To convert the read register value to the actual value you can set the `scale=0.01`. The scale +is used as a factor e.g. `field_value * scale`. -This setting is ignored if the field's `omit` is set to `true` or if the `register` type is a bit-type (`coil` or `discrete`) and can be omitted in these cases. +This setting is ignored if the field's `omit` is set to `true` or if the `register` type is a bit-type (`coil` or `discrete`) and +can be omitted in these cases. __Please note:__ The resulting field-type will be set to `FLOAT64` if no output format is specified. ##### output datatype -Using the `output` setting you can explicitly specify the output field-datatype. The `output` type can be `INT64`, `UINT64` or `FLOAT64`. If not set explicitly, the output type is guessed as follows: If `scale` is set to a non-zero value, the output type is `FLOAT64`. Otherwise, the output type corresponds to the register datatype _class_, i.e. `INT*` will result in `INT64`, `UINT*` in `UINT64` and `FLOAT*` in `FLOAT64`. +Using the `output` setting you can explicitly specify the output field-datatype. The `output` type can be `INT64`, `UINT64` or +`FLOAT64`. If not set explicitly, the output type is guessed as follows: If `scale` is set to a non-zero value, the output type +is `FLOAT64`. Otherwise, the output type corresponds to the register datatype _class_, i.e. `INT*` will result in `INT64`, `UINT*` +in `UINT64` and `FLOAT*` in `FLOAT64`. -This setting is ignored if the field's `omit` is set to `true` or if the `register` type is a bit-type (`coil` or `discrete`) and can be omitted in these cases. For `coil` and `discrete` registers the field-value is output as zero or one in `UINT16` format. +This setting is ignored if the field's `omit` is set to `true` or if the `register` type is a bit-type (`coil` or `discrete`) and +can be omitted in these cases. For `coil` and `discrete` registers the field-value is output as zero or one in `UINT16` format. #### per-field measurement setting -The `measurement` setting can be used to override the measurement name on a per-field basis. This might be useful if you want to split the fields in one request to multiple measurements. If not specified, the value specified in the [`request` section](#per-request-measurement-setting) or, if also omitted, `modbus` is used. +The `measurement` setting can be used to override the measurement name on a per-field basis. This might be useful if you want +to split the fields in one request to multiple measurements. If not specified, the value specified in the [`request` section] +(#per-request-measurement-setting) or, if also omitted, `modbus` is used. This setting is ignored if the field's `omit` is set to `true` and can be omitted in this case. #### omitting a field -When specifying `omit=true`, the corresponding field will be ignored when collecting the metric but is taken into account when constructing the modbus requests. This way, you can fill "holes" in the addresses to construct consecutive address ranges resulting in a single request. Using a single modbus request can be beneficial as the values are all collected at the same point in time. +When specifying `omit=true`, the corresponding field will be ignored when collecting the metric but is taken into +account when constructing the modbus requests. This way, you can fill "holes" in the addresses to construct consecutive +address ranges resulting in a single request. Using a single modbus request can be beneficial as the values are all +collected at the same point in time. +Requests constituted of only omitted fields will be skipped. #### Tags definitions @@ -343,24 +373,38 @@ __Please note:__ These tags take precedence over predefined tags such as `name`, ### Strange data -Modbus documentation is often a mess. People confuse memory-address (starts at one) and register address (starts at zero) or are unsure about the word-order used. Furthermore, there are some non-standard implementations that also swap the bytes within the register word (16-bit). +Modbus documentation is often a mess. People confuse memory-address (starts at one) and register address (starts at zero) +or are unsure about the word-order used. Furthermore, there are some non-standard implementations that also swap the bytes +within the register word (16-bit). If you get an error or don't get the expected values from your device, you can try the following steps (assuming a 32-bit value). If you are using a serial device and get a `permission denied` error, check the permissions of your serial device and change them accordingly. -In case you get an `exception '2' (illegal data address)` error you might try to offset your `address` entries by minus one as it is very likely that there is confusion between memory and register addresses. +In case you get an `exception '2' (illegal data address)` error you might try to offset your `address` entries by minus one +as it is very likely that there is confusion between memory and register addresses. -If you see strange values, the `byte_order` might be wrong. You can either probe all combinations (`ABCD`, `CDBA`, `BADC` or `DCBA`) or set `byte_order="ABCD" data_type="UINT32"` and use the resulting value(s) in an online converter like [this](https://www.scadacore.com/tools/programming-calculators/online-hex-converter/). This especially makes sense if you don't want to mess with the device, deal with 64-bit values and/or don't know the `data_type` of your register (e.g. fix-point floating values vs. IEEE floating point). +If you see strange values, the `byte_order` might be wrong. You can either probe all combinations (`ABCD`, `CDBA`, `BADC` or +`DCBA`) or set `byte_order="ABCD" data_type="UINT32"` and use the resulting value(s) in an online converter like +[this](https://www.scadacore.com/tools/programming-calculators/online-hex-converter/). This especially makes sense if you +don't want to mess with the device, deal with 64-bit values and/or don't know the `data_type` of your register +(e.g. fix-point floating values vs. IEEE floating point). -If your data still looks corrupted, please post your configuration, error message and/or the output of `byte_order="ABCD" data_type="UINT32"` to one of the telegraf support channels (forum, slack or as an issue). -If nothing helps, please post your configuration, error message and/or the output of `byte_order="ABCD" data_type="UINT32"` to one of the telegraf support channels (forum, slack or as an issue). +If your data still looks corrupted, please post your configuration, error message and/or the output of +`byte_order="ABCD" data_type="UINT32"` to one of the telegraf support channels (forum, slack or as an issue). +If nothing helps, please post your configuration, error message and/or the output of `byte_order="ABCD" data_type="UINT32"` +to one of the telegraf support channels (forum, slack or as an issue). ### Workarounds -Some Modbus devices need special read characteristics when reading data and will fail otherwise. For example, some serial devices need a pause between register read requests. Others might only support a limited number of simultaneously connected devices, like serial devices or some ModbusTCP devices. In case you need to access those devices in parallel you might want to disconnect immediately after the plugin finishes reading. +Some Modbus devices need special read characteristics when reading data and will fail otherwise. For example, some serial +devices need a pause between register read requests. Others might only support a limited number of simultaneously connected +devices, like serial devices or some ModbusTCP devices. In case you need to access those devices in parallel you might want +to disconnect immediately after the plugin finishes reading. -To enable this plugin to also handle those "special" devices, there is the `workarounds` configuration option. In case your documentation states certain read requirements or you get read timeouts or other read errors, you might want to try one or more workaround options. +To enable this plugin to also handle those "special" devices, there is the `workarounds` configuration option. In case +your documentation states certain read requirements or you get read timeouts or other read errors, you might want to try +one or more workaround options. If you find that other/more workarounds are required for your device, please let us know. In case your device needs a workaround that is not yet implemented, please open an issue or submit a pull-request. diff --git a/plugins/inputs/modbus/modbus_test.go b/plugins/inputs/modbus/modbus_test.go index 5e3d045230c7c..d070bcf3bd5dd 100644 --- a/plugins/inputs/modbus/modbus_test.go +++ b/plugins/inputs/modbus/modbus_test.go @@ -1857,3 +1857,51 @@ func TestConfigurationPerRequestFail(t *testing.T) { }) } } + +func TestFilterOutEmptyRequests(t *testing.T) { + modbus := Modbus{ + Name: "Test", + Controller: "tcp://localhost:1502", + ConfigurationType: "request", + Log: testutil.Logger{}, + } + modbus.Requests = []requestDefinition{ + {SlaveID: 1, + ByteOrder: "ABCD", + RegisterType: "holding", + Fields: []requestFieldDefinition{ + { + Name: "holding-0", + Address: uint16(0), + InputType: "INT16", + Omit: true, + }, + { + Name: "holding-1", + Address: uint16(1), + InputType: "UINT16", + Omit: true, + }, + { + Name: "holding-2", + Address: uint16(2), + InputType: "INT64", + }, + }, + }, + } + for address := uint16(3); address < 2*maxQuantityHoldingRegisters; address++ { + newField := requestFieldDefinition{ + Name: "holding-0", + Address: uint16(address), + InputType: "INT16", + Omit: true, + } + modbus.Requests[0].Fields = append(modbus.Requests[0].Fields, newField) + } + require.NoError(t, modbus.Init()) + require.NotEmpty(t, modbus.requests) + require.NotNil(t, modbus.requests[1]) + require.Len(t, modbus.requests[1].holding, 1) + require.Equal(t, uint16(0), modbus.requests[1].holding[0].address) +} diff --git a/plugins/inputs/modbus/request.go b/plugins/inputs/modbus/request.go index 16e054b67b723..bc14cd3087987 100644 --- a/plugins/inputs/modbus/request.go +++ b/plugins/inputs/modbus/request.go @@ -13,10 +13,12 @@ func newRequest(f field, tags map[string]string) request { r := request{ address: f.address, length: f.length, - fields: []field{f}, + fields: []field{}, tags: map[string]string{}, } - + if !f.omit { + r.fields = append(r.fields, f) + } // Copy the tags for k, v := range tags { r.tags[k] = v @@ -63,6 +65,13 @@ func groupFieldsToRequests(fields []field, tags map[string]string, maxBatchSize current = newRequest(f, tags) } requests = append(requests, current) - + // Filter out completely empty requests + nonEmptyRequests := make([]request, 0, len(requests)) + for _, request := range requests { + if len(request.fields) != 0 { + nonEmptyRequests = append(nonEmptyRequests, request) + } + } + requests = nonEmptyRequests return requests } From e70b7aded2ea9f2f566ddbcfa70c429a452d8d16 Mon Sep 17 00:00:00 2001 From: Timur Date: Fri, 27 May 2022 10:20:25 +0200 Subject: [PATCH 2/9] remove duplicated lines in readme --- plugins/inputs/modbus/README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/inputs/modbus/README.md b/plugins/inputs/modbus/README.md index a9c4815f81610..e90bd914f7bbb 100644 --- a/plugins/inputs/modbus/README.md +++ b/plugins/inputs/modbus/README.md @@ -206,10 +206,9 @@ Registers via Modbus TCP or Modbus RTU/ASCII. ## Notes -You can debug Modbus connection issues by enabling `debug_connection`. To see those debug messages, Telegraf has to be started with debugging enabled (i.e. with the `--debug` option). Please be aware that connection tracing will produce a lot of messages and should __NOT__ be used in production environments. -You can debug Modbus connection issues by enabling `debug_connection`. To see those debug messages, Telegraf has to be started -with debugging enabled (i.e. with the `--debug` option). Please be aware that connection tracing will produce a lot of messages -and should __NOT__ be used in production environments. +You can debug Modbus connection issues by enabling `debug_connection`. To see those debug messages, +Telegraf has to be started with debugging enabled (i.e. with the `--debug` option). Please be aware +that connection tracing will produce a lot of messages and should __NOT__ be used in production environments. Please use `pause_between_requests` with care. Ensure the total gather time, including the pause(s), does not exceed the configured collection interval. Note that pauses add up if multiple requests are sent! From 226017623b406a12fd3a524ca4cdc616dc7fb61b Mon Sep 17 00:00:00 2001 From: Timur Date: Fri, 27 May 2022 11:05:47 +0200 Subject: [PATCH 3/9] remove unnecessary conversion --- plugins/inputs/modbus/modbus_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/inputs/modbus/modbus_test.go b/plugins/inputs/modbus/modbus_test.go index d070bcf3bd5dd..cab1f3a9839df 100644 --- a/plugins/inputs/modbus/modbus_test.go +++ b/plugins/inputs/modbus/modbus_test.go @@ -1893,7 +1893,7 @@ func TestFilterOutEmptyRequests(t *testing.T) { for address := uint16(3); address < 2*maxQuantityHoldingRegisters; address++ { newField := requestFieldDefinition{ Name: "holding-0", - Address: uint16(address), + Address: address, InputType: "INT16", Omit: true, } From 5f6e61ea12493be0b0929136be0f279c78b3b2ba Mon Sep 17 00:00:00 2001 From: Timur Date: Fri, 27 May 2022 11:20:02 +0200 Subject: [PATCH 4/9] limit changes to fixing omit first --- plugins/inputs/modbus/modbus_test.go | 12 +----------- plugins/inputs/modbus/request.go | 8 ++------ 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/plugins/inputs/modbus/modbus_test.go b/plugins/inputs/modbus/modbus_test.go index cab1f3a9839df..2e3c451b75d44 100644 --- a/plugins/inputs/modbus/modbus_test.go +++ b/plugins/inputs/modbus/modbus_test.go @@ -1858,7 +1858,7 @@ func TestConfigurationPerRequestFail(t *testing.T) { } } -func TestFilterOutEmptyRequests(t *testing.T) { +func TestRequestsStartingWithOmits(t *testing.T) { modbus := Modbus{ Name: "Test", Controller: "tcp://localhost:1502", @@ -1890,18 +1890,8 @@ func TestFilterOutEmptyRequests(t *testing.T) { }, }, } - for address := uint16(3); address < 2*maxQuantityHoldingRegisters; address++ { - newField := requestFieldDefinition{ - Name: "holding-0", - Address: address, - InputType: "INT16", - Omit: true, - } - modbus.Requests[0].Fields = append(modbus.Requests[0].Fields, newField) - } require.NoError(t, modbus.Init()) require.NotEmpty(t, modbus.requests) require.NotNil(t, modbus.requests[1]) - require.Len(t, modbus.requests[1].holding, 1) require.Equal(t, uint16(0), modbus.requests[1].holding[0].address) } diff --git a/plugins/inputs/modbus/request.go b/plugins/inputs/modbus/request.go index bc14cd3087987..72e9f616b74a2 100644 --- a/plugins/inputs/modbus/request.go +++ b/plugins/inputs/modbus/request.go @@ -13,12 +13,9 @@ func newRequest(f field, tags map[string]string) request { r := request{ address: f.address, length: f.length, - fields: []field{}, + fields: []field{f}, tags: map[string]string{}, } - if !f.omit { - r.fields = append(r.fields, f) - } // Copy the tags for k, v := range tags { r.tags[k] = v @@ -72,6 +69,5 @@ func groupFieldsToRequests(fields []field, tags map[string]string, maxBatchSize nonEmptyRequests = append(nonEmptyRequests, request) } } - requests = nonEmptyRequests - return requests + return nonEmptyRequests } From 80a6c291dfdfe532684e8f969ee07a92d13f87c6 Mon Sep 17 00:00:00 2001 From: Timur Date: Fri, 27 May 2022 11:47:50 +0200 Subject: [PATCH 5/9] fix confusion between PRs --- plugins/inputs/modbus/request.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/inputs/modbus/request.go b/plugins/inputs/modbus/request.go index 72e9f616b74a2..b3d3eabfe7dc0 100644 --- a/plugins/inputs/modbus/request.go +++ b/plugins/inputs/modbus/request.go @@ -13,9 +13,12 @@ func newRequest(f field, tags map[string]string) request { r := request{ address: f.address, length: f.length, - fields: []field{f}, + fields: []field{}, tags: map[string]string{}, } + if !f.omit { + r.fields = append(r.fields, f) + } // Copy the tags for k, v := range tags { r.tags[k] = v From 5e3f4a823c6dc2884bc18be4cbb913dd1896b1e7 Mon Sep 17 00:00:00 2001 From: Timur Date: Fri, 27 May 2022 11:52:18 +0200 Subject: [PATCH 6/9] revert readme file since this PR only fixes a bug --- plugins/inputs/modbus/README.md | 91 +++++++++------------------------ 1 file changed, 24 insertions(+), 67 deletions(-) diff --git a/plugins/inputs/modbus/README.md b/plugins/inputs/modbus/README.md index e90bd914f7bbb..203d049e63822 100644 --- a/plugins/inputs/modbus/README.md +++ b/plugins/inputs/modbus/README.md @@ -206,18 +206,13 @@ Registers via Modbus TCP or Modbus RTU/ASCII. ## Notes -You can debug Modbus connection issues by enabling `debug_connection`. To see those debug messages, -Telegraf has to be started with debugging enabled (i.e. with the `--debug` option). Please be aware -that connection tracing will produce a lot of messages and should __NOT__ be used in production environments. +You can debug Modbus connection issues by enabling `debug_connection`. To see those debug messages, Telegraf has to be started with debugging enabled (i.e. with the `--debug` option). Please be aware that connection tracing will produce a lot of messages and should __NOT__ be used in production environments. -Please use `pause_between_requests` with care. Ensure the total gather time, including the pause(s), does not exceed the configured -collection interval. Note that pauses add up if multiple requests are sent! +Please use `pause_between_requests` with care. Ensure the total gather time, including the pause(s), does not exceed the configured collection interval. Note that pauses add up if multiple requests are sent! ## Configuration styles -The modbus plugin supports multiple configuration styles that can be set using the `configuration_type` setting. The different -styles are described below. Please note that styles cannot be mixed, i.e. only the settings belonging to the configured -`configuration_type` are used for constructing _modbus_ requests and creation of metrics. +The modbus plugin supports multiple configuration styles that can be set using the `configuration_type` setting. The different styles are described below. Please note that styles cannot be mixed, i.e. only the settings belonging to the configured `configuration_type` are used for constructing _modbus_ requests and creation of metrics. Directly jump to the styles: @@ -273,20 +268,15 @@ with N decimal places'. ### `request` configuration style -This sytle can be used to specify the modbus requests directly. It enables specifying multiple -`[[inputs.modbus.request]]` sections including multiple slave-devices. This way, _modbus_ gateway devices -can be queried. Please note that _requests_ might be split for non-consecutive addresses. If you want to avoid -this behavior please add _fields_ with the `omit` flag set filling the gaps between addresses. +This sytle can be used to specify the modbus requests directly. It enables specifying multiple `[[inputs.modbus.request]]` sections including multiple slave-devices. This way, _modbus_ gateway devices can be queried. Please note that _requests_ might be split for non-consecutive addresses. If you want to avoid this behavior please add _fields_ with the `omit` flag set filling the gaps between addresses. #### Slave device -You can use the `slave_id` setting to specify the ID of the slave device to query. It should be specified for each request, - otherwise it defaults to zero. Please note, only one `slave_id` can be specified per request. +You can use the `slave_id` setting to specify the ID of the slave device to query. It should be specified for each request, otherwise it defaults to zero. Please note, only one `slave_id` can be specified per request. #### Byte order of the register -The `byte_order` setting specifies the byte and word-order of the registers. It can be set to `ABCD` for _big endian (Motorola)_ -or `DCBA` for _little endian (Intel)_ format as well as `BADC` and `CDAB` for _big endian_ or _little endian_ with _byte swap_. +The `byte_order` setting specifies the byte and word-order of the registers. It can be set to `ABCD` for _big endian (Motorola)_ or `DCBA` for _little endian (Intel)_ format as well as `BADC` and `CDAB` for _big endian_ or _little endian_ with _byte swap_. #### Register type @@ -294,8 +284,7 @@ The `register` setting specifies the modbus register-set to query and can be set #### Per-request measurement setting -You can specify the name of the measurement for the following field definitions using the `measurement` setting. If the setting -is omitted `modbus` is used. Furthermore, the measurement value can be overridden by each field individually. +You can specify the name of the measurement for the following field definitions using the `measurement` setting. If the setting is omitted `modbus` is used. Furthermore, the measurement value can be overridden by each field individually. #### Field definitions @@ -303,63 +292,45 @@ Each `request` can contain a list of fields to collect from the modbus device. ##### address -A field is identified by an `address` that reflects the modbus register address. You can usually find the address values for -the different datapoints in the datasheet of your modbus device. This is a mandatory setting. +A field is identified by an `address` that reflects the modbus register address. You can usually find the address values for the different datapoints in the datasheet of your modbus device. This is a mandatory setting. For _coil_ and _discrete input_ registers this setting specifies the __bit__ containing the value of the field. ##### name -Using the `name` setting you can specify the field-name in the metric as output by the plugin. This setting is ignored if the -field's `omit` is set to `true` and can be omitted in this case. +Using the `name` setting you can specify the field-name in the metric as output by the plugin. This setting is ignored if the field's `omit` is set to `true` and can be omitted in this case. __Please note:__ There cannot be multiple fields with the same `name` in one metric identified by `measurement`, `slave_id` and `register`. ##### register datatype -The `register` setting specifies the datatype of the modbus register and can be set to `INT16`, `UINT16`, `INT32`, `UINT32`, -`INT64` or `UINT64` for integer types or `FLOAT32` and `FLOAT64` for IEEE 754 binary representations of floating point values. -Usually the datatype of the register is listed in the datasheet of your modbus device in relation to the `address` described above. +The `register` setting specifies the datatype of the modbus register and can be set to `INT16`, `UINT16`, `INT32`, `UINT32`, `INT64` or `UINT64` for integer types or `FLOAT32` and `FLOAT64` for IEEE 754 binary representations of floating point values. Usually the datatype of the register is listed in the datasheet of your modbus device in relation to the `address` described above. - This setting is ignored if the field's `omit` is set to `true` or if the `register` type is a bit-type (`coil` or `discrete`) - and can be omitted in these cases. + This setting is ignored if the field's `omit` is set to `true` or if the `register` type is a bit-type (`coil` or `discrete`) and can be omitted in these cases. ##### scaling -You can use the `scale` setting to scale the register values, e.g. if the register contains a fix-point values in `UINT32` format -with two decimal places for example. To convert the read register value to the actual value you can set the `scale=0.01`. The scale -is used as a factor e.g. `field_value * scale`. +You can use the `scale` setting to scale the register values, e.g. if the register contains a fix-point values in `UINT32` format with two decimal places for example. To convert the read register value to the actual value you can set the `scale=0.01`. The scale is used as a factor e.g. `field_value * scale`. -This setting is ignored if the field's `omit` is set to `true` or if the `register` type is a bit-type (`coil` or `discrete`) and -can be omitted in these cases. +This setting is ignored if the field's `omit` is set to `true` or if the `register` type is a bit-type (`coil` or `discrete`) and can be omitted in these cases. __Please note:__ The resulting field-type will be set to `FLOAT64` if no output format is specified. ##### output datatype -Using the `output` setting you can explicitly specify the output field-datatype. The `output` type can be `INT64`, `UINT64` or -`FLOAT64`. If not set explicitly, the output type is guessed as follows: If `scale` is set to a non-zero value, the output type -is `FLOAT64`. Otherwise, the output type corresponds to the register datatype _class_, i.e. `INT*` will result in `INT64`, `UINT*` -in `UINT64` and `FLOAT*` in `FLOAT64`. +Using the `output` setting you can explicitly specify the output field-datatype. The `output` type can be `INT64`, `UINT64` or `FLOAT64`. If not set explicitly, the output type is guessed as follows: If `scale` is set to a non-zero value, the output type is `FLOAT64`. Otherwise, the output type corresponds to the register datatype _class_, i.e. `INT*` will result in `INT64`, `UINT*` in `UINT64` and `FLOAT*` in `FLOAT64`. -This setting is ignored if the field's `omit` is set to `true` or if the `register` type is a bit-type (`coil` or `discrete`) and -can be omitted in these cases. For `coil` and `discrete` registers the field-value is output as zero or one in `UINT16` format. +This setting is ignored if the field's `omit` is set to `true` or if the `register` type is a bit-type (`coil` or `discrete`) and can be omitted in these cases. For `coil` and `discrete` registers the field-value is output as zero or one in `UINT16` format. #### per-field measurement setting -The `measurement` setting can be used to override the measurement name on a per-field basis. This might be useful if you want -to split the fields in one request to multiple measurements. If not specified, the value specified in the [`request` section] -(#per-request-measurement-setting) or, if also omitted, `modbus` is used. +The `measurement` setting can be used to override the measurement name on a per-field basis. This might be useful if you want to split the fields in one request to multiple measurements. If not specified, the value specified in the [`request` section](#per-request-measurement-setting) or, if also omitted, `modbus` is used. This setting is ignored if the field's `omit` is set to `true` and can be omitted in this case. #### omitting a field -When specifying `omit=true`, the corresponding field will be ignored when collecting the metric but is taken into -account when constructing the modbus requests. This way, you can fill "holes" in the addresses to construct consecutive -address ranges resulting in a single request. Using a single modbus request can be beneficial as the values are all -collected at the same point in time. -Requests constituted of only omitted fields will be skipped. +When specifying `omit=true`, the corresponding field will be ignored when collecting the metric but is taken into account when constructing the modbus requests. This way, you can fill "holes" in the addresses to construct consecutive address ranges resulting in a single request. Using a single modbus request can be beneficial as the values are all collected at the same point in time. #### Tags definitions @@ -372,38 +343,24 @@ __Please note:__ These tags take precedence over predefined tags such as `name`, ### Strange data -Modbus documentation is often a mess. People confuse memory-address (starts at one) and register address (starts at zero) -or are unsure about the word-order used. Furthermore, there are some non-standard implementations that also swap the bytes -within the register word (16-bit). +Modbus documentation is often a mess. People confuse memory-address (starts at one) and register address (starts at zero) or are unsure about the word-order used. Furthermore, there are some non-standard implementations that also swap the bytes within the register word (16-bit). If you get an error or don't get the expected values from your device, you can try the following steps (assuming a 32-bit value). If you are using a serial device and get a `permission denied` error, check the permissions of your serial device and change them accordingly. -In case you get an `exception '2' (illegal data address)` error you might try to offset your `address` entries by minus one -as it is very likely that there is confusion between memory and register addresses. +In case you get an `exception '2' (illegal data address)` error you might try to offset your `address` entries by minus one as it is very likely that there is confusion between memory and register addresses. -If you see strange values, the `byte_order` might be wrong. You can either probe all combinations (`ABCD`, `CDBA`, `BADC` or -`DCBA`) or set `byte_order="ABCD" data_type="UINT32"` and use the resulting value(s) in an online converter like -[this](https://www.scadacore.com/tools/programming-calculators/online-hex-converter/). This especially makes sense if you -don't want to mess with the device, deal with 64-bit values and/or don't know the `data_type` of your register -(e.g. fix-point floating values vs. IEEE floating point). +If you see strange values, the `byte_order` might be wrong. You can either probe all combinations (`ABCD`, `CDBA`, `BADC` or `DCBA`) or set `byte_order="ABCD" data_type="UINT32"` and use the resulting value(s) in an online converter like [this](https://www.scadacore.com/tools/programming-calculators/online-hex-converter/). This especially makes sense if you don't want to mess with the device, deal with 64-bit values and/or don't know the `data_type` of your register (e.g. fix-point floating values vs. IEEE floating point). -If your data still looks corrupted, please post your configuration, error message and/or the output of -`byte_order="ABCD" data_type="UINT32"` to one of the telegraf support channels (forum, slack or as an issue). -If nothing helps, please post your configuration, error message and/or the output of `byte_order="ABCD" data_type="UINT32"` -to one of the telegraf support channels (forum, slack or as an issue). +If your data still looks corrupted, please post your configuration, error message and/or the output of `byte_order="ABCD" data_type="UINT32"` to one of the telegraf support channels (forum, slack or as an issue). +If nothing helps, please post your configuration, error message and/or the output of `byte_order="ABCD" data_type="UINT32"` to one of the telegraf support channels (forum, slack or as an issue). ### Workarounds -Some Modbus devices need special read characteristics when reading data and will fail otherwise. For example, some serial -devices need a pause between register read requests. Others might only support a limited number of simultaneously connected -devices, like serial devices or some ModbusTCP devices. In case you need to access those devices in parallel you might want -to disconnect immediately after the plugin finishes reading. +Some Modbus devices need special read characteristics when reading data and will fail otherwise. For example, some serial devices need a pause between register read requests. Others might only support a limited number of simultaneously connected devices, like serial devices or some ModbusTCP devices. In case you need to access those devices in parallel you might want to disconnect immediately after the plugin finishes reading. -To enable this plugin to also handle those "special" devices, there is the `workarounds` configuration option. In case -your documentation states certain read requirements or you get read timeouts or other read errors, you might want to try -one or more workaround options. +To enable this plugin to also handle those "special" devices, there is the `workarounds` configuration option. In case your documentation states certain read requirements or you get read timeouts or other read errors, you might want to try one or more workaround options. If you find that other/more workarounds are required for your device, please let us know. In case your device needs a workaround that is not yet implemented, please open an issue or submit a pull-request. From 4327519bb153136d27a276a11a45552764c5c1b8 Mon Sep 17 00:00:00 2001 From: Timur Date: Fri, 27 May 2022 12:34:25 +0200 Subject: [PATCH 7/9] actually test that assignment of request starting with omit does not fail --- plugins/inputs/modbus/modbus_test.go | 32 +++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/plugins/inputs/modbus/modbus_test.go b/plugins/inputs/modbus/modbus_test.go index 2e3c451b75d44..0d9bceca53691 100644 --- a/plugins/inputs/modbus/modbus_test.go +++ b/plugins/inputs/modbus/modbus_test.go @@ -1885,7 +1885,7 @@ func TestRequestsStartingWithOmits(t *testing.T) { { Name: "holding-2", Address: uint16(2), - InputType: "INT64", + InputType: "INT16", }, }, }, @@ -1894,4 +1894,34 @@ func TestRequestsStartingWithOmits(t *testing.T) { require.NotEmpty(t, modbus.requests) require.NotNil(t, modbus.requests[1]) require.Equal(t, uint16(0), modbus.requests[1].holding[0].address) + + 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) + _, err := client.WriteMultipleRegisters(uint16(0), 3, []byte{0x00, 0x01, 0x00, 0x02, 0x00, 0x03}) + require.NoError(t, err) + + expected := []telegraf.Metric{ + testutil.MustMetric( + "modbus", + map[string]string{ + "type": cHoldingRegisters, + "slave_id": strconv.Itoa(int(modbus.Requests[0].SlaveID)), + "name": modbus.Name, + }, + map[string]interface{}{"holding-2": int16(3)}, + time.Unix(0, 0), + ), + } + + var acc testutil.Accumulator + require.NoError(t, modbus.Gather(&acc)) + acc.Wait(len(expected)) + testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime()) + } From bc3df028cba4ca92a7d219147fb7f0cdb09fc87a Mon Sep 17 00:00:00 2001 From: Timur Date: Fri, 27 May 2022 12:41:49 +0200 Subject: [PATCH 8/9] lint --- plugins/inputs/modbus/modbus_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/inputs/modbus/modbus_test.go b/plugins/inputs/modbus/modbus_test.go index 0d9bceca53691..5a7a7570d7b7d 100644 --- a/plugins/inputs/modbus/modbus_test.go +++ b/plugins/inputs/modbus/modbus_test.go @@ -1923,5 +1923,4 @@ func TestRequestsStartingWithOmits(t *testing.T) { require.NoError(t, modbus.Gather(&acc)) acc.Wait(len(expected)) testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime()) - } From c789c0c902dbcdcd3fe2d5c9a8349706270eb80e Mon Sep 17 00:00:00 2001 From: Timur Date: Tue, 31 May 2022 05:17:50 +0200 Subject: [PATCH 9/9] keep empty requests --- plugins/inputs/modbus/request.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/plugins/inputs/modbus/request.go b/plugins/inputs/modbus/request.go index b3d3eabfe7dc0..b3c5b62dc76ed 100644 --- a/plugins/inputs/modbus/request.go +++ b/plugins/inputs/modbus/request.go @@ -65,12 +65,5 @@ func groupFieldsToRequests(fields []field, tags map[string]string, maxBatchSize current = newRequest(f, tags) } requests = append(requests, current) - // Filter out completely empty requests - nonEmptyRequests := make([]request, 0, len(requests)) - for _, request := range requests { - if len(request.fields) != 0 { - nonEmptyRequests = append(nonEmptyRequests, request) - } - } - return nonEmptyRequests + return requests }