Modbus: reuse modbus decoder in aa55 (#30848)
This commit is contained in:
parent
18609ee874
commit
cae5e66085
9 changed files with 83 additions and 246 deletions
|
|
@ -8,10 +8,7 @@
|
|||
package aa55
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/grid-x/modbus"
|
||||
)
|
||||
|
|
@ -60,80 +57,3 @@ func modbusCRC16(data []byte) []byte {
|
|||
}
|
||||
return []byte{byte(crc & 0xFF), byte(crc >> 8)}
|
||||
}
|
||||
|
||||
// decodeMeta describes the properties of a supported decode type.
|
||||
type decodeMeta struct {
|
||||
size int
|
||||
}
|
||||
|
||||
func decodeMetadata(name string) (decodeMeta, bool) {
|
||||
switch name {
|
||||
case "float32be", "int32be", "uint32be", "uint32nan":
|
||||
return decodeMeta{size: 4}, true
|
||||
case "int16be", "uint16be":
|
||||
return decodeMeta{size: 2}, true
|
||||
default:
|
||||
return decodeMeta{}, false
|
||||
}
|
||||
}
|
||||
|
||||
// validateDecode returns an error if decode is not a supported type.
|
||||
func validateDecode(decode string) error {
|
||||
if _, ok := decodeMetadata(decode); !ok {
|
||||
return fmt.Errorf("unsupported decode %q (want int32be|uint32be|uint32nan|int16be|uint16be|float32be)", decode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeSize returns the number of bytes required to decode the given type.
|
||||
// Panics if decode type is unknown — callers must validateDecode first.
|
||||
func decodeSize(decode string) int {
|
||||
if info, ok := decodeMetadata(decode); ok {
|
||||
return info.size
|
||||
}
|
||||
panic(fmt.Sprintf("unknown decode type %q", decode))
|
||||
}
|
||||
|
||||
// decodeAt extracts a value at the given byte offset of payload and interprets
|
||||
// it according to decode.
|
||||
func decodeAt(payload []byte, offset int, decode string) (float64, error) {
|
||||
switch decode {
|
||||
case "float32be":
|
||||
if len(payload) < offset+4 {
|
||||
return 0, fmt.Errorf("payload too short for float32be at offset %d (len=%d)", offset, len(payload))
|
||||
}
|
||||
bits := binary.BigEndian.Uint32(payload[offset:])
|
||||
return float64(math.Float32frombits(bits)), nil
|
||||
case "int32be":
|
||||
if len(payload) < offset+4 {
|
||||
return 0, fmt.Errorf("payload too short for int32be at offset %d (len=%d)", offset, len(payload))
|
||||
}
|
||||
return float64(int32(binary.BigEndian.Uint32(payload[offset:]))), nil
|
||||
case "uint32be":
|
||||
if len(payload) < offset+4 {
|
||||
return 0, fmt.Errorf("payload too short for uint32be at offset %d (len=%d)", offset, len(payload))
|
||||
}
|
||||
return float64(binary.BigEndian.Uint32(payload[offset:])), nil
|
||||
case "uint32nan":
|
||||
// Like uint32be but treats 0xFFFFFFFF (not-connected sentinel) as 0.
|
||||
// Used for PV string power registers where disconnected strings report NaN.
|
||||
if len(payload) < offset+4 {
|
||||
return 0, fmt.Errorf("payload too short for uint32nan at offset %d (len=%d)", offset, len(payload))
|
||||
}
|
||||
if v := binary.BigEndian.Uint32(payload[offset:]); v != 0xFFFFFFFF {
|
||||
return float64(v), nil
|
||||
}
|
||||
return 0, nil
|
||||
case "int16be":
|
||||
if len(payload) < offset+2 {
|
||||
return 0, fmt.Errorf("payload too short for int16be at offset %d (len=%d)", offset, len(payload))
|
||||
}
|
||||
return float64(int16(binary.BigEndian.Uint16(payload[offset:]))), nil
|
||||
case "uint16be":
|
||||
if len(payload) < offset+2 {
|
||||
return 0, fmt.Errorf("payload too short for uint16be at offset %d (len=%d)", offset, len(payload))
|
||||
}
|
||||
return float64(binary.BigEndian.Uint16(payload[offset:])), nil
|
||||
}
|
||||
return 0, fmt.Errorf("unknown decode type: %s", decode)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
package aa55
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/evcc-io/evcc/util/modbus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
|
@ -108,107 +107,6 @@ func TestStripHeader_Short(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "short")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// decodeAt
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestDecodeAt_Int32BE_Positive(t *testing.T) {
|
||||
payload := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(payload, uint32(int32(1972)))
|
||||
v, err := decodeAt(payload, 0, "int32be")
|
||||
require.NoError(t, err)
|
||||
assert.InDelta(t, 1972.0, v, 0)
|
||||
}
|
||||
|
||||
func TestDecodeAt_Int32BE_Negative(t *testing.T) {
|
||||
payload := make([]byte, 4)
|
||||
v32 := int32(-2512)
|
||||
binary.BigEndian.PutUint32(payload, uint32(v32))
|
||||
v, err := decodeAt(payload, 0, "int32be")
|
||||
require.NoError(t, err)
|
||||
assert.InDelta(t, -2512.0, v, 0)
|
||||
}
|
||||
|
||||
func TestDecodeAt_Uint32BE(t *testing.T) {
|
||||
payload := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(payload, 43513)
|
||||
v, err := decodeAt(payload, 0, "uint32be")
|
||||
require.NoError(t, err)
|
||||
assert.InDelta(t, 43513.0, v, 0)
|
||||
}
|
||||
|
||||
func TestDecodeAt_Uint16BE(t *testing.T) {
|
||||
payload := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(payload, 68)
|
||||
v, err := decodeAt(payload, 0, "uint16be")
|
||||
require.NoError(t, err)
|
||||
assert.InDelta(t, 68.0, v, 0)
|
||||
}
|
||||
|
||||
func TestDecodeAt_Int16BE_Negative(t *testing.T) {
|
||||
payload := make([]byte, 2)
|
||||
v16 := int16(-300)
|
||||
binary.BigEndian.PutUint16(payload, uint16(v16))
|
||||
v, err := decodeAt(payload, 0, "int16be")
|
||||
require.NoError(t, err)
|
||||
assert.InDelta(t, -300.0, v, 0)
|
||||
}
|
||||
|
||||
func TestDecodeAt_Float32BE(t *testing.T) {
|
||||
payload := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(payload, math.Float32bits(123.456))
|
||||
v, err := decodeAt(payload, 0, "float32be")
|
||||
require.NoError(t, err)
|
||||
assert.InDelta(t, 123.456, v, 0.001)
|
||||
}
|
||||
|
||||
func TestDecodeAt_Uint32NAN_Normal(t *testing.T) {
|
||||
payload := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(payload, 8310) // e.g. 83.10 W string power
|
||||
v, err := decodeAt(payload, 0, "uint32nan")
|
||||
require.NoError(t, err)
|
||||
assert.InDelta(t, 8310.0, v, 0)
|
||||
}
|
||||
|
||||
func TestDecodeAt_Uint32NAN_Disconnected(t *testing.T) {
|
||||
payload := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(payload, 0xFFFFFFFF) // disconnected string sentinel
|
||||
v, err := decodeAt(payload, 0, "uint32nan")
|
||||
require.NoError(t, err)
|
||||
assert.InDelta(t, 0.0, v, 0) // must return 0, not 4.3GW
|
||||
}
|
||||
|
||||
func TestDecodeAt_TooShort(t *testing.T) {
|
||||
_, err := decodeAt([]byte{0x00}, 0, "int32be")
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestDecodeAt_UnknownType(t *testing.T) {
|
||||
_, err := decodeAt(make([]byte, 4), 0, "float32")
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// validateDecode / decodeSize
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestValidateDecode_OK(t *testing.T) {
|
||||
for _, d := range []string{"int32be", "uint32be", "uint32nan", "int16be", "uint16be", "float32be"} {
|
||||
assert.NoError(t, validateDecode(d), d)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateDecode_Reject(t *testing.T) {
|
||||
assert.Error(t, validateDecode("float32"))
|
||||
assert.Error(t, validateDecode(""))
|
||||
}
|
||||
|
||||
func TestDecodeSize(t *testing.T) {
|
||||
assert.Equal(t, 4, decodeSize("int32be"))
|
||||
assert.Equal(t, 4, decodeSize("uint32nan"))
|
||||
assert.Equal(t, 2, decodeSize("uint16be"))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// modbusCRC16
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -243,63 +141,63 @@ func TestModbusCRC16_KnownValue(t *testing.T) {
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestDT_Power_GW3000DNS30(t *testing.T) {
|
||||
assertBlockOffset(t, capGW3000DNS30, 54, "int32be", 1.0, 1972.0)
|
||||
assertBlockOffset(t, capGW3000DNS30, 54, "int32", 1.0, 1972.0)
|
||||
}
|
||||
|
||||
func TestDT_Power_GW17K(t *testing.T) {
|
||||
assertBlockOffset(t, capGW17kDT, 54, "int32be", 1.0, 12470.0)
|
||||
assertBlockOffset(t, capGW17kDT, 54, "int32", 1.0, 12470.0)
|
||||
}
|
||||
|
||||
func TestDT_Power_GW20KAU(t *testing.T) {
|
||||
assertBlockOffset(t, capGW20kAUDT, 54, "int32be", 1.0, 4957.0)
|
||||
assertBlockOffset(t, capGW20kAUDT, 54, "int32", 1.0, 4957.0)
|
||||
}
|
||||
|
||||
func TestDT_Energy_GW17K(t *testing.T) {
|
||||
assertBlockOffset(t, capGW17kDT, 90, "uint32be", 0.1, 29984.4)
|
||||
assertBlockOffset(t, capGW17kDT, 90, "uint32", 0.1, 29984.4)
|
||||
}
|
||||
|
||||
func TestDT_Energy_GW6000(t *testing.T) {
|
||||
assertBlockOffset(t, capGW6000DT, 90, "uint32be", 0.1, 13350.2)
|
||||
assertBlockOffset(t, capGW6000DT, 90, "uint32", 0.1, 13350.2)
|
||||
}
|
||||
|
||||
func TestDT_Energy_GW20KAU(t *testing.T) {
|
||||
assertBlockOffset(t, capGW20kAUDT, 90, "uint32be", 0.1, 4304.8)
|
||||
assertBlockOffset(t, capGW20kAUDT, 90, "uint32", 0.1, 4304.8)
|
||||
}
|
||||
|
||||
func TestET_PV_GW10K(t *testing.T) {
|
||||
assertBlockOffset(t, capGW10kET, 74, "int32be", 1.0, 831.0)
|
||||
assertBlockOffset(t, capGW10kET, 74, "int32", 1.0, 831.0)
|
||||
}
|
||||
|
||||
func TestET_Grid_GW10K_TinyExport(t *testing.T) {
|
||||
assertBlockOffset(t, capGW10kET, 78, "int32be", 1.0, -3.0)
|
||||
assertBlockOffset(t, capGW10kET, 78, "int32", 1.0, -3.0)
|
||||
}
|
||||
|
||||
func TestET_Grid_GW25K_Importing(t *testing.T) {
|
||||
assertBlockOffset(t, capGW25kET, 78, "int32be", 1.0, 1511.0)
|
||||
assertBlockOffset(t, capGW25kET, 78, "int32", 1.0, 1511.0)
|
||||
}
|
||||
|
||||
func TestET_Grid_GW29K9_Exporting(t *testing.T) {
|
||||
assertBlockOffset(t, capGW29k9ET, 78, "int32be", 1.0, -5403.0)
|
||||
assertBlockOffset(t, capGW29k9ET, 78, "int32", 1.0, -5403.0)
|
||||
}
|
||||
|
||||
func TestET_Battery_GW10K_Charging(t *testing.T) {
|
||||
assertBlockOffset(t, capGW10kET, 164, "int32be", 1.0, -2512.0)
|
||||
assertBlockOffset(t, capGW10kET, 164, "int32", 1.0, -2512.0)
|
||||
}
|
||||
|
||||
func TestET_Energy_GW10K(t *testing.T) {
|
||||
assertBlockOffset(t, capGW10kET, 182, "uint32be", 0.1, 6085.3)
|
||||
assertBlockOffset(t, capGW10kET, 182, "uint32", 0.1, 6085.3)
|
||||
}
|
||||
|
||||
func TestET_Energy_GW25K(t *testing.T) {
|
||||
assertBlockOffset(t, capGW25kET, 182, "uint32be", 0.1, 160.3)
|
||||
assertBlockOffset(t, capGW25kET, 182, "uint32", 0.1, 160.3)
|
||||
}
|
||||
|
||||
func TestET_SoC_GW10K(t *testing.T) {
|
||||
assertBlockOffset(t, capGW10kETBattery, 14, "uint16be", 1.0, 68.0)
|
||||
assertBlockOffset(t, capGW10kETBattery, 14, "uint16", 1.0, 68.0)
|
||||
}
|
||||
|
||||
func TestET_SoC_GW25K(t *testing.T) {
|
||||
assertBlockOffset(t, capGW25kETBattery, 14, "uint16be", 1.0, 100.0)
|
||||
assertBlockOffset(t, capGW25kETBattery, 14, "uint16", 1.0, 100.0)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -317,7 +215,11 @@ func assertBlockOffset(t *testing.T, capHex string, offset int, decode string, s
|
|||
t.Helper()
|
||||
payload, err := stripHeader(mustHex(t, capHex))
|
||||
require.NoError(t, err)
|
||||
v, err := decodeAt(payload, offset, decode)
|
||||
reg := modbus.Register{Type: "holding", Decode: decode}
|
||||
length, err := reg.Length()
|
||||
require.NoError(t, err)
|
||||
fn, err := reg.DecodeFunc()
|
||||
require.NoError(t, err)
|
||||
v := fn(payload[offset : offset+int(length)*2])
|
||||
assert.InDelta(t, expected, v*scale, 0.05)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,9 +31,10 @@ var cache = modbus.NewCache(cacheTTL)
|
|||
type AA55UDP struct {
|
||||
log *util.Logger
|
||||
conn *net.UDPConn
|
||||
pdu []byte // 6-byte PDU body, no CRC
|
||||
offset int // byte offset into the response payload (0 for register reads)
|
||||
decode string // int32be | uint32be | uint32nan | int16be | uint16be | float32be
|
||||
pdu []byte // 6-byte PDU body, no CRC
|
||||
offset int // byte offset into the response payload (0 for register reads)
|
||||
length int // value length in bytes
|
||||
decode func([]byte) float64 // modbus register decoder
|
||||
scale float64
|
||||
cacheKey string // precomputed cache key (remoteAddr/pdu); empty disables caching
|
||||
delay time.Duration // minimum gap between sends to the inverter (0 disables)
|
||||
|
|
@ -77,14 +78,18 @@ func buildReadConfig(id int, register, count uint16, block *modbus.Block) (readC
|
|||
return readConfig{pdu: buildPDU(byte(id), register, count), useCache: false}, nil
|
||||
}
|
||||
|
||||
// New constructs an AA55UDP from a high-level configuration. It validates
|
||||
// decode, resolves the read mode (register vs block), and wraps the conn.
|
||||
// The caller is responsible for dialling conn.
|
||||
func New(log *util.Logger, conn *net.UDPConn, id int, register, count uint16, block *modbus.Block, decode string, scale float64, delay time.Duration) (*AA55UDP, error) {
|
||||
if err := validateDecode(decode); err != nil {
|
||||
// New constructs an AA55UDP from a high-level configuration. The register count
|
||||
// and decoder are derived from reg; the read mode is resolved from block.
|
||||
func New(log *util.Logger, conn *net.UDPConn, id int, reg modbus.Register, block *modbus.Block, scale float64, delay time.Duration) (*AA55UDP, error) {
|
||||
count, err := reg.Length()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg, err := buildReadConfig(id, register, count, block)
|
||||
decode, err := reg.DecodeFunc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg, err := buildReadConfig(id, reg.Address, count, block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -92,6 +97,7 @@ func New(log *util.Logger, conn *net.UDPConn, id int, register, count uint16, bl
|
|||
log: log,
|
||||
conn: conn,
|
||||
decode: decode,
|
||||
length: int(count) * 2,
|
||||
scale: scale,
|
||||
delay: delay,
|
||||
pdu: cfg.pdu,
|
||||
|
|
@ -115,17 +121,12 @@ func (p *AA55UDP) query() (float64, error) {
|
|||
return 0, err
|
||||
}
|
||||
|
||||
minLen := p.offset + decodeSize(p.decode)
|
||||
if len(payload) < minLen {
|
||||
return 0, fmt.Errorf("payload too short (len=%d, need=%d)", len(payload), minLen)
|
||||
end := p.offset + p.length
|
||||
if len(payload) < end {
|
||||
return 0, fmt.Errorf("payload too short (len=%d, need=%d)", len(payload), end)
|
||||
}
|
||||
|
||||
v, err := decodeAt(payload, p.offset, p.decode)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return v * p.scale, nil
|
||||
return p.decode(payload[p.offset:end]) * p.scale, nil
|
||||
}
|
||||
|
||||
// fetch returns the response payload. In block-read mode the shared cache
|
||||
|
|
|
|||
|
|
@ -11,19 +11,30 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Wire-protocol primitives (buildPDU, stripHeader, DecodeAt, ModbusCRC16,
|
||||
// Cache, real-capture register tests) are covered in aa55_test.go.
|
||||
// Wire-protocol primitives are covered in aa55_test.go.
|
||||
// This file covers the AA55UDP plugin adapter end-to-end via FloatGetter.
|
||||
|
||||
func decodeFor(t *testing.T, name string) (func([]byte) float64, int) {
|
||||
t.Helper()
|
||||
reg := modbus.Register{Type: "holding", Decode: name}
|
||||
length, err := reg.Length()
|
||||
require.NoError(t, err)
|
||||
fn, err := reg.DecodeFunc()
|
||||
require.NoError(t, err)
|
||||
return fn, int(length) * 2
|
||||
}
|
||||
|
||||
// TestFloatGetter_DT_Power verifies the full query/decode pipeline using the
|
||||
// GW17K-DT real capture sliced to just the power register bytes: 12470 W.
|
||||
func TestFloatGetter_DT_Power(t *testing.T) {
|
||||
response := singleRegResponse(t, capGW17kDT, 54, 4, 0x7f)
|
||||
dec, n := decodeFor(t, "int32")
|
||||
p := &AA55UDP{
|
||||
log: util.NewLogger("test"),
|
||||
conn: mockConn(t, response),
|
||||
pdu: buildPDU(0x7F, 0x75AF, 2),
|
||||
decode: "int32be",
|
||||
decode: dec,
|
||||
length: n,
|
||||
scale: 1.0,
|
||||
}
|
||||
getter, err := p.FloatGetter()
|
||||
|
|
@ -36,11 +47,13 @@ func TestFloatGetter_DT_Power(t *testing.T) {
|
|||
// TestFloatGetter_DT_Energy verifies scale 0.1: 299844 × 0.1 = 29984.4 kWh.
|
||||
func TestFloatGetter_DT_Energy(t *testing.T) {
|
||||
response := singleRegResponse(t, capGW17kDT, 90, 4, 0x7f)
|
||||
dec, n := decodeFor(t, "uint32")
|
||||
p := &AA55UDP{
|
||||
log: util.NewLogger("test"),
|
||||
conn: mockConn(t, response),
|
||||
pdu: buildPDU(0x7F, 0x75C1, 2),
|
||||
decode: "uint32be",
|
||||
decode: dec,
|
||||
length: n,
|
||||
scale: 0.1,
|
||||
}
|
||||
getter, err := p.FloatGetter()
|
||||
|
|
@ -53,11 +66,13 @@ func TestFloatGetter_DT_Energy(t *testing.T) {
|
|||
// TestFloatGetter_ET_PV verifies ET pv power: GW10K-ET = 831 W.
|
||||
func TestFloatGetter_ET_PV(t *testing.T) {
|
||||
response := singleRegResponse(t, capGW10kET, 74, 4, 0xf7)
|
||||
dec, n := decodeFor(t, "int32")
|
||||
p := &AA55UDP{
|
||||
log: util.NewLogger("test"),
|
||||
conn: mockConn(t, response),
|
||||
pdu: buildPDU(0xF7, 0x8941, 2),
|
||||
decode: "int32be",
|
||||
decode: dec,
|
||||
length: n,
|
||||
scale: 1.0,
|
||||
}
|
||||
getter, err := p.FloatGetter()
|
||||
|
|
@ -70,11 +85,13 @@ func TestFloatGetter_ET_PV(t *testing.T) {
|
|||
// TestFloatGetter_ET_Battery verifies charging battery: GW10K-ET = -2512 W.
|
||||
func TestFloatGetter_ET_Battery(t *testing.T) {
|
||||
response := singleRegResponse(t, capGW10kET, 164, 4, 0xf7)
|
||||
dec, n := decodeFor(t, "int32")
|
||||
p := &AA55UDP{
|
||||
log: util.NewLogger("test"),
|
||||
conn: mockConn(t, response),
|
||||
pdu: buildPDU(0xF7, 0x896E, 2),
|
||||
decode: "int32be",
|
||||
decode: dec,
|
||||
length: n,
|
||||
scale: 1.0,
|
||||
}
|
||||
getter, err := p.FloatGetter()
|
||||
|
|
@ -87,11 +104,13 @@ func TestFloatGetter_ET_Battery(t *testing.T) {
|
|||
// TestFloatGetter_ET_SoC verifies SoC: GW10K-ET = 68%.
|
||||
func TestFloatGetter_ET_SoC(t *testing.T) {
|
||||
response := singleRegResponse(t, capGW10kETBattery, 14, 2, 0xf7)
|
||||
dec, n := decodeFor(t, "uint16")
|
||||
p := &AA55UDP{
|
||||
log: util.NewLogger("test"),
|
||||
conn: mockConn(t, response),
|
||||
pdu: buildPDU(0xF7, 0x908F, 1),
|
||||
decode: "uint16be",
|
||||
decode: dec,
|
||||
length: n,
|
||||
scale: 1.0,
|
||||
}
|
||||
getter, err := p.FloatGetter()
|
||||
|
|
|
|||
|
|
@ -60,11 +60,6 @@ func NewAA55UDPFromConfig(ctx context.Context, other map[string]any) (Plugin, er
|
|||
return nil, err
|
||||
}
|
||||
|
||||
count, err := cc.Register.Length()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
raddr, err := net.ResolveUDPAddr("udp4", net.JoinHostPort(cc.Host, "8899"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -76,7 +71,7 @@ func NewAA55UDPFromConfig(ctx context.Context, other map[string]any) (Plugin, er
|
|||
return nil, err
|
||||
}
|
||||
|
||||
res, err := aa55.New(util.NewLogger("aa55udp"), conn, cc.Id, cc.Register.Address, count, cc.Block, cc.Register.Decode, cc.Scale, cc.Delay)
|
||||
res, err := aa55.New(util.NewLogger("aa55udp"), conn, cc.Id, cc.Register, cc.Block, cc.Scale, cc.Delay)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("aa55udp: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ import (
|
|||
)
|
||||
|
||||
// TestAA55UDPFromConfig_Block verifies the modbus.Register and nested block map
|
||||
// decode, with the count (2 registers) derived from the int32be decode width.
|
||||
// decode, with the count (2 registers) derived from the int32 decode width.
|
||||
func TestAA55UDPFromConfig_Block(t *testing.T) {
|
||||
_, err := NewAA55UDPFromConfig(context.Background(), map[string]any{
|
||||
"host": "127.0.0.1",
|
||||
"id": 247,
|
||||
"register": map[string]any{"address": 35139, "decode": "int32be"},
|
||||
"register": map[string]any{"address": 35139, "decode": "int32"},
|
||||
"block": map[string]any{"register": 35100, "count": 125},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
|
@ -25,7 +25,7 @@ func TestAA55UDPFromConfig_BlockRejectsOutOfRange(t *testing.T) {
|
|||
_, err := NewAA55UDPFromConfig(context.Background(), map[string]any{
|
||||
"host": "127.0.0.1",
|
||||
"id": 247,
|
||||
"register": map[string]any{"address": 36017, "decode": "float32be"}, // outside READ 125 @ 35100
|
||||
"register": map[string]any{"address": 36017, "decode": "float32"}, // outside READ 125 @ 35100
|
||||
"block": map[string]any{"register": 35100, "count": 125},
|
||||
})
|
||||
require.Error(t, err)
|
||||
|
|
@ -35,7 +35,7 @@ func TestAA55UDPFromConfig_BlockRejectsOutOfRange(t *testing.T) {
|
|||
func TestAA55UDPFromConfig_Register(t *testing.T) {
|
||||
_, err := NewAA55UDPFromConfig(context.Background(), map[string]any{
|
||||
"host": "127.0.0.1",
|
||||
"register": map[string]any{"address": 30127, "decode": "int32be"},
|
||||
"register": map[string]any{"address": 30127, "decode": "int32"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue