feat(inputs.modbus): Allow to convert coil and discrete registers to boolean (#12825)
This commit is contained in:
parent
1eb70808d0
commit
2006086262
|
|
@ -76,6 +76,7 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
|
|||
## Digital Variables, Discrete Inputs and Coils
|
||||
## measurement - the (optional) measurement name, defaults to "modbus"
|
||||
## name - the variable name
|
||||
## data_type - the (optional) output type, can be BOOL or UINT16 (default)
|
||||
## address - variable address
|
||||
|
||||
discrete_inputs = [
|
||||
|
|
@ -178,23 +179,24 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
|
|||
## INT16, UINT16, INT32, UINT32, INT64, UINT64 and
|
||||
## FLOAT16, FLOAT32, FLOAT64 (IEEE 754 binary representation)
|
||||
## scale *1,2 - (optional) factor to scale the variable with
|
||||
## output *1,2 - (optional) type of resulting field, can be INT64, UINT64 or FLOAT64. Defaults to FLOAT64 if
|
||||
## output *1,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).
|
||||
## measurement *1 - (optional) measurement name, defaults to the setting of the request
|
||||
## omit - (optional) omit this field. Useful to leave out single values when querying many registers
|
||||
## with a single request. Defaults to "false".
|
||||
##
|
||||
## *1: Those fields are ignored if field is omitted ("omit"=true)
|
||||
##
|
||||
## *2: Thise fields are ignored for both "coil" and "discrete"-input type of registers. For those register types
|
||||
## the fields are output as zero or one in UINT64 format by default.
|
||||
## *1: These fields are ignored if field is omitted ("omit"=true)
|
||||
## *2: These fields are ignored for both "coil" and "discrete"-input type of registers.
|
||||
## *3: This field can only be "UINT16" or "BOOL" if specified for both "coil"
|
||||
## and "discrete"-input type of registers. By default the fields are
|
||||
## output as zero or one in UINT16 format unless "BOOL" is used.
|
||||
|
||||
## Coil / discrete input example
|
||||
fields = [
|
||||
{ address=0, name="motor1_run"},
|
||||
{ address=1, name="jog", measurement="motor"},
|
||||
{ address=2, name="motor1_stop", omit=true},
|
||||
{ address=3, name="motor1_overheating"},
|
||||
{ address=3, name="motor1_overheating", output="BOOL"},
|
||||
]
|
||||
|
||||
[inputs.modbus.request.tags]
|
||||
|
|
@ -320,6 +322,11 @@ floating-point-number. The size of the output type is assumed to be large enough
|
|||
for all supported input types. The mapping from the input type to the output
|
||||
type is fixed and cannot be configured.
|
||||
|
||||
##### Booleans: `BOOL`
|
||||
|
||||
This type is only valid for _coil_ and _discrete_ registers. The value will be
|
||||
`true` if the register has a non-zero (ON) value and `false` otherwise.
|
||||
|
||||
##### Integers: `INT8L`, `INT8H`, `UINT8L`, `UINT8H`
|
||||
|
||||
These types are used for 8-bit integer values. Select the one that matches your
|
||||
|
|
@ -329,7 +336,7 @@ the register respectively.
|
|||
##### Integers: `INT16`, `UINT16`, `INT32`, `UINT32`, `INT64`, `UINT64`
|
||||
|
||||
These types are used for integer input values. Select the one that matches your
|
||||
modbus data source.
|
||||
modbus data source. For _coil_ and _discrete_ registers only `UINT16` is valid.
|
||||
|
||||
##### Floating Point: `FLOAT16-IEEE`, `FLOAT32-IEEE`, `FLOAT64-IEEE`
|
||||
|
||||
|
|
@ -512,10 +519,11 @@ 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` and can be
|
||||
omitted. In case the `register` type is a bit-type (`coil` or `discrete`) only
|
||||
`UINT16` or `BOOL` are valid with the former being the default if omitted.
|
||||
For `coil` and `discrete` registers the field-value is output as zero or one in
|
||||
`UINT16` format or as `true` and `false` in `BOOL` format.
|
||||
|
||||
#### per-field measurement setting
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ func (c *ConfigurationOriginal) Process() (map[byte]requestSet, error) {
|
|||
if !c.workarounds.OnRequestPerField {
|
||||
maxQuantity = maxQuantityCoils
|
||||
}
|
||||
coil, err := c.initRequests(c.Coils, maxQuantity)
|
||||
coil, err := c.initRequests(c.Coils, maxQuantity, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -59,7 +59,7 @@ func (c *ConfigurationOriginal) Process() (map[byte]requestSet, error) {
|
|||
if !c.workarounds.OnRequestPerField {
|
||||
maxQuantity = maxQuantityDiscreteInput
|
||||
}
|
||||
discrete, err := c.initRequests(c.DiscreteInputs, maxQuantity)
|
||||
discrete, err := c.initRequests(c.DiscreteInputs, maxQuantity, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -67,7 +67,7 @@ func (c *ConfigurationOriginal) Process() (map[byte]requestSet, error) {
|
|||
if !c.workarounds.OnRequestPerField {
|
||||
maxQuantity = maxQuantityHoldingRegisters
|
||||
}
|
||||
holding, err := c.initRequests(c.HoldingRegisters, maxQuantity)
|
||||
holding, err := c.initRequests(c.HoldingRegisters, maxQuantity, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -75,7 +75,7 @@ func (c *ConfigurationOriginal) Process() (map[byte]requestSet, error) {
|
|||
if !c.workarounds.OnRequestPerField {
|
||||
maxQuantity = maxQuantityInputRegisters
|
||||
}
|
||||
input, err := c.initRequests(c.InputRegisters, maxQuantity)
|
||||
input, err := c.initRequests(c.InputRegisters, maxQuantity, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -90,8 +90,8 @@ func (c *ConfigurationOriginal) Process() (map[byte]requestSet, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (c *ConfigurationOriginal) initRequests(fieldDefs []fieldDefinition, maxQuantity uint16) ([]request, error) {
|
||||
fields, err := c.initFields(fieldDefs)
|
||||
func (c *ConfigurationOriginal) initRequests(fieldDefs []fieldDefinition, maxQuantity uint16, typed bool) ([]request, error) {
|
||||
fields, err := c.initFields(fieldDefs, typed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -104,11 +104,11 @@ func (c *ConfigurationOriginal) initRequests(fieldDefs []fieldDefinition, maxQua
|
|||
return groupFieldsToRequests(fields, params), nil
|
||||
}
|
||||
|
||||
func (c *ConfigurationOriginal) initFields(fieldDefs []fieldDefinition) ([]field, error) {
|
||||
func (c *ConfigurationOriginal) initFields(fieldDefs []fieldDefinition, typed bool) ([]field, error) {
|
||||
// Construct the fields from the field definitions
|
||||
fields := make([]field, 0, len(fieldDefs))
|
||||
for _, def := range fieldDefs {
|
||||
f, err := c.newFieldFromDefinition(def)
|
||||
f, err := c.newFieldFromDefinition(def, typed)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("initializing field %q failed: %w", def.Name, err)
|
||||
}
|
||||
|
|
@ -118,7 +118,7 @@ func (c *ConfigurationOriginal) initFields(fieldDefs []fieldDefinition) ([]field
|
|||
return fields, nil
|
||||
}
|
||||
|
||||
func (c *ConfigurationOriginal) newFieldFromDefinition(def fieldDefinition) (field, error) {
|
||||
func (c *ConfigurationOriginal) newFieldFromDefinition(def fieldDefinition, typed bool) (field, error) {
|
||||
// Check if the addresses are consecutive
|
||||
expected := def.Address[0]
|
||||
for _, current := range def.Address[1:] {
|
||||
|
|
@ -135,6 +135,17 @@ func (c *ConfigurationOriginal) newFieldFromDefinition(def fieldDefinition) (fie
|
|||
address: def.Address[0],
|
||||
length: uint16(len(def.Address)),
|
||||
}
|
||||
|
||||
// Handle coil and discrete registers which do have a limited datatype set
|
||||
if !typed {
|
||||
var err error
|
||||
f.converter, err = determineUntypedConverter(def.DataType)
|
||||
if err != nil {
|
||||
return field{}, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
if def.DataType != "" {
|
||||
inType, err := c.normalizeInputDatatype(def.DataType, len(def.Address))
|
||||
if err != nil {
|
||||
|
|
@ -194,6 +205,13 @@ 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)
|
||||
}
|
||||
} else {
|
||||
// Bit-registers do have less data types
|
||||
switch item.DataType {
|
||||
case "", "UINT16", "BOOL":
|
||||
default:
|
||||
return fmt.Errorf("invalid data type %q in %q - %q", item.DataType, registerType, item.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// check address
|
||||
|
|
|
|||
|
|
@ -102,9 +102,12 @@ func (c *ConfigurationPerRequest) Check() error {
|
|||
for fidx, f := range def.Fields {
|
||||
// Check the input type for all fields except the bit-field ones.
|
||||
// We later need the type (even for omitted fields) to determine the length.
|
||||
if def.RegisterType == cHoldingRegisters || def.RegisterType == cInputRegisters {
|
||||
if def.RegisterType == "holding" || def.RegisterType == "input" {
|
||||
switch f.InputType {
|
||||
case "INT16", "UINT16", "INT32", "UINT32", "INT64", "UINT64", "FLOAT32", "FLOAT64":
|
||||
case "":
|
||||
case "INT8L", "INT8H", "INT16", "INT32", "INT64":
|
||||
case "UINT8L", "UINT8H", "UINT16", "UINT32", "UINT64":
|
||||
case "FLOAT16", "FLOAT32", "FLOAT64":
|
||||
default:
|
||||
return fmt.Errorf("unknown register data-type %q for field %q", f.InputType, f.Name)
|
||||
}
|
||||
|
|
@ -120,14 +123,20 @@ func (c *ConfigurationPerRequest) Check() error {
|
|||
return fmt.Errorf("empty field name in request for slave %d", def.SlaveID)
|
||||
}
|
||||
|
||||
// Check fields only relevant for non-bit register types
|
||||
if def.RegisterType == cHoldingRegisters || def.RegisterType == cInputRegisters {
|
||||
// Check output type
|
||||
// Check output type
|
||||
if def.RegisterType == "holding" || def.RegisterType == "input" {
|
||||
switch f.OutputType {
|
||||
case "", "INT64", "UINT64", "FLOAT64":
|
||||
default:
|
||||
return fmt.Errorf("unknown output data-type %q for field %q", f.OutputType, f.Name)
|
||||
}
|
||||
} else {
|
||||
// Bit register types can only be UINT64 or BOOL
|
||||
switch f.OutputType {
|
||||
case "", "UINT16", "BOOL":
|
||||
default:
|
||||
return fmt.Errorf("unknown output data-type %q for field %q", f.OutputType, f.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the default for measurement
|
||||
|
|
@ -257,6 +266,14 @@ func (c *ConfigurationPerRequest) newFieldFromDefinition(def requestFieldDefinit
|
|||
omit: def.Omit,
|
||||
}
|
||||
|
||||
// Handle type conversions for coil and discrete registers
|
||||
if !typed {
|
||||
f.converter, err = determineUntypedConverter(def.OutputType)
|
||||
if err != nil {
|
||||
return field{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// No more processing for un-typed (coil and discrete registers) or omitted fields
|
||||
if !typed || def.Omit {
|
||||
return f, nil
|
||||
|
|
|
|||
|
|
@ -406,8 +406,9 @@ func (m *Modbus) gatherRequestsCoil(requests []request) error {
|
|||
idx := offset / 8
|
||||
bit := offset % 8
|
||||
|
||||
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)
|
||||
v := (bytes[idx] >> bit) & 0x01
|
||||
request.fields[i].value = field.converter([]byte{v})
|
||||
m.Log.Debugf(" field %s with bit %d @ byte %d: %v --> %v", field.name, bit, idx, v, request.fields[i].value)
|
||||
}
|
||||
|
||||
// Some (serial) devices require a pause between requests...
|
||||
|
|
@ -432,8 +433,9 @@ func (m *Modbus) gatherRequestsDiscrete(requests []request) error {
|
|||
idx := offset / 8
|
||||
bit := offset % 8
|
||||
|
||||
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)
|
||||
v := (bytes[idx] >> bit) & 0x01
|
||||
request.fields[i].value = field.converter([]byte{v})
|
||||
m.Log.Debugf(" field %s with bit %d @ byte %d: %v --> %v", field.name, bit, idx, v, request.fields[i].value)
|
||||
}
|
||||
|
||||
// Some (serial) devices require a pause between requests...
|
||||
|
|
|
|||
|
|
@ -21,6 +21,11 @@ import (
|
|||
"github.com/influxdata/telegraf/testutil"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
telegraf.Debug = false
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestControllers(t *testing.T) {
|
||||
var tests = []struct {
|
||||
name string
|
||||
|
|
@ -149,65 +154,96 @@ func TestCoils(t *testing.T) {
|
|||
var coilTests = []struct {
|
||||
name string
|
||||
address uint16
|
||||
dtype string
|
||||
quantity uint16
|
||||
write []byte
|
||||
read uint16
|
||||
read interface{}
|
||||
}{
|
||||
{
|
||||
name: "coil0_turn_off",
|
||||
address: 0,
|
||||
quantity: 1,
|
||||
write: []byte{0x00},
|
||||
read: 0,
|
||||
read: uint16(0),
|
||||
},
|
||||
{
|
||||
name: "coil0_turn_on",
|
||||
address: 0,
|
||||
quantity: 1,
|
||||
write: []byte{0x01},
|
||||
read: 1,
|
||||
read: uint16(1),
|
||||
},
|
||||
{
|
||||
name: "coil1_turn_on",
|
||||
address: 1,
|
||||
quantity: 1,
|
||||
write: []byte{0x01},
|
||||
read: 1,
|
||||
read: uint16(1),
|
||||
},
|
||||
{
|
||||
name: "coil2_turn_on",
|
||||
address: 2,
|
||||
quantity: 1,
|
||||
write: []byte{0x01},
|
||||
read: 1,
|
||||
read: uint16(1),
|
||||
},
|
||||
{
|
||||
name: "coil3_turn_on",
|
||||
address: 3,
|
||||
quantity: 1,
|
||||
write: []byte{0x01},
|
||||
read: 1,
|
||||
read: uint16(1),
|
||||
},
|
||||
{
|
||||
name: "coil1_turn_off",
|
||||
address: 1,
|
||||
quantity: 1,
|
||||
write: []byte{0x00},
|
||||
read: 0,
|
||||
read: uint16(0),
|
||||
},
|
||||
{
|
||||
name: "coil2_turn_off",
|
||||
address: 2,
|
||||
quantity: 1,
|
||||
write: []byte{0x00},
|
||||
read: 0,
|
||||
read: uint16(0),
|
||||
},
|
||||
{
|
||||
name: "coil3_turn_off",
|
||||
address: 3,
|
||||
quantity: 1,
|
||||
write: []byte{0x00},
|
||||
read: 0,
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -233,8 +269,9 @@ func TestCoils(t *testing.T) {
|
|||
modbus.SlaveID = 1
|
||||
modbus.Coils = []fieldDefinition{
|
||||
{
|
||||
Name: ct.name,
|
||||
Address: []uint16{ct.address},
|
||||
Name: ct.name,
|
||||
Address: []uint16{ct.address},
|
||||
DataType: ct.dtype,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -262,6 +299,101 @@ func TestCoils(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRequestTypesCoil(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
address uint16
|
||||
dataTypeOut string
|
||||
write uint16
|
||||
read interface{}
|
||||
}{
|
||||
{
|
||||
name: "coil-1-off",
|
||||
address: 1,
|
||||
write: 0,
|
||||
read: uint16(0),
|
||||
},
|
||||
{
|
||||
name: "coil-2-on",
|
||||
address: 2,
|
||||
write: 0xFF00,
|
||||
read: uint16(1),
|
||||
},
|
||||
{
|
||||
name: "coil-3-false",
|
||||
address: 3,
|
||||
dataTypeOut: "BOOL",
|
||||
write: 0,
|
||||
read: false,
|
||||
},
|
||||
{
|
||||
name: "coil-4-true",
|
||||
address: 4,
|
||||
dataTypeOut: "BOOL",
|
||||
write: 0xFF00,
|
||||
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 _, hrt := range tests {
|
||||
t.Run(hrt.name, func(t *testing.T) {
|
||||
_, err := client.WriteSingleCoil(hrt.address, hrt.write)
|
||||
require.NoError(t, err)
|
||||
|
||||
modbus := Modbus{
|
||||
Name: "TestRequestTypesCoil",
|
||||
Controller: "tcp://localhost:1502",
|
||||
ConfigurationType: "request",
|
||||
Log: testutil.Logger{},
|
||||
}
|
||||
modbus.Requests = []requestDefinition{
|
||||
{
|
||||
SlaveID: 1,
|
||||
ByteOrder: "ABCD",
|
||||
RegisterType: "coil",
|
||||
Fields: []requestFieldDefinition{
|
||||
{
|
||||
Name: hrt.name,
|
||||
OutputType: hrt.dataTypeOut,
|
||||
Address: hrt.address,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expected := []telegraf.Metric{
|
||||
testutil.MustMetric(
|
||||
"modbus",
|
||||
map[string]string{
|
||||
"type": cCoils,
|
||||
"slave_id": "1",
|
||||
"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 TestHoldingRegisters(t *testing.T) {
|
||||
var holdingRegisterTests = []struct {
|
||||
name string
|
||||
|
|
@ -2651,7 +2783,15 @@ func TestConfigurationPerRequest(t *testing.T) {
|
|||
Address: uint16(2),
|
||||
InputType: "INT64",
|
||||
Scale: 1.2,
|
||||
OutputType: "FLOAT64",
|
||||
OutputType: "UINT16",
|
||||
Measurement: "modbus",
|
||||
},
|
||||
{
|
||||
Name: "coil-3",
|
||||
Address: uint16(3),
|
||||
InputType: "INT64",
|
||||
Scale: 1.2,
|
||||
OutputType: "BOOL",
|
||||
Measurement: "modbus",
|
||||
},
|
||||
},
|
||||
|
|
@ -2661,20 +2801,28 @@ func TestConfigurationPerRequest(t *testing.T) {
|
|||
RegisterType: "coil",
|
||||
Fields: []requestFieldDefinition{
|
||||
{
|
||||
Name: "coil-3",
|
||||
Name: "coil-4",
|
||||
Address: uint16(6),
|
||||
},
|
||||
{
|
||||
Name: "coil-4",
|
||||
Name: "coil-5",
|
||||
Address: uint16(7),
|
||||
Omit: true,
|
||||
},
|
||||
{
|
||||
Name: "coil-5",
|
||||
Name: "coil-6",
|
||||
Address: uint16(8),
|
||||
InputType: "INT64",
|
||||
Scale: 1.2,
|
||||
OutputType: "FLOAT64",
|
||||
OutputType: "UINT16",
|
||||
Measurement: "modbus",
|
||||
},
|
||||
{
|
||||
Name: "coil-7",
|
||||
Address: uint16(9),
|
||||
InputType: "INT64",
|
||||
Scale: 1.2,
|
||||
OutputType: "BOOL",
|
||||
Measurement: "modbus",
|
||||
},
|
||||
},
|
||||
|
|
@ -2698,7 +2846,15 @@ func TestConfigurationPerRequest(t *testing.T) {
|
|||
Address: uint16(2),
|
||||
InputType: "INT64",
|
||||
Scale: 1.2,
|
||||
OutputType: "FLOAT64",
|
||||
OutputType: "UINT16",
|
||||
Measurement: "modbus",
|
||||
},
|
||||
{
|
||||
Name: "discrete-3",
|
||||
Address: uint16(3),
|
||||
InputType: "INT64",
|
||||
Scale: 1.2,
|
||||
OutputType: "BOOL",
|
||||
Measurement: "modbus",
|
||||
},
|
||||
},
|
||||
|
|
@ -2793,7 +2949,7 @@ func TestConfigurationPerRequestWithTags(t *testing.T) {
|
|||
Address: uint16(2),
|
||||
InputType: "INT64",
|
||||
Scale: 1.2,
|
||||
OutputType: "FLOAT64",
|
||||
OutputType: "UINT16",
|
||||
Measurement: "modbus",
|
||||
},
|
||||
},
|
||||
|
|
@ -2821,7 +2977,7 @@ func TestConfigurationPerRequestWithTags(t *testing.T) {
|
|||
Address: uint16(8),
|
||||
InputType: "INT64",
|
||||
Scale: 1.2,
|
||||
OutputType: "FLOAT64",
|
||||
OutputType: "UINT16",
|
||||
Measurement: "modbus",
|
||||
},
|
||||
},
|
||||
|
|
@ -2850,7 +3006,7 @@ func TestConfigurationPerRequestWithTags(t *testing.T) {
|
|||
Address: uint16(2),
|
||||
InputType: "INT64",
|
||||
Scale: 1.2,
|
||||
OutputType: "FLOAT64",
|
||||
OutputType: "UINT16",
|
||||
Measurement: "modbus",
|
||||
},
|
||||
},
|
||||
|
|
@ -3151,7 +3307,7 @@ func TestConfigurationPerRequestFail(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
errormsg: "cannot process configuration: initializing field \"holding-0\" failed: unknown output type \"UINT8\"",
|
||||
errormsg: `configuration invalid: unknown output data-type "UINT8" for field "holding-0"`,
|
||||
},
|
||||
{
|
||||
name: "duplicate fields (holding)",
|
||||
|
|
@ -3263,7 +3419,7 @@ func TestConfigurationPerRequestFail(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
errormsg: "cannot process configuration: initializing field \"input-0\" failed: unknown output type \"UINT8\"",
|
||||
errormsg: `configuration invalid: unknown output data-type "UINT8" for field "input-0"`,
|
||||
},
|
||||
{
|
||||
name: "duplicate fields (input)",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
## Digital Variables, Discrete Inputs and Coils
|
||||
## measurement - the (optional) measurement name, defaults to "modbus"
|
||||
## name - the variable name
|
||||
## data_type - the (optional) output type, can be BOOL or UINT16 (default)
|
||||
## address - variable address
|
||||
|
||||
discrete_inputs = [
|
||||
|
|
|
|||
|
|
@ -57,23 +57,24 @@
|
|||
## INT16, UINT16, INT32, UINT32, INT64, UINT64 and
|
||||
## FLOAT16, FLOAT32, FLOAT64 (IEEE 754 binary representation)
|
||||
## scale *1,2 - (optional) factor to scale the variable with
|
||||
## output *1,2 - (optional) type of resulting field, can be INT64, UINT64 or FLOAT64. Defaults to FLOAT64 if
|
||||
## output *1,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).
|
||||
## measurement *1 - (optional) measurement name, defaults to the setting of the request
|
||||
## omit - (optional) omit this field. Useful to leave out single values when querying many registers
|
||||
## with a single request. Defaults to "false".
|
||||
##
|
||||
## *1: Those fields are ignored if field is omitted ("omit"=true)
|
||||
##
|
||||
## *2: Thise fields are ignored for both "coil" and "discrete"-input type of registers. For those register types
|
||||
## the fields are output as zero or one in UINT64 format by default.
|
||||
## *1: These fields are ignored if field is omitted ("omit"=true)
|
||||
## *2: These fields are ignored for both "coil" and "discrete"-input type of registers.
|
||||
## *3: This field can only be "UINT16" or "BOOL" if specified for both "coil"
|
||||
## and "discrete"-input type of registers. By default the fields are
|
||||
## output as zero or one in UINT16 format unless "BOOL" is used.
|
||||
|
||||
## Coil / discrete input example
|
||||
fields = [
|
||||
{ address=0, name="motor1_run"},
|
||||
{ address=1, name="jog", measurement="motor"},
|
||||
{ address=2, name="motor1_stop", omit=true},
|
||||
{ address=3, name="motor1_overheating"},
|
||||
{ address=3, name="motor1_overheating", output="BOOL"},
|
||||
]
|
||||
|
||||
[inputs.modbus.request.tags]
|
||||
|
|
|
|||
|
|
@ -2,6 +2,20 @@ package modbus
|
|||
|
||||
import "fmt"
|
||||
|
||||
func determineUntypedConverter(outType string) (fieldConverterFunc, error) {
|
||||
switch outType {
|
||||
case "", "UINT16":
|
||||
return func(b []byte) interface{} {
|
||||
return uint16(b[0])
|
||||
}, nil
|
||||
case "BOOL":
|
||||
return func(b []byte) interface{} {
|
||||
return b[0] != 0
|
||||
}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("invalid output data-type: %s", outType)
|
||||
}
|
||||
|
||||
func determineConverter(inType, byteOrder, outType string, scale float64) (fieldConverterFunc, error) {
|
||||
if scale != 0.0 {
|
||||
return determineConverterScale(inType, byteOrder, outType, scale)
|
||||
|
|
|
|||
Loading…
Reference in New Issue