feat(inputs.modbus): Allow reading single bits of input and holding registers (#15648)
This commit is contained in:
parent
f9900f8787
commit
fe7321e35b
|
|
@ -101,12 +101,14 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
|
|||
## |---BA, DCBA - Little Endian
|
||||
## |---BADC - Mid-Big Endian
|
||||
## |---CDAB - Mid-Little Endian
|
||||
## data_type - INT8L, INT8H, UINT8L, UINT8H (low and high byte variants)
|
||||
## data_type - BIT (single bit of a register)
|
||||
## INT8L, INT8H, UINT8L, UINT8H (low and high byte variants)
|
||||
## INT16, UINT16, INT32, UINT32, INT64, UINT64,
|
||||
## FLOAT16-IEEE, FLOAT32-IEEE, FLOAT64-IEEE (IEEE 754 binary representation)
|
||||
## FIXED, UFIXED (fixed-point representation on input)
|
||||
## FLOAT32 is a deprecated alias for UFIXED for historic reasons, should be avoided
|
||||
## STRING (byte-sequence converted to string)
|
||||
## bit - (optional) bit of the register, ONLY valid for BIT type
|
||||
## scale - the final numeric variable representation
|
||||
## address - variable address
|
||||
|
||||
|
|
@ -176,11 +178,13 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
|
|||
## address - address of the register to query. For coil and discrete inputs this is the bit address.
|
||||
## name *1 - field name
|
||||
## type *1,2 - type of the modbus field, can be
|
||||
## BIT (single bit of a register)
|
||||
## INT8L, INT8H, UINT8L, UINT8H (low and high byte variants)
|
||||
## INT16, UINT16, INT32, UINT32, INT64, UINT64 and
|
||||
## FLOAT16, FLOAT32, FLOAT64 (IEEE 754 binary representation)
|
||||
## STRING (byte-sequence converted to string)
|
||||
## length *1,2 - (optional) number of registers, ONLY valid for STRING type
|
||||
## bit *1,2 - (optional) bit of the register, ONLY valid for BIT type
|
||||
## scale *1,2,4 - (optional) factor to scale the variable with
|
||||
## output *1,3,4 - (optional) type of resulting field, can be INT64, UINT64 or FLOAT64.
|
||||
## Defaults to FLOAT64 for numeric fields if "scale" is provided.
|
||||
|
|
@ -286,11 +290,13 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
|
|||
## address - address of the register to query. For coil and discrete inputs this is the bit address.
|
||||
## name - field name
|
||||
## type *1 - type of the modbus field, can be
|
||||
## BIT (single bit of a register)
|
||||
## INT8L, INT8H, UINT8L, UINT8H (low and high byte variants)
|
||||
## INT16, UINT16, INT32, UINT32, INT64, UINT64 and
|
||||
## FLOAT16, FLOAT32, FLOAT64 (IEEE 754 binary representation)
|
||||
## STRING (byte-sequence converted to string)
|
||||
## length *1 - (optional) number of registers, ONLY valid for STRING type
|
||||
## bit *1,2 - (optional) bit of the register, ONLY valid for BIT type
|
||||
## scale *1,3 - (optional) factor to scale the variable with
|
||||
## output *2,3 - (optional) type of resulting field, can be INT64, UINT64 or FLOAT64. Defaults to FLOAT64 if
|
||||
## "scale" is provided and to the input "type" class otherwise (i.e. INT* -> INT64, etc).
|
||||
|
|
@ -461,6 +467,12 @@ setting and convert the byte-sequence to a string. Please note, if the
|
|||
byte-sequence contains a `null` byte, the string is truncated at this position.
|
||||
You cannot use the `scale` setting for string fields.
|
||||
|
||||
##### Bit: `BIT`
|
||||
|
||||
This type is used to query a single bit of a register specified in the `address`
|
||||
setting and convert the value to an unsigned integer. This type __requires__ the
|
||||
`bit` setting to be specified.
|
||||
|
||||
---
|
||||
|
||||
### `request` configuration style
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ func removeDuplicates(elements []uint16) []uint16 {
|
|||
|
||||
func normalizeInputDatatype(dataType string) (string, error) {
|
||||
switch dataType {
|
||||
case "INT8L", "INT8H", "UINT8L", "UINT8H",
|
||||
case "BIT", "INT8L", "INT8H", "UINT8L", "UINT8H",
|
||||
"INT16", "UINT16", "INT32", "UINT32", "INT64", "UINT64",
|
||||
"FLOAT16", "FLOAT32", "FLOAT64", "STRING":
|
||||
return dataType, nil
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ type metricFieldDefinition struct {
|
|||
InputType string `toml:"type"`
|
||||
Scale float64 `toml:"scale"`
|
||||
OutputType string `toml:"output"`
|
||||
Bit uint8 `toml:"bit"`
|
||||
}
|
||||
|
||||
type metricDefinition struct {
|
||||
|
|
@ -116,6 +117,9 @@ func (c *ConfigurationPerMetric) Check() error {
|
|||
if f.Length != 0 {
|
||||
return fmt.Errorf("length option cannot be used for type %q of field %q", f.InputType, f.Name)
|
||||
}
|
||||
if f.Bit != 0 {
|
||||
return fmt.Errorf("bit option cannot be used for type %q of field %q", f.InputType, f.Name)
|
||||
}
|
||||
if f.OutputType == "STRING" {
|
||||
return fmt.Errorf("cannot output field %q as string", f.Name)
|
||||
}
|
||||
|
|
@ -123,12 +127,22 @@ func (c *ConfigurationPerMetric) Check() error {
|
|||
if f.Length < 1 {
|
||||
return fmt.Errorf("missing length for string field %q", f.Name)
|
||||
}
|
||||
if f.Bit != 0 {
|
||||
return fmt.Errorf("bit option cannot be used for type %q of field %q", f.InputType, f.Name)
|
||||
}
|
||||
if f.Scale != 0.0 {
|
||||
return fmt.Errorf("scale option cannot be used for string field %q", f.Name)
|
||||
}
|
||||
if f.OutputType != "" && f.OutputType != "STRING" {
|
||||
return fmt.Errorf("invalid output type %q for string field %q", f.OutputType, f.Name)
|
||||
}
|
||||
case "BIT":
|
||||
if f.Length != 0 {
|
||||
return fmt.Errorf("length option cannot be used for type %q of field %q", f.InputType, f.Name)
|
||||
}
|
||||
if f.OutputType == "STRING" {
|
||||
return fmt.Errorf("cannot output field %q as string", f.Name)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown register data-type %q for field %q", f.InputType, f.Name)
|
||||
}
|
||||
|
|
@ -315,7 +329,7 @@ func (c *ConfigurationPerMetric) newField(def metricFieldDefinition, mdef metric
|
|||
return field{}, err
|
||||
}
|
||||
|
||||
f.converter, err = determineConverter(inType, order, outType, def.Scale, c.workarounds.StringRegisterLocation)
|
||||
f.converter, err = determineConverter(inType, order, outType, def.Scale, def.Bit, c.workarounds.StringRegisterLocation)
|
||||
if err != nil {
|
||||
return field{}, err
|
||||
}
|
||||
|
|
@ -353,7 +367,7 @@ func (c *ConfigurationPerMetric) determineOutputDatatype(input string) (string,
|
|||
switch input {
|
||||
case "INT8L", "INT8H", "INT16", "INT32", "INT64":
|
||||
return "INT64", nil
|
||||
case "UINT8L", "UINT8H", "UINT16", "UINT32", "UINT64":
|
||||
case "BIT", "UINT8L", "UINT8H", "UINT16", "UINT32", "UINT64":
|
||||
return "UINT64", nil
|
||||
case "FLOAT16", "FLOAT32", "FLOAT64":
|
||||
return "FLOAT64", nil
|
||||
|
|
@ -366,7 +380,7 @@ func (c *ConfigurationPerMetric) determineOutputDatatype(input string) (string,
|
|||
func (c *ConfigurationPerMetric) determineFieldLength(input string, length uint16) (uint16, error) {
|
||||
// Handle our special types
|
||||
switch input {
|
||||
case "INT8L", "INT8H", "UINT8L", "UINT8H":
|
||||
case "BIT", "INT8L", "INT8H", "UINT8L", "UINT8H":
|
||||
return 1, nil
|
||||
case "INT16", "UINT16", "FLOAT16":
|
||||
return 1, nil
|
||||
|
|
|
|||
|
|
@ -255,13 +255,43 @@ func TestMetricResult(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
SlaveID: 1,
|
||||
Measurement: "bitvalues",
|
||||
Fields: []metricFieldDefinition{
|
||||
{
|
||||
Name: "bit 0",
|
||||
Address: uint16(1),
|
||||
InputType: "BIT",
|
||||
Bit: 0,
|
||||
},
|
||||
{
|
||||
Name: "bit 1",
|
||||
Address: uint16(1),
|
||||
InputType: "BIT",
|
||||
Bit: 1,
|
||||
},
|
||||
{
|
||||
Name: "bit 2",
|
||||
Address: uint16(1),
|
||||
InputType: "BIT",
|
||||
Bit: 2,
|
||||
},
|
||||
{
|
||||
Name: "bit 3",
|
||||
Address: uint16(1),
|
||||
InputType: "BIT",
|
||||
Bit: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
require.NoError(t, plugin.Init())
|
||||
|
||||
// Check the generated requests
|
||||
require.Len(t, plugin.requests, 1)
|
||||
require.NotNil(t, plugin.requests[1])
|
||||
require.Len(t, plugin.requests[1].holding, 1)
|
||||
require.Len(t, plugin.requests[1].holding, 5)
|
||||
require.Empty(t, plugin.requests[1].coil)
|
||||
require.Empty(t, plugin.requests[1].discrete)
|
||||
require.Empty(t, plugin.requests[1].input)
|
||||
|
|
@ -313,10 +343,25 @@ func TestMetricResult(t *testing.T) {
|
|||
map[string]interface{}{"pi": float64(3.1415927410125732421875)},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
metric.New(
|
||||
"bitvalues",
|
||||
map[string]string{
|
||||
"name": "FAKEMETER",
|
||||
"slave_id": "1",
|
||||
"type": "holding_register",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"bit 0": uint64(0),
|
||||
"bit 1": uint64(1),
|
||||
"bit 2": uint64(0),
|
||||
"bit 3": uint64(1),
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
}
|
||||
|
||||
actual := acc.GetTelegrafMetrics()
|
||||
testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime())
|
||||
testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime(), testutil.SortMetrics())
|
||||
}
|
||||
|
||||
func TestMetricAddressOverflow(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ type fieldDefinition struct {
|
|||
DataType string `toml:"data_type"`
|
||||
Scale float64 `toml:"scale"`
|
||||
Address []uint16 `toml:"address"`
|
||||
Bit uint8 `toml:"bit"`
|
||||
}
|
||||
|
||||
type ConfigurationOriginal struct {
|
||||
|
|
@ -172,7 +173,7 @@ func (c *ConfigurationOriginal) newFieldFromDefinition(def fieldDefinition, type
|
|||
return f, err
|
||||
}
|
||||
|
||||
f.converter, err = determineConverter(inType, byteOrder, outType, def.Scale, c.workarounds.StringRegisterLocation)
|
||||
f.converter, err = determineConverter(inType, byteOrder, outType, def.Scale, def.Bit, c.workarounds.StringRegisterLocation)
|
||||
if err != nil {
|
||||
return f, err
|
||||
}
|
||||
|
|
@ -213,7 +214,7 @@ func (c *ConfigurationOriginal) validateFieldDefinitions(fieldDefs []fieldDefini
|
|||
if item.Scale == 0.0 {
|
||||
return fmt.Errorf("invalid scale '%f' in %q - %q", item.Scale, registerType, item.Name)
|
||||
}
|
||||
case "STRING":
|
||||
case "BIT", "STRING":
|
||||
default:
|
||||
return fmt.Errorf("invalid data type %q in %q - %q", item.DataType, registerType, item.Name)
|
||||
}
|
||||
|
|
@ -226,42 +227,50 @@ func (c *ConfigurationOriginal) validateFieldDefinitions(fieldDefs []fieldDefini
|
|||
}
|
||||
}
|
||||
|
||||
// check address
|
||||
if item.DataType != "STRING" {
|
||||
if len(item.Address) != 1 && len(item.Address) != 2 && len(item.Address) != 4 {
|
||||
return fmt.Errorf("invalid address '%v' length '%v' in %q - %q", item.Address, len(item.Address), registerType, item.Name)
|
||||
// Special address checking for special types
|
||||
switch item.DataType {
|
||||
case "STRING":
|
||||
continue
|
||||
case "BIT":
|
||||
if len(item.Address) != 1 {
|
||||
return fmt.Errorf("address '%v' has length '%v' bit should be one in %q - %q", item.Address, len(item.Address), registerType, item.Name)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
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)
|
||||
// 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
|
||||
|
|
@ -308,6 +317,8 @@ func (c *ConfigurationOriginal) normalizeInputDatatype(dataType string, words in
|
|||
return "FLOAT64", nil
|
||||
case "STRING":
|
||||
return "STRING", nil
|
||||
case "BIT":
|
||||
return "BIT", nil
|
||||
}
|
||||
return normalizeInputDatatype(dataType)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -215,12 +215,33 @@ func TestRegisterHoldingRegisters(t *testing.T) {
|
|||
name string
|
||||
address []uint16
|
||||
quantity uint16
|
||||
bit uint8
|
||||
byteOrder string
|
||||
dataType string
|
||||
scale float64
|
||||
write []byte
|
||||
read interface{}
|
||||
}{
|
||||
{
|
||||
name: "register5_bit3",
|
||||
address: []uint16{5},
|
||||
quantity: 1,
|
||||
byteOrder: "AB",
|
||||
dataType: "BIT",
|
||||
bit: 3,
|
||||
write: []byte{0x18, 0x0d},
|
||||
read: uint8(1),
|
||||
},
|
||||
{
|
||||
name: "register5_bit14",
|
||||
address: []uint16{5},
|
||||
quantity: 1,
|
||||
byteOrder: "AB",
|
||||
dataType: "BIT",
|
||||
bit: 14,
|
||||
write: []byte{0x18, 0x0d},
|
||||
read: uint8(0),
|
||||
},
|
||||
{
|
||||
name: "register0_ab_float32",
|
||||
address: []uint16{0},
|
||||
|
|
@ -888,6 +909,7 @@ func TestRegisterHoldingRegisters(t *testing.T) {
|
|||
DataType: hrt.dataType,
|
||||
Scale: hrt.scale,
|
||||
Address: hrt.address,
|
||||
Bit: hrt.bit,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ type requestFieldDefinition struct {
|
|||
OutputType string `toml:"output"`
|
||||
Measurement string `toml:"measurement"`
|
||||
Omit bool `toml:"omit"`
|
||||
Bit uint8 `toml:"bit"`
|
||||
}
|
||||
|
||||
type requestDefinition struct {
|
||||
|
|
@ -135,6 +136,9 @@ func (c *ConfigurationPerRequest) Check() error {
|
|||
if f.Length != 0 {
|
||||
return fmt.Errorf("length option cannot be used for type %q of field %q", f.InputType, f.Name)
|
||||
}
|
||||
if f.Bit != 0 {
|
||||
return fmt.Errorf("bit option cannot be used for type %q of field %q", f.InputType, f.Name)
|
||||
}
|
||||
if f.OutputType == "STRING" {
|
||||
return fmt.Errorf("cannot output field %q as string", f.Name)
|
||||
}
|
||||
|
|
@ -142,12 +146,22 @@ func (c *ConfigurationPerRequest) Check() error {
|
|||
if f.Length < 1 {
|
||||
return fmt.Errorf("missing length for string field %q", f.Name)
|
||||
}
|
||||
if f.Bit != 0 {
|
||||
return fmt.Errorf("bit option cannot be used for type %q of field %q", f.InputType, f.Name)
|
||||
}
|
||||
if f.Scale != 0.0 {
|
||||
return fmt.Errorf("scale option cannot be used for string field %q", f.Name)
|
||||
}
|
||||
if f.OutputType != "" && f.OutputType != "STRING" {
|
||||
return fmt.Errorf("invalid output type %q for string field %q", f.OutputType, f.Name)
|
||||
}
|
||||
case "BIT":
|
||||
if f.Length != 0 {
|
||||
return fmt.Errorf("length option cannot be used for type %q of field %q", f.InputType, f.Name)
|
||||
}
|
||||
if f.OutputType == "STRING" {
|
||||
return fmt.Errorf("cannot output field %q as string", f.Name)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown register data-type %q for field %q", f.InputType, f.Name)
|
||||
}
|
||||
|
|
@ -361,7 +375,7 @@ func (c *ConfigurationPerRequest) newFieldFromDefinition(def requestFieldDefinit
|
|||
return field{}, err
|
||||
}
|
||||
|
||||
f.converter, err = determineConverter(inType, order, outType, def.Scale, c.workarounds.StringRegisterLocation)
|
||||
f.converter, err = determineConverter(inType, order, outType, def.Scale, def.Bit, c.workarounds.StringRegisterLocation)
|
||||
if err != nil {
|
||||
return field{}, err
|
||||
}
|
||||
|
|
@ -399,7 +413,7 @@ func (c *ConfigurationPerRequest) determineOutputDatatype(input string) (string,
|
|||
switch input {
|
||||
case "INT8L", "INT8H", "INT16", "INT32", "INT64":
|
||||
return "INT64", nil
|
||||
case "UINT8L", "UINT8H", "UINT16", "UINT32", "UINT64":
|
||||
case "BIT", "UINT8L", "UINT8H", "UINT16", "UINT32", "UINT64":
|
||||
return "UINT64", nil
|
||||
case "FLOAT16", "FLOAT32", "FLOAT64":
|
||||
return "FLOAT64", nil
|
||||
|
|
@ -412,7 +426,7 @@ func (c *ConfigurationPerRequest) determineOutputDatatype(input string) (string,
|
|||
func (c *ConfigurationPerRequest) determineFieldLength(input string, length uint16) (uint16, error) {
|
||||
// Handle our special types
|
||||
switch input {
|
||||
case "INT8L", "INT8H", "UINT8L", "UINT8H":
|
||||
case "BIT", "INT8L", "INT8H", "UINT8L", "UINT8H":
|
||||
return 1, nil
|
||||
case "INT16", "UINT16", "FLOAT16":
|
||||
return 1, nil
|
||||
|
|
|
|||
|
|
@ -458,6 +458,7 @@ func TestRequestTypesHoldingABCD(t *testing.T) {
|
|||
tests := []struct {
|
||||
name string
|
||||
address uint16
|
||||
bit uint8
|
||||
length uint16
|
||||
byteOrder string
|
||||
dataTypeIn string
|
||||
|
|
@ -466,6 +467,22 @@ func TestRequestTypesHoldingABCD(t *testing.T) {
|
|||
write []byte
|
||||
read interface{}
|
||||
}{
|
||||
{
|
||||
name: "register5_bit3",
|
||||
address: 5,
|
||||
dataTypeIn: "BIT",
|
||||
bit: 3,
|
||||
write: []byte{0x18, 0x0d},
|
||||
read: uint8(1),
|
||||
},
|
||||
{
|
||||
name: "register5_bit14",
|
||||
address: 5,
|
||||
dataTypeIn: "BIT",
|
||||
bit: 14,
|
||||
write: []byte{0x18, 0x0d},
|
||||
read: uint8(0),
|
||||
},
|
||||
{
|
||||
name: "register10_uint8L",
|
||||
address: 10,
|
||||
|
|
@ -1035,6 +1052,7 @@ func TestRequestTypesHoldingABCD(t *testing.T) {
|
|||
Scale: hrt.scale,
|
||||
Address: hrt.address,
|
||||
Length: hrt.length,
|
||||
Bit: hrt.bit,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -40,11 +40,13 @@
|
|||
## address - address of the register to query. For coil and discrete inputs this is the bit address.
|
||||
## name - field name
|
||||
## type *1 - type of the modbus field, can be
|
||||
## BIT (single bit of a register)
|
||||
## INT8L, INT8H, UINT8L, UINT8H (low and high byte variants)
|
||||
## INT16, UINT16, INT32, UINT32, INT64, UINT64 and
|
||||
## FLOAT16, FLOAT32, FLOAT64 (IEEE 754 binary representation)
|
||||
## STRING (byte-sequence converted to string)
|
||||
## length *1 - (optional) number of registers, ONLY valid for STRING type
|
||||
## bit *1,2 - (optional) bit of the register, ONLY valid for BIT type
|
||||
## scale *1,3 - (optional) factor to scale the variable with
|
||||
## output *2,3 - (optional) type of resulting field, can be INT64, UINT64 or FLOAT64. Defaults to FLOAT64 if
|
||||
## "scale" is provided and to the input "type" class otherwise (i.e. INT* -> INT64, etc).
|
||||
|
|
|
|||
|
|
@ -29,12 +29,14 @@
|
|||
## |---BA, DCBA - Little Endian
|
||||
## |---BADC - Mid-Big Endian
|
||||
## |---CDAB - Mid-Little Endian
|
||||
## data_type - INT8L, INT8H, UINT8L, UINT8H (low and high byte variants)
|
||||
## data_type - BIT (single bit of a register)
|
||||
## INT8L, INT8H, UINT8L, UINT8H (low and high byte variants)
|
||||
## INT16, UINT16, INT32, UINT32, INT64, UINT64,
|
||||
## FLOAT16-IEEE, FLOAT32-IEEE, FLOAT64-IEEE (IEEE 754 binary representation)
|
||||
## FIXED, UFIXED (fixed-point representation on input)
|
||||
## FLOAT32 is a deprecated alias for UFIXED for historic reasons, should be avoided
|
||||
## STRING (byte-sequence converted to string)
|
||||
## bit - (optional) bit of the register, ONLY valid for BIT type
|
||||
## scale - the final numeric variable representation
|
||||
## address - variable address
|
||||
|
||||
|
|
|
|||
|
|
@ -49,11 +49,13 @@
|
|||
## address - address of the register to query. For coil and discrete inputs this is the bit address.
|
||||
## name *1 - field name
|
||||
## type *1,2 - type of the modbus field, can be
|
||||
## BIT (single bit of a register)
|
||||
## INT8L, INT8H, UINT8L, UINT8H (low and high byte variants)
|
||||
## INT16, UINT16, INT32, UINT32, INT64, UINT64 and
|
||||
## FLOAT16, FLOAT32, FLOAT64 (IEEE 754 binary representation)
|
||||
## STRING (byte-sequence converted to string)
|
||||
## length *1,2 - (optional) number of registers, ONLY valid for STRING type
|
||||
## bit *1,2 - (optional) bit of the register, ONLY valid for BIT type
|
||||
## scale *1,2,4 - (optional) factor to scale the variable with
|
||||
## output *1,3,4 - (optional) type of resulting field, can be INT64, UINT64 or FLOAT64.
|
||||
## Defaults to FLOAT64 for numeric fields if "scale" is provided.
|
||||
|
|
|
|||
|
|
@ -18,8 +18,9 @@ func determineUntypedConverter(outType string) (fieldConverterFunc, error) {
|
|||
return nil, fmt.Errorf("invalid output data-type: %s", outType)
|
||||
}
|
||||
|
||||
func determineConverter(inType, byteOrder, outType string, scale float64, strloc string) (fieldConverterFunc, error) {
|
||||
if inType == "STRING" {
|
||||
func determineConverter(inType, byteOrder, outType string, scale float64, bit uint8, strloc string) (fieldConverterFunc, error) {
|
||||
switch inType {
|
||||
case "STRING":
|
||||
switch strloc {
|
||||
case "", "both":
|
||||
return determineConverterString(byteOrder)
|
||||
|
|
@ -28,6 +29,8 @@ func determineConverter(inType, byteOrder, outType string, scale float64, strloc
|
|||
case "upper":
|
||||
return determineConverterStringHigh(byteOrder)
|
||||
}
|
||||
case "BIT":
|
||||
return determineConverterBit(byteOrder, bit)
|
||||
}
|
||||
|
||||
if scale != 0.0 {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
package modbus
|
||||
|
||||
func determineConverterBit(byteOrder string, bit uint8) (fieldConverterFunc, error) {
|
||||
tohost, err := endiannessConverter16(byteOrder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return func(b []byte) interface{} {
|
||||
// Swap the bytes according to endianness
|
||||
v := tohost(b)
|
||||
return uint8(v >> bit & 0x01)
|
||||
}, nil
|
||||
}
|
||||
Loading…
Reference in New Issue