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/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/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/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/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/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) - 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/vmware/govmomi v0.28.1-0.20220921224932-b4b508abf208
github.com/wavefronthq/wavefront-sdk-go v0.10.4 github.com/wavefronthq/wavefront-sdk-go v0.10.4
github.com/wvanbergen/kafka v0.0.0-20171203153745-e2edea948ddf 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/xdg/scram v1.0.5
github.com/yuin/goldmark v1.5.3 github.com/yuin/goldmark v1.5.3
go.mongodb.org/mongo-driver v1.11.0 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/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 h1:ILoU84rj4AQ3q6cjQvtb9jBjx4xzR/Riq/zYhmDQiOk=
github.com/wvanbergen/kazoo-go v0.0.0-20180202103751-f72d8611297a/go.mod h1:vQQATAGxVK20DC1rRubTJbZDDhhpA4QfU02pMdPxGO4= 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 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= 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= 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 ## |---CDAB - Mid-Little Endian
## data_type - INT8L, INT8H, UINT8L, UINT8H (low and high byte variants) ## data_type - INT8L, INT8H, UINT8L, UINT8H (low and high byte variants)
## INT16, UINT16, INT32, UINT32, INT64, UINT64, ## 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) ## FLOAT32, FIXED, UFIXED (fixed-point representation on input)
## scale - the final numeric variable representation ## scale - the final numeric variable representation
## address - variable address ## address - variable address
@ -172,7 +172,7 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
## type *1,2 - type of the modbus field, can be ## type *1,2 - type of the modbus field, can be
## INT8L, INT8H, UINT8L, UINT8H (low and high byte variants) ## INT8L, INT8H, UINT8L, UINT8H (low and high byte variants)
## INT16, UINT16, INT32, UINT32, INT64, UINT64 and ## 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 ## 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,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). ## "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 These types are used for integer input values. Select the one that matches your
modbus data source. 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 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. 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 set to `INT8L`, `INT8H`, `UINT8L`, `UINT8H` where `L` is the lower byte of the
register and `H` is the higher byte. register and `H` is the higher byte.
Furthermore, the types `INT16`, `UINT16`, `INT32`, `UINT32`, `INT64` or `UINT64` Furthermore, the types `INT16`, `UINT16`, `INT32`, `UINT32`, `INT64` or `UINT64`
for integer types or `FLOAT32` and `FLOAT64` for IEEE 754 binary representations for integer types or `FLOAT16`, `FLOAT32` and `FLOAT64` for IEEE 754 binary
of floating point values exist. Usually the datatype of the register is listed representations of floating point values exist. `FLOAT16` denotes a
in the datasheet of your modbus device in relation to the `address` described half-precision float with a 16-bit representation.
above. 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 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 `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 { switch dataType {
case "INT8L", "INT8H", "UINT8L", "UINT8H", case "INT8L", "INT8H", "UINT8L", "UINT8H",
"INT16", "UINT16", "INT32", "UINT32", "INT64", "UINT64", "INT16", "UINT16", "INT32", "UINT32", "INT64", "UINT64",
"FLOAT32", "FLOAT64": "FLOAT16", "FLOAT32", "FLOAT64":
return dataType, nil return dataType, nil
} }
return "unknown", fmt.Errorf("unknown input type %q", dataType) return "unknown", fmt.Errorf("unknown input type %q", dataType)

View File

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

View File

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

View File

@ -819,6 +819,26 @@ func TestHoldingRegisters(t *testing.T) {
write: []byte{0x8F, 0x55, 0xC3, 0x47, 0x6A, 0x40, 0xBF, 0x9C}, write: []byte{0x8F, 0x55, 0xC3, 0x47, 0x6A, 0x40, 0xBF, 0x9C},
read: float64(-0.02774907295123737), 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() serv := mbserver.NewServer()
@ -1381,6 +1401,37 @@ func TestRequestTypesHoldingABCD(t *testing.T) {
write: []byte{0x40, 0x09, 0x21, 0xfb, 0x54, 0x44, 0x2e, 0xea}, write: []byte{0x40, 0x09, 0x21, 0xfb, 0x54, 0x44, 0x2e, 0xea},
read: float64(3.14159265359000006156975359772), 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() serv := mbserver.NewServer()
@ -1951,6 +2002,37 @@ func TestRequestTypesHoldingDCBA(t *testing.T) {
write: []byte{0x40, 0x09, 0x21, 0xfb, 0x54, 0x44, 0x2e, 0xea}, write: []byte{0x40, 0x09, 0x21, 0xfb, 0x54, 0x44, 0x2e, 0xea},
read: float64(3.14159265359000006156975359772), 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() serv := mbserver.NewServer()
@ -1973,7 +2055,7 @@ func TestRequestTypesHoldingDCBA(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
modbus := Modbus{ modbus := Modbus{
Name: "TestRequestTypesHoldingABCD", Name: "TestRequestTypesHoldingDCBA",
Controller: "tcp://localhost:1502", Controller: "tcp://localhost:1502",
ConfigurationType: "request", ConfigurationType: "request",
Log: testutil.Logger{}, Log: testutil.Logger{},

View File

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

View File

@ -55,7 +55,7 @@
## type *1,2 - type of the modbus field, can be ## type *1,2 - type of the modbus field, can be
## INT8L, INT8H, UINT8L, UINT8H (low and high byte variants) ## INT8L, INT8H, UINT8L, UINT8H (low and high byte variants)
## INT16, UINT16, INT32, UINT32, INT64, UINT64 and ## 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 ## 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,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). ## "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) return determineConverterI64Scale(outType, byteOrder, scale)
case "UINT64": case "UINT64":
return determineConverterU64Scale(outType, byteOrder, scale) return determineConverterU64Scale(outType, byteOrder, scale)
case "FLOAT16":
return determineConverterF16Scale(outType, byteOrder, scale)
case "FLOAT32": case "FLOAT32":
return determineConverterF32Scale(outType, byteOrder, scale) return determineConverterF32Scale(outType, byteOrder, scale)
case "FLOAT64": case "FLOAT64":
@ -61,6 +63,8 @@ func determineConverterNoScale(inType, byteOrder, outType string) (fieldConverte
return determineConverterI64(outType, byteOrder) return determineConverterI64(outType, byteOrder)
case "UINT64": case "UINT64":
return determineConverterU64(outType, byteOrder) return determineConverterU64(outType, byteOrder)
case "FLOAT16":
return determineConverterF16(outType, byteOrder)
case "FLOAT32": case "FLOAT32":
return determineConverterF32(outType, byteOrder) return determineConverterF32(outType, byteOrder)
case "FLOAT64": case "FLOAT64":

View File

@ -3,6 +3,8 @@ package modbus
import ( import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"github.com/x448/float16"
) )
type convert16 func([]byte) uint16 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) 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 // I16 - scale
func determineConverterI16Scale(outType, byteOrder string, scale float64) (fieldConverterFunc, error) { func determineConverterI16Scale(outType, byteOrder string, scale float64) (fieldConverterFunc, error) {
tohost, err := endianessConverter16(byteOrder) 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) 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)
}