1
0
Fork 0
telegraf/plugins/inputs/modbus/configuration_register.go
Daniel Baumann 4978089aab
Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-05-24 07:26:29 +02:00

348 lines
9.7 KiB
Go

package modbus
import (
_ "embed"
"fmt"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
)
//go:embed sample_register.conf
var sampleConfigPartPerRegister string
type fieldDefinition struct {
Measurement string `toml:"measurement"`
Name string `toml:"name"`
ByteOrder string `toml:"byte_order"`
DataType string `toml:"data_type"`
Scale float64 `toml:"scale"`
Address []uint16 `toml:"address"`
Bit uint8 `toml:"bit"`
}
type configurationOriginal struct {
SlaveID byte `toml:"slave_id"`
DiscreteInputs []fieldDefinition `toml:"discrete_inputs"`
Coils []fieldDefinition `toml:"coils"`
HoldingRegisters []fieldDefinition `toml:"holding_registers"`
InputRegisters []fieldDefinition `toml:"input_registers"`
workarounds workarounds
logger telegraf.Logger
}
func (*configurationOriginal) sampleConfigPart() string {
return sampleConfigPartPerRegister
}
func (c *configurationOriginal) check() error {
switch c.workarounds.StringRegisterLocation {
case "", "both", "lower", "upper":
// Do nothing as those are valid
default:
return fmt.Errorf("invalid 'string_register_location' %q", c.workarounds.StringRegisterLocation)
}
if err := validateFieldDefinitions(c.DiscreteInputs, cDiscreteInputs); err != nil {
return err
}
if err := validateFieldDefinitions(c.Coils, cCoils); err != nil {
return err
}
if err := validateFieldDefinitions(c.HoldingRegisters, cHoldingRegisters); err != nil {
return err
}
return validateFieldDefinitions(c.InputRegisters, cInputRegisters)
}
func (c *configurationOriginal) process() (map[byte]requestSet, error) {
maxQuantity := uint16(1)
if !c.workarounds.OnRequestPerField {
maxQuantity = maxQuantityCoils
}
coil, err := c.initRequests(c.Coils, maxQuantity, false)
if err != nil {
return nil, err
}
if !c.workarounds.OnRequestPerField {
maxQuantity = maxQuantityDiscreteInput
}
discrete, err := c.initRequests(c.DiscreteInputs, maxQuantity, false)
if err != nil {
return nil, err
}
if !c.workarounds.OnRequestPerField {
maxQuantity = maxQuantityHoldingRegisters
}
holding, err := c.initRequests(c.HoldingRegisters, maxQuantity, true)
if err != nil {
return nil, err
}
if !c.workarounds.OnRequestPerField {
maxQuantity = maxQuantityInputRegisters
}
input, err := c.initRequests(c.InputRegisters, maxQuantity, true)
if err != nil {
return nil, err
}
return map[byte]requestSet{
c.SlaveID: {
coil: coil,
discrete: discrete,
holding: holding,
input: input,
},
}, nil
}
func (c *configurationOriginal) initRequests(fieldDefs []fieldDefinition, maxQuantity uint16, typed bool) ([]request, error) {
fields, err := c.initFields(fieldDefs, typed)
if err != nil {
return nil, err
}
params := groupingParams{
maxBatchSize: maxQuantity,
optimization: "none",
enforceFromZero: c.workarounds.ReadCoilsStartingAtZero,
log: c.logger,
}
return groupFieldsToRequests(fields, params), nil
}
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, typed)
if err != nil {
return nil, fmt.Errorf("initializing field %q failed: %w", def.Name, err)
}
fields = append(fields, f)
}
return fields, nil
}
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:] {
expected++
if current != expected {
return field{}, fmt.Errorf("addresses of field %q are not consecutive", def.Name)
}
}
// Initialize the field
f := field{
measurement: def.Measurement,
name: def.Name,
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 {
return f, err
}
outType, err := c.normalizeOutputDatatype(def.DataType)
if err != nil {
return f, err
}
byteOrder, err := c.normalizeByteOrder(def.ByteOrder)
if err != nil {
return f, err
}
f.converter, err = determineConverter(inType, byteOrder, outType, def.Scale, def.Bit, c.workarounds.StringRegisterLocation)
if err != nil {
return f, err
}
}
return f, nil
}
func validateFieldDefinitions(fieldDefs []fieldDefinition, registerType string) error {
nameEncountered := make(map[string]bool, len(fieldDefs))
for _, item := range fieldDefs {
// check empty name
if item.Name == "" {
return fmt.Errorf("empty name in %q", registerType)
}
// search name duplicate
canonicalName := item.Measurement + "." + item.Name
if nameEncountered[canonicalName] {
return fmt.Errorf("name %q is duplicated in measurement %q %q - %q", item.Name, item.Measurement, registerType, item.Name)
}
nameEncountered[canonicalName] = true
if registerType == cInputRegisters || registerType == cHoldingRegisters {
// search byte order
switch item.ByteOrder {
case "AB", "BA", "ABCD", "CDAB", "BADC", "DCBA", "ABCDEFGH", "HGFEDCBA", "BADCFEHG", "GHEFCDAB":
default:
return fmt.Errorf("invalid byte order %q in %q - %q", item.ByteOrder, registerType, item.Name)
}
// search data type
switch item.DataType {
case "INT8L", "INT8H", "UINT8L", "UINT8H",
"UINT16", "INT16", "UINT32", "INT32", "UINT64", "INT64",
"FLOAT16-IEEE", "FLOAT32-IEEE", "FLOAT64-IEEE", "FLOAT32", "FIXED", "UFIXED":
// Check scale
if item.Scale == 0.0 {
return fmt.Errorf("invalid scale '%f' in %q - %q", item.Scale, registerType, item.Name)
}
case "BIT", "STRING":
default:
return fmt.Errorf("invalid data type %q in %q - %q", item.DataType, 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)
}
}
// 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
}
// 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)
}
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 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)
}
}
return nil
}
func (*configurationOriginal) normalizeInputDatatype(dataType string, words int) (string, error) {
if dataType == "FLOAT32" {
config.PrintOptionValueDeprecationNotice("input.modbus", "data_type", "FLOAT32", telegraf.DeprecationInfo{
Since: "1.16.0",
RemovalIn: "1.35.0",
Notice: "Use 'UFIXED' instead",
})
}
// Handle our special types
switch dataType {
case "FIXED":
switch words {
case 1:
return "INT16", nil
case 2:
return "INT32", nil
case 4:
return "INT64", nil
default:
return "unknown", fmt.Errorf("invalid length %d for type %q", words, dataType)
}
case "FLOAT32", "UFIXED":
switch words {
case 1:
return "UINT16", nil
case 2:
return "UINT32", nil
case 4:
return "UINT64", nil
default:
return "unknown", fmt.Errorf("invalid length %d for type %q", words, dataType)
}
case "FLOAT16-IEEE":
return "FLOAT16", nil
case "FLOAT32-IEEE":
return "FLOAT32", nil
case "FLOAT64-IEEE":
return "FLOAT64", nil
case "STRING":
return "STRING", nil
case "BIT":
return "BIT", nil
}
return normalizeInputDatatype(dataType)
}
func (*configurationOriginal) normalizeOutputDatatype(dataType string) (string, error) {
// Handle our special types
switch dataType {
case "FIXED", "FLOAT32", "UFIXED":
return "FLOAT64", nil
}
return normalizeOutputDatatype("native")
}
func (*configurationOriginal) normalizeByteOrder(byteOrder string) (string, error) {
// Handle our special types
switch byteOrder {
case "AB", "ABCDEFGH":
return "ABCD", nil
case "BADCFEHG":
return "BADC", nil
case "GHEFCDAB":
return "CDAB", nil
case "BA", "HGFEDCBA":
return "DCBA", nil
}
return normalizeByteOrder(byteOrder)
}