Files
uopi/internal/datasource/modbus/decode_test.go
T
2026-06-24 06:12:52 +02:00

116 lines
3.3 KiB
Go

package modbus
import (
"math"
"testing"
"github.com/uopi/uopi/internal/datasource"
)
func TestDecodeEncodings(t *testing.T) {
cases := []struct {
name string
reg Register
words []uint16
want any
}{
{"uint16", Register{Encoding: "uint16"}, []uint16{42}, int64(42)},
{"int16 neg", Register{Encoding: "int16"}, []uint16{0xFFFF}, int64(-1)},
{"uint32", Register{Encoding: "uint32"}, []uint16{0x0001, 0x0000}, int64(65536)},
{"int32 neg", Register{Encoding: "int32"}, []uint16{0xFFFF, 0xFFFF}, int64(-1)},
{"scaled", Register{Encoding: "int16", Scale: 0.1}, []uint16{235}, 23.5},
{"float32", Register{Encoding: "float32"}, f32Words(3.5), 3.5},
{"float64", Register{Encoding: "float64"}, f64Words(2.25), 2.25},
}
for _, tc := range cases {
got, err := tc.reg.decode(tc.words)
if err != nil {
t.Errorf("%s: decode error %v", tc.name, err)
continue
}
switch w := tc.want.(type) {
case int64:
if got != w {
t.Errorf("%s: got %v (%T), want %v", tc.name, got, got, w)
}
case float64:
gf, ok := got.(float64)
if !ok || math.Abs(gf-w) > 1e-6 {
t.Errorf("%s: got %v (%T), want %v", tc.name, got, got, w)
}
}
}
}
func TestWordOrder(t *testing.T) {
big := Register{Encoding: "uint32", WordOrder: "big"}
little := Register{Encoding: "uint32", WordOrder: "little"}
words := []uint16{0x0001, 0x0002} // big: 0x00010002, little reverses to 0x00020001
gb, _ := big.decode(words)
gl, _ := little.decode(words)
if gb != int64(0x00010002) {
t.Errorf("big = %v, want %d", gb, 0x00010002)
}
if gl != int64(0x00020001) {
t.Errorf("little = %v, want %d", gl, 0x00020001)
}
}
func TestEncodeRoundTrip(t *testing.T) {
for _, enc := range []string{"uint16", "int16", "uint32", "int32", "float32", "float64"} {
reg := Register{Encoding: enc}
words, err := reg.encode(123)
if err != nil {
t.Fatalf("%s encode: %v", enc, err)
}
got, err := reg.decode(words)
if err != nil {
t.Fatalf("%s decode: %v", enc, err)
}
var f float64
switch v := got.(type) {
case int64:
f = float64(v)
case float64:
f = v
}
if math.Abs(f-123) > 1e-3 {
t.Errorf("%s round-trip = %v, want 123", enc, f)
}
}
}
func TestDataTypeAndWritable(t *testing.T) {
if got := (Register{Kind: "coil"}).dataType(); got != datasource.TypeBool {
t.Errorf("coil type = %v, want bool", got)
}
if got := (Register{Encoding: "float32"}).dataType(); got != datasource.TypeFloat64 {
t.Errorf("float type = %v, want float64", got)
}
if got := (Register{Encoding: "uint16", Scale: 0.5}).dataType(); got != datasource.TypeFloat64 {
t.Errorf("scaled int type = %v, want float64", got)
}
if got := (Register{Encoding: "uint16"}).dataType(); got != datasource.TypeInt64 {
t.Errorf("plain int type = %v, want int64", got)
}
if (Register{Kind: "input", Writable: true}).writable() {
t.Error("input register must be read-only")
}
if (Register{Kind: "discrete", Writable: true}).writable() {
t.Error("discrete input must be read-only")
}
if !(Register{Kind: "holding", Writable: true}).writable() {
t.Error("writable holding should be writable")
}
}
func f32Words(v float32) []uint16 {
u := math.Float32bits(v)
return []uint16{uint16(u >> 16), uint16(u)}
}
func f64Words(v float64) []uint16 {
u := math.Float64bits(v)
return []uint16{uint16(u >> 48), uint16(u >> 32), uint16(u >> 16), uint16(u)}
}