feat(inputs.modbus): add support for half-precision float (float16) (#12340)

This commit is contained in:
Sven Rebhan 2022-12-07 15:20:51 +01:00 committed by GitHub
parent da0c186a71
commit 5cb40a1882
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 160 additions and 18 deletions

View File

@ -302,6 +302,7 @@ following works:
- github.com/wavefronthq/wavefront-sdk-go [Apache License 2.0](https://github.com/wavefrontHQ/wavefront-sdk-go/blob/master/LICENSE)
- github.com/wvanbergen/kafka [MIT License](https://github.com/wvanbergen/kafka/blob/master/LICENSE)
- github.com/wvanbergen/kazoo-go [MIT License](https://github.com/wvanbergen/kazoo-go/blob/master/MIT-LICENSE)
- github.com/x448/float16 [MIT License](https://github.com/x448/float16/blob/master/LICENSE)
- github.com/xdg-go/pbkdf2 [Apache License 2.0](https://github.com/xdg-go/pbkdf2/blob/main/LICENSE)
- github.com/xdg-go/scram [Apache License 2.0](https://github.com/xdg-go/scram/blob/master/LICENSE)
- github.com/xdg-go/stringprep [Apache License 2.0](https://github.com/xdg-go/stringprep/blob/master/LICENSE)

1
go.mod
View File

@ -160,6 +160,7 @@ require (
github.com/vmware/govmomi v0.28.1-0.20220921224932-b4b508abf208
github.com/wavefronthq/wavefront-sdk-go v0.10.4
github.com/wvanbergen/kafka v0.0.0-20171203153745-e2edea948ddf
github.com/x448/float16 v0.8.4
github.com/xdg/scram v1.0.5
github.com/yuin/goldmark v1.5.3
go.mongodb.org/mongo-driver v1.11.0

2
go.sum
View File

@ -2547,6 +2547,8 @@ github.com/wvanbergen/kafka v0.0.0-20171203153745-e2edea948ddf h1:TOV5PC6fIWwFOF
github.com/wvanbergen/kafka v0.0.0-20171203153745-e2edea948ddf/go.mod h1:nxx7XRXbR9ykhnC8lXqQyJS0rfvJGxKyKw/sT1YOttg=
github.com/wvanbergen/kazoo-go v0.0.0-20180202103751-f72d8611297a h1:ILoU84rj4AQ3q6cjQvtb9jBjx4xzR/Riq/zYhmDQiOk=
github.com/wvanbergen/kazoo-go v0.0.0-20180202103751-f72d8611297a/go.mod h1:vQQATAGxVK20DC1rRubTJbZDDhhpA4QfU02pMdPxGO4=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=

View File

@ -96,7 +96,7 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
## |---CDAB - Mid-Little Endian
## data_type - INT8L, INT8H, UINT8L, UINT8H (low and high byte variants)
## INT16, UINT16, INT32, UINT32, INT64, UINT64,
## FLOAT32-IEEE, FLOAT64-IEEE (the IEEE 754 binary representation)
## FLOAT16-IEEE, FLOAT32-IEEE, FLOAT64-IEEE (IEEE 754 binary representation)
## FLOAT32, FIXED, UFIXED (fixed-point representation on input)
## scale - the final numeric variable representation
## address - variable address
@ -158,10 +158,10 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
## fields as the optimisation will add such field only where needed.
# optimization = "none"
## Maximum number register the optimizer is allowed to insert between two fields to
## Maximum number register the optimizer is allowed to insert between two fields to
## save requests.
## This option is only used for the 'max_insert' optimization strategy.
## NOTE: All omitted fields are ignored, so this option denotes the effective hole
## NOTE: All omitted fields are ignored, so this option denotes the effective hole
## size to fill.
# optimization_max_register_fill = 50
@ -172,7 +172,7 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
## type *1,2 - type of the modbus field, can be
## INT8L, INT8H, UINT8L, UINT8H (low and high byte variants)
## INT16, UINT16, INT32, UINT32, INT64, UINT64 and
## FLOAT32, FLOAT64 (IEEE 754 binary representation)
## 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
## "scale" is provided and to the input "type" class otherwise (i.e. INT* -> INT64, etc).
@ -307,7 +307,7 @@ the register respectively.
These types are used for integer input values. Select the one that matches your
modbus data source.
##### Floating Point: `FLOAT32-IEEE`, `FLOAT64-IEEE`
##### Floating Point: `FLOAT16-IEEE`, `FLOAT32-IEEE`, `FLOAT64-IEEE`
Use these types if your modbus registers contain a value that is encoded in this
format. These types always include the sign, therefore no variant exists.
@ -455,10 +455,11 @@ The `type` setting specifies the datatype of the modbus register and can be
set to `INT8L`, `INT8H`, `UINT8L`, `UINT8H` where `L` is the lower byte of the
register and `H` is the higher byte.
Furthermore, the types `INT16`, `UINT16`, `INT32`, `UINT32`, `INT64` or `UINT64`
for integer types or `FLOAT32` and `FLOAT64` for IEEE 754 binary representations
of floating point values exist. Usually the datatype of the register is listed
in the datasheet of your modbus device in relation to the `address` described
above.
for integer types or `FLOAT16`, `FLOAT32` and `FLOAT64` for IEEE 754 binary
representations of floating point values exist. `FLOAT16` denotes a
half-precision float with a 16-bit representation.
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

View File

@ -33,7 +33,7 @@ func normalizeInputDatatype(dataType string) (string, error) {
switch dataType {
case "INT8L", "INT8H", "UINT8L", "UINT8H",
"INT16", "UINT16", "INT32", "UINT32", "INT64", "UINT64",
"FLOAT32", "FLOAT64":
"FLOAT16", "FLOAT32", "FLOAT64":
return dataType, nil
}
return "unknown", fmt.Errorf("unknown input type %q", dataType)

View File

@ -179,7 +179,7 @@ func (c *ConfigurationOriginal) validateFieldDefinitions(fieldDefs []fieldDefini
switch item.DataType {
case "INT8L", "INT8H", "UINT8L", "UINT8H",
"UINT16", "INT16", "UINT32", "INT32", "UINT64", "INT64",
"FLOAT32-IEEE", "FLOAT64-IEEE", "FLOAT32", "FIXED", "UFIXED":
"FLOAT16-IEEE", "FLOAT32-IEEE", "FLOAT64-IEEE", "FLOAT32", "FIXED", "UFIXED":
default:
return fmt.Errorf("invalid data type '%s' in '%s' - '%s'", item.DataType, registerType, item.Name)
}
@ -236,6 +236,8 @@ func (c *ConfigurationOriginal) normalizeInputDatatype(dataType string, words in
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":

View File

@ -354,7 +354,7 @@ func (c *ConfigurationPerRequest) determineOutputDatatype(input string) (string,
return "INT64", nil
case "UINT8L", "UINT8H", "UINT16", "UINT32", "UINT64":
return "UINT64", nil
case "FLOAT32", "FLOAT64":
case "FLOAT16", "FLOAT32", "FLOAT64":
return "FLOAT64", nil
}
return "unknown", fmt.Errorf("invalid input datatype %q for determining output", input)
@ -365,7 +365,7 @@ func (c *ConfigurationPerRequest) determineFieldLength(input string) (uint16, er
switch input {
case "INT8L", "INT8H", "UINT8L", "UINT8H":
return 1, nil
case "INT16", "UINT16":
case "INT16", "UINT16", "FLOAT16":
return 1, nil
case "INT32", "UINT32", "FLOAT32":
return 2, nil

View File

@ -819,6 +819,26 @@ func TestHoldingRegisters(t *testing.T) {
write: []byte{0x8F, 0x55, 0xC3, 0x47, 0x6A, 0x40, 0xBF, 0x9C},
read: float64(-0.02774907295123737),
},
{
name: "register240_abcd_float16",
address: []uint16{240},
quantity: 1,
byteOrder: "AB",
dataType: "FLOAT16-IEEE",
scale: 1,
write: []byte{0xb8, 0x14},
read: float64(-0.509765625),
},
{
name: "register240_dcba_float16",
address: []uint16{240},
quantity: 1,
byteOrder: "BA",
dataType: "FLOAT16-IEEE",
scale: 1,
write: []byte{0x14, 0xb8},
read: float64(-0.509765625),
},
}
serv := mbserver.NewServer()
@ -1381,6 +1401,37 @@ func TestRequestTypesHoldingABCD(t *testing.T) {
write: []byte{0x40, 0x09, 0x21, 0xfb, 0x54, 0x44, 0x2e, 0xea},
read: float64(3.14159265359000006156975359772),
},
{
name: "register100_float16",
address: 100,
dataTypeIn: "FLOAT16",
write: []byte{0xb8, 0x14},
read: float64(-0.509765625),
},
{
name: "register100_float16-scale_.1",
address: 100,
dataTypeIn: "FLOAT16",
scale: .1,
write: []byte{0xb8, 0x14},
read: float64(-0.0509765625),
},
{
name: "register100_float16_scale_10",
address: 100,
dataTypeIn: "FLOAT16",
scale: 10,
write: []byte{0xb8, 0x14},
read: float64(-5.09765625),
},
{
name: "register100_float16_float64_scale",
address: 100,
dataTypeIn: "FLOAT16",
scale: 1.0,
write: []byte{0xb8, 0x14},
read: float64(-0.509765625),
},
}
serv := mbserver.NewServer()
@ -1951,6 +2002,37 @@ func TestRequestTypesHoldingDCBA(t *testing.T) {
write: []byte{0x40, 0x09, 0x21, 0xfb, 0x54, 0x44, 0x2e, 0xea},
read: float64(3.14159265359000006156975359772),
},
{
name: "register100_float16",
address: 100,
dataTypeIn: "FLOAT16",
write: []byte{0xb8, 0x14},
read: float64(-0.509765625),
},
{
name: "register100_float16-scale_.1",
address: 100,
dataTypeIn: "FLOAT16",
scale: .1,
write: []byte{0xb8, 0x14},
read: float64(-0.0509765625),
},
{
name: "register100_float16_scale_10",
address: 100,
dataTypeIn: "FLOAT16",
scale: 10,
write: []byte{0xb8, 0x14},
read: float64(-5.09765625),
},
{
name: "register100_float16_float64_scale",
address: 100,
dataTypeIn: "FLOAT16",
scale: 1.0,
write: []byte{0xb8, 0x14},
read: float64(-0.509765625),
},
}
serv := mbserver.NewServer()
@ -1973,7 +2055,7 @@ func TestRequestTypesHoldingDCBA(t *testing.T) {
require.NoError(t, err)
modbus := Modbus{
Name: "TestRequestTypesHoldingABCD",
Name: "TestRequestTypesHoldingDCBA",
Controller: "tcp://localhost:1502",
ConfigurationType: "request",
Log: testutil.Logger{},

View File

@ -30,7 +30,7 @@
## |---CDAB - Mid-Little Endian
## data_type - INT8L, INT8H, UINT8L, UINT8H (low and high byte variants)
## INT16, UINT16, INT32, UINT32, INT64, UINT64,
## FLOAT32-IEEE, FLOAT64-IEEE (the IEEE 754 binary representation)
## FLOAT16-IEEE, FLOAT32-IEEE, FLOAT64-IEEE (IEEE 754 binary representation)
## FLOAT32, FIXED, UFIXED (fixed-point representation on input)
## scale - the final numeric variable representation
## address - variable address

View File

@ -41,10 +41,10 @@
## fields as the optimisation will add such field only where needed.
# optimization = "none"
## Maximum number register the optimizer is allowed to insert between two fields to
## Maximum number register the optimizer is allowed to insert between two fields to
## save requests.
## This option is only used for the 'max_insert' optimization strategy.
## NOTE: All omitted fields are ignored, so this option denotes the effective hole
## NOTE: All omitted fields are ignored, so this option denotes the effective hole
## size to fill.
# optimization_max_register_fill = 50
@ -55,7 +55,7 @@
## type *1,2 - type of the modbus field, can be
## INT8L, INT8H, UINT8L, UINT8H (low and high byte variants)
## INT16, UINT16, INT32, UINT32, INT64, UINT64 and
## FLOAT32, FLOAT64 (IEEE 754 binary representation)
## 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
## "scale" is provided and to the input "type" class otherwise (i.e. INT* -> INT64, etc).

View File

@ -31,6 +31,8 @@ func determineConverterScale(inType, byteOrder, outType string, scale float64) (
return determineConverterI64Scale(outType, byteOrder, scale)
case "UINT64":
return determineConverterU64Scale(outType, byteOrder, scale)
case "FLOAT16":
return determineConverterF16Scale(outType, byteOrder, scale)
case "FLOAT32":
return determineConverterF32Scale(outType, byteOrder, scale)
case "FLOAT64":
@ -61,6 +63,8 @@ func determineConverterNoScale(inType, byteOrder, outType string) (fieldConverte
return determineConverterI64(outType, byteOrder)
case "UINT64":
return determineConverterU64(outType, byteOrder)
case "FLOAT16":
return determineConverterF16(outType, byteOrder)
case "FLOAT32":
return determineConverterF32(outType, byteOrder)
case "FLOAT64":

View File

@ -3,6 +3,8 @@ package modbus
import (
"encoding/binary"
"fmt"
"github.com/x448/float16"
)
type convert16 func([]byte) uint16
@ -73,6 +75,29 @@ func determineConverterU16(outType, byteOrder string) (fieldConverterFunc, error
return nil, fmt.Errorf("invalid output data-type: %s", outType)
}
// F16 - no scale
func determineConverterF16(outType, byteOrder string) (fieldConverterFunc, error) {
tohost, err := endianessConverter16(byteOrder)
if err != nil {
return nil, err
}
switch outType {
case "native":
return func(b []byte) interface{} {
raw := tohost(b)
return float16.Frombits(raw).Float32()
}, nil
case "FLOAT64":
return func(b []byte) interface{} {
raw := tohost(b)
in := float16.Frombits(raw).Float32()
return float64(in)
}, nil
}
return nil, fmt.Errorf("invalid output data-type: %s", outType)
}
// I16 - scale
func determineConverterI16Scale(outType, byteOrder string, scale float64) (fieldConverterFunc, error) {
tohost, err := endianessConverter16(byteOrder)
@ -136,3 +161,27 @@ func determineConverterU16Scale(outType, byteOrder string, scale float64) (field
}
return nil, fmt.Errorf("invalid output data-type: %s", outType)
}
// F16 - scale
func determineConverterF16Scale(outType, byteOrder string, scale float64) (fieldConverterFunc, error) {
tohost, err := endianessConverter16(byteOrder)
if err != nil {
return nil, err
}
switch outType {
case "native":
return func(b []byte) interface{} {
raw := tohost(b)
in := float16.Frombits(raw)
return in.Float32() * float32(scale)
}, nil
case "FLOAT64":
return func(b []byte) interface{} {
raw := tohost(b)
in := float16.Frombits(raw)
return float64(in.Float32()) * scale
}, nil
}
return nil, fmt.Errorf("invalid output data-type: %s", outType)
}