feat: Modbus connection settings (serial) (#9256)
This commit is contained in:
parent
a7582fb893
commit
cf605b5d9a
|
|
@ -42,6 +42,10 @@ Registers via Modbus TCP or Modbus RTU/ASCII.
|
||||||
## For Serial you can choose between "RTU" and "ASCII"
|
## For Serial you can choose between "RTU" and "ASCII"
|
||||||
# transmission_mode = "RTU"
|
# transmission_mode = "RTU"
|
||||||
|
|
||||||
|
## Trace the connection to the modbus device as debug messages
|
||||||
|
## Note: You have to enable telegraf's debug mode to see those messages!
|
||||||
|
# debug_connection = false
|
||||||
|
|
||||||
## Measurements
|
## Measurements
|
||||||
##
|
##
|
||||||
|
|
||||||
|
|
@ -88,8 +92,22 @@ Registers via Modbus TCP or Modbus RTU/ASCII.
|
||||||
{ name = "tank_ph", byte_order = "AB", data_type = "INT16", scale=1.0, address = [1]},
|
{ name = "tank_ph", byte_order = "AB", data_type = "INT16", scale=1.0, address = [1]},
|
||||||
{ name = "pump1_speed", byte_order = "ABCD", data_type = "INT32", scale=1.0, address = [3,4]},
|
{ name = "pump1_speed", byte_order = "ABCD", data_type = "INT32", scale=1.0, address = [3,4]},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
## Enable workarounds required by some devices to work correctly
|
||||||
|
# [inputs.modbus.workarounds]
|
||||||
|
## Pause between read requests sent to the device. This might be necessary for (slow) serial devices.
|
||||||
|
# pause_between_requests = "0ms"
|
||||||
|
## Close the connection after every gather cycle. Usually the plugin closes the connection after a certain
|
||||||
|
## idle-timeout, however, if you query a device with limited simultaneous connectivity (e.g. serial devices)
|
||||||
|
## from multiple instances you might want to only stay connected during gather and disconnect afterwards.
|
||||||
|
# close_connection_after_gather = false
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 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 `--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. Especially make sure that 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!
|
||||||
|
|
||||||
### Metrics
|
### Metrics
|
||||||
|
|
||||||
Metric are custom and configured using the `discrete_inputs`, `coils`,
|
Metric are custom and configured using the `discrete_inputs`, `coils`,
|
||||||
|
|
@ -131,6 +149,8 @@ with N decimal places'.
|
||||||
from unsigned values).
|
from unsigned values).
|
||||||
|
|
||||||
### Trouble shooting
|
### Trouble shooting
|
||||||
|
|
||||||
|
#### Strange data
|
||||||
Modbus documentations are often a mess. People confuse memory-address (starts at one) and register address (starts at zero) or stay unclear about the used word-order. Furthermore, there are some non-standard implementations that also
|
Modbus documentations are often a mess. People confuse memory-address (starts at one) and register address (starts at zero) or stay unclear about the used word-order. Furthermore, there are some non-standard implementations that also
|
||||||
swap the bytes within the register word (16-bit).
|
swap the bytes within the register word (16-bit).
|
||||||
|
|
||||||
|
|
@ -142,7 +162,15 @@ In case you get an `exception '2' (illegal data address)` error you might try to
|
||||||
|
|
||||||
In case you see strange values, the `byte_order` might be off. You can either probe all combinations (`ABCD`, `CDBA`, `BADC` or `DCBA`) or you 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 makes especially 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).
|
In case you see strange values, the `byte_order` might be off. You can either probe all combinations (`ABCD`, `CDBA`, `BADC` or `DCBA`) or you 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 makes especially 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 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 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 issue).
|
||||||
|
|
||||||
|
#### Workarounds
|
||||||
|
Some Modbus devices need special read characteristics when reading data and will fail otherwise. For example, there are certain serial devices that need a certain pause between register read requests. Others might only offer 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 finished reading.
|
||||||
|
|
||||||
|
To allow this plugin to also handle those "special" devices there is the `workarounds` configuration options. 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.
|
||||||
|
|
||||||
### Example Output
|
### Example Output
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,19 +15,26 @@ import (
|
||||||
"github.com/influxdata/telegraf/plugins/inputs"
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ModbusWorkarounds struct {
|
||||||
|
PollPause config.Duration `toml:"pause_between_requests"`
|
||||||
|
CloseAfterGather bool `toml:"close_connection_after_gather"`
|
||||||
|
}
|
||||||
|
|
||||||
// Modbus holds all data relevant to the plugin
|
// Modbus holds all data relevant to the plugin
|
||||||
type Modbus struct {
|
type Modbus struct {
|
||||||
Name string `toml:"name"`
|
Name string `toml:"name"`
|
||||||
Controller string `toml:"controller"`
|
Controller string `toml:"controller"`
|
||||||
TransmissionMode string `toml:"transmission_mode"`
|
TransmissionMode string `toml:"transmission_mode"`
|
||||||
BaudRate int `toml:"baud_rate"`
|
BaudRate int `toml:"baud_rate"`
|
||||||
DataBits int `toml:"data_bits"`
|
DataBits int `toml:"data_bits"`
|
||||||
Parity string `toml:"parity"`
|
Parity string `toml:"parity"`
|
||||||
StopBits int `toml:"stop_bits"`
|
StopBits int `toml:"stop_bits"`
|
||||||
Timeout config.Duration `toml:"timeout"`
|
Timeout config.Duration `toml:"timeout"`
|
||||||
Retries int `toml:"busy_retries"`
|
Retries int `toml:"busy_retries"`
|
||||||
RetriesWaitTime config.Duration `toml:"busy_retries_wait"`
|
RetriesWaitTime config.Duration `toml:"busy_retries_wait"`
|
||||||
Log telegraf.Logger `toml:"-"`
|
DebugConnection bool `toml:"debug_connection"`
|
||||||
|
Workarounds ModbusWorkarounds `toml:"workarounds"`
|
||||||
|
Log telegraf.Logger `toml:"-"`
|
||||||
// Register configuration
|
// Register configuration
|
||||||
ConfigurationOriginal
|
ConfigurationOriginal
|
||||||
// Connection handling
|
// Connection handling
|
||||||
|
|
@ -95,6 +102,11 @@ const sampleConfig = `
|
||||||
# data_bits = 8
|
# data_bits = 8
|
||||||
# parity = "N"
|
# parity = "N"
|
||||||
# stop_bits = 1
|
# stop_bits = 1
|
||||||
|
# transmission_mode = "RTU"
|
||||||
|
|
||||||
|
## Trace the connection to the modbus device as debug messages
|
||||||
|
## Note: You have to enable telegraf's debug mode to see those messages!
|
||||||
|
# debug_connection = false
|
||||||
|
|
||||||
## For Modbus over TCP you can choose between "TCP", "RTUoverTCP" and "ASCIIoverTCP"
|
## For Modbus over TCP you can choose between "TCP", "RTUoverTCP" and "ASCIIoverTCP"
|
||||||
## default behaviour is "TCP" if the controller is TCP
|
## default behaviour is "TCP" if the controller is TCP
|
||||||
|
|
@ -148,6 +160,15 @@ const sampleConfig = `
|
||||||
{ name = "tank_ph", byte_order = "AB", data_type = "INT16", scale=1.0, address = [1]},
|
{ name = "tank_ph", byte_order = "AB", data_type = "INT16", scale=1.0, address = [1]},
|
||||||
{ name = "pump1_speed", byte_order = "ABCD", data_type = "INT32", scale=1.0, address = [3,4]},
|
{ name = "pump1_speed", byte_order = "ABCD", data_type = "INT32", scale=1.0, address = [3,4]},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
## Enable workarounds required by some devices to work correctly
|
||||||
|
# [inputs.modbus.workarounds]
|
||||||
|
## Pause between read requests sent to the device. This might be necessary for (slow) serial devices.
|
||||||
|
# pause_between_requests = "0ms"
|
||||||
|
## Close the connection after every gather cycle. Usually the plugin closes the connection after a certain
|
||||||
|
## idle-timeout, however, if you query a device with limited simultaneous connectivity (e.g. serial devices)
|
||||||
|
## from multiple instances you might want to only stay connected during gather and disconnect afterwards.
|
||||||
|
# close_connection_after_gather = false
|
||||||
`
|
`
|
||||||
|
|
||||||
// SampleConfig returns a basic configuration for the plugin
|
// SampleConfig returns a basic configuration for the plugin
|
||||||
|
|
@ -234,6 +255,11 @@ func (m *Modbus) Gather(acc telegraf.Accumulator) error {
|
||||||
m.collectFields(acc, timestamp, tags, requests.input)
|
m.collectFields(acc, timestamp, tags, requests.input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Disconnect after read if configured
|
||||||
|
if m.Workarounds.CloseAfterGather {
|
||||||
|
return m.disconnect()
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -253,14 +279,23 @@ func (m *Modbus) initClient() error {
|
||||||
case "RTUoverTCP":
|
case "RTUoverTCP":
|
||||||
handler := mb.NewRTUOverTCPClientHandler(host + ":" + port)
|
handler := mb.NewRTUOverTCPClientHandler(host + ":" + port)
|
||||||
handler.Timeout = time.Duration(m.Timeout)
|
handler.Timeout = time.Duration(m.Timeout)
|
||||||
|
if m.DebugConnection {
|
||||||
|
handler.Logger = m
|
||||||
|
}
|
||||||
m.handler = handler
|
m.handler = handler
|
||||||
case "ASCIIoverTCP":
|
case "ASCIIoverTCP":
|
||||||
handler := mb.NewASCIIOverTCPClientHandler(host + ":" + port)
|
handler := mb.NewASCIIOverTCPClientHandler(host + ":" + port)
|
||||||
handler.Timeout = time.Duration(m.Timeout)
|
handler.Timeout = time.Duration(m.Timeout)
|
||||||
|
if m.DebugConnection {
|
||||||
|
handler.Logger = m
|
||||||
|
}
|
||||||
m.handler = handler
|
m.handler = handler
|
||||||
default:
|
default:
|
||||||
handler := mb.NewTCPClientHandler(host + ":" + port)
|
handler := mb.NewTCPClientHandler(host + ":" + port)
|
||||||
handler.Timeout = time.Duration(m.Timeout)
|
handler.Timeout = time.Duration(m.Timeout)
|
||||||
|
if m.DebugConnection {
|
||||||
|
handler.Logger = m
|
||||||
|
}
|
||||||
m.handler = handler
|
m.handler = handler
|
||||||
}
|
}
|
||||||
case "file":
|
case "file":
|
||||||
|
|
@ -272,6 +307,9 @@ func (m *Modbus) initClient() error {
|
||||||
handler.DataBits = m.DataBits
|
handler.DataBits = m.DataBits
|
||||||
handler.Parity = m.Parity
|
handler.Parity = m.Parity
|
||||||
handler.StopBits = m.StopBits
|
handler.StopBits = m.StopBits
|
||||||
|
if m.DebugConnection {
|
||||||
|
handler.Logger = m
|
||||||
|
}
|
||||||
m.handler = handler
|
m.handler = handler
|
||||||
case "ASCII":
|
case "ASCII":
|
||||||
handler := mb.NewASCIIClientHandler(u.Path)
|
handler := mb.NewASCIIClientHandler(u.Path)
|
||||||
|
|
@ -280,6 +318,9 @@ func (m *Modbus) initClient() error {
|
||||||
handler.DataBits = m.DataBits
|
handler.DataBits = m.DataBits
|
||||||
handler.Parity = m.Parity
|
handler.Parity = m.Parity
|
||||||
handler.StopBits = m.StopBits
|
handler.StopBits = m.StopBits
|
||||||
|
if m.DebugConnection {
|
||||||
|
handler.Logger = m
|
||||||
|
}
|
||||||
m.handler = handler
|
m.handler = handler
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid protocol '%s' - '%s' ", u.Scheme, m.TransmissionMode)
|
return fmt.Errorf("invalid protocol '%s' - '%s' ", u.Scheme, m.TransmissionMode)
|
||||||
|
|
@ -334,6 +375,7 @@ func (m *Modbus) gatherRequestsCoil(requests []request) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
nextRequest := time.Now().Add(time.Duration(m.Workarounds.PollPause))
|
||||||
m.Log.Debugf("got coil@%v[%v]: %v", request.address, request.length, bytes)
|
m.Log.Debugf("got coil@%v[%v]: %v", request.address, request.length, bytes)
|
||||||
|
|
||||||
// Bit value handling
|
// Bit value handling
|
||||||
|
|
@ -345,6 +387,9 @@ func (m *Modbus) gatherRequestsCoil(requests []request) error {
|
||||||
request.fields[i].value = uint16((bytes[idx] >> bit) & 0x01)
|
request.fields[i].value = uint16((bytes[idx] >> bit) & 0x01)
|
||||||
m.Log.Debugf(" field %s with bit %d @ byte %d: %v --> %v", field.name, bit, idx, (bytes[idx]>>bit)&0x01, request.fields[i].value)
|
m.Log.Debugf(" field %s with bit %d @ byte %d: %v --> %v", field.name, bit, idx, (bytes[idx]>>bit)&0x01, request.fields[i].value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Some (serial) devices require a pause between requests...
|
||||||
|
time.Sleep(time.Until(nextRequest))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -356,6 +401,7 @@ func (m *Modbus) gatherRequestsDiscrete(requests []request) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
nextRequest := time.Now().Add(time.Duration(m.Workarounds.PollPause))
|
||||||
m.Log.Debugf("got discrete@%v[%v]: %v", request.address, request.length, bytes)
|
m.Log.Debugf("got discrete@%v[%v]: %v", request.address, request.length, bytes)
|
||||||
|
|
||||||
// Bit value handling
|
// Bit value handling
|
||||||
|
|
@ -367,6 +413,9 @@ func (m *Modbus) gatherRequestsDiscrete(requests []request) error {
|
||||||
request.fields[i].value = uint16((bytes[idx] >> bit) & 0x01)
|
request.fields[i].value = uint16((bytes[idx] >> bit) & 0x01)
|
||||||
m.Log.Debugf(" field %s with bit %d @ byte %d: %v --> %v", field.name, bit, idx, (bytes[idx]>>bit)&0x01, request.fields[i].value)
|
m.Log.Debugf(" field %s with bit %d @ byte %d: %v --> %v", field.name, bit, idx, (bytes[idx]>>bit)&0x01, request.fields[i].value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Some (serial) devices require a pause between requests...
|
||||||
|
time.Sleep(time.Until(nextRequest))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -378,6 +427,7 @@ func (m *Modbus) gatherRequestsHolding(requests []request) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
nextRequest := time.Now().Add(time.Duration(m.Workarounds.PollPause))
|
||||||
m.Log.Debugf("got holding@%v[%v]: %v", request.address, request.length, bytes)
|
m.Log.Debugf("got holding@%v[%v]: %v", request.address, request.length, bytes)
|
||||||
|
|
||||||
// Non-bit value handling
|
// Non-bit value handling
|
||||||
|
|
@ -390,6 +440,9 @@ func (m *Modbus) gatherRequestsHolding(requests []request) error {
|
||||||
request.fields[i].value = field.converter(bytes[offset : offset+length])
|
request.fields[i].value = field.converter(bytes[offset : offset+length])
|
||||||
m.Log.Debugf(" field %s with offset %d with len %d: %v --> %v", field.name, offset, length, bytes[offset:offset+length], request.fields[i].value)
|
m.Log.Debugf(" field %s with offset %d with len %d: %v --> %v", field.name, offset, length, bytes[offset:offset+length], request.fields[i].value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Some (serial) devices require a pause between requests...
|
||||||
|
time.Sleep(time.Until(nextRequest))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -401,6 +454,7 @@ func (m *Modbus) gatherRequestsInput(requests []request) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
nextRequest := time.Now().Add(time.Duration(m.Workarounds.PollPause))
|
||||||
m.Log.Debugf("got input@%v[%v]: %v", request.address, request.length, bytes)
|
m.Log.Debugf("got input@%v[%v]: %v", request.address, request.length, bytes)
|
||||||
|
|
||||||
// Non-bit value handling
|
// Non-bit value handling
|
||||||
|
|
@ -413,6 +467,9 @@ func (m *Modbus) gatherRequestsInput(requests []request) error {
|
||||||
request.fields[i].value = field.converter(bytes[offset : offset+length])
|
request.fields[i].value = field.converter(bytes[offset : offset+length])
|
||||||
m.Log.Debugf(" field %s with offset %d with len %d: %v --> %v", field.name, offset, length, bytes[offset:offset+length], request.fields[i].value)
|
m.Log.Debugf(" field %s with offset %d with len %d: %v --> %v", field.name, offset, length, bytes[offset:offset+length], request.fields[i].value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Some (serial) devices require a pause between requests...
|
||||||
|
time.Sleep(time.Until(nextRequest))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -441,6 +498,11 @@ func (m *Modbus) collectFields(acc telegraf.Accumulator, timestamp time.Time, ta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implement the logger interface of the modbus client
|
||||||
|
func (m *Modbus) Printf(format string, v ...interface{}) {
|
||||||
|
m.Log.Debugf(format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
// Add this plugin to telegraf
|
// Add this plugin to telegraf
|
||||||
func init() {
|
func init() {
|
||||||
inputs.Add("modbus", func() telegraf.Input { return &Modbus{} })
|
inputs.Add("modbus", func() telegraf.Input { return &Modbus{} })
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue