116 lines
3.3 KiB
Go
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)}
|
|
}
|