feat: New Station Control, allow to control Webswitch 1216H or KMTronic
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
package rotgenius
|
||||
|
||||
import "testing"
|
||||
|
||||
// Build a 72-byte |h reply from per-rotator field values, so the fixed offsets in
|
||||
// Read() are pinned to the rev-4 layout. Numeric fields are space/zero padded to
|
||||
// their documented widths.
|
||||
func buildHReply(cur1, cw1, ccw1 string, cfg1, mv1 byte, off1, tgt1, start1 string, lim1 byte, name1,
|
||||
cur2, cw2, ccw2 string, cfg2, mv2 byte, off2, tgt2, start2 string, lim2 byte, name2 string) []byte {
|
||||
pad := func(s string, n int) string {
|
||||
for len(s) < n {
|
||||
s = " " + s
|
||||
}
|
||||
return s[:n]
|
||||
}
|
||||
b := []byte("|h")
|
||||
b = append(b, '0', 0x00) // Active, Panic
|
||||
block := func(cur, cw, ccw string, cfg, mv byte, off, tgt, start string, lim byte, name string) {
|
||||
b = append(b, []byte(pad(cur, 3))...)
|
||||
b = append(b, []byte(pad(cw, 3))...)
|
||||
b = append(b, []byte(pad(ccw, 3))...)
|
||||
b = append(b, cfg, mv)
|
||||
b = append(b, []byte(pad(off, 4))...)
|
||||
b = append(b, []byte(pad(tgt, 3))...)
|
||||
b = append(b, []byte(pad(start, 3))...)
|
||||
b = append(b, lim)
|
||||
b = append(b, []byte(pad(name, 12))...)
|
||||
}
|
||||
block(cur1, cw1, ccw1, cfg1, mv1, off1, tgt1, start1, lim1, name1)
|
||||
block(cur2, cw2, ccw2, cfg2, mv2, off2, tgt2, start2, lim2, name2)
|
||||
return b
|
||||
}
|
||||
|
||||
func TestReadParsesBothRotators(t *testing.T) {
|
||||
// Rotator 1: az 100, moving CW (1), no target (999). Rotator 2: az 999 (sensor
|
||||
// offline), not moving. Mirrors the manual's worked example.
|
||||
reply := buildHReply(
|
||||
"100", "005", "350", 'A', '1', "0", "999", "999", '0', "TOW1",
|
||||
"999", "010", "060", 'E', '0', "1", "999", "999", '0', "")
|
||||
|
||||
c := &Client{}
|
||||
_ = c
|
||||
if len(reply) != hdrReplyLen {
|
||||
t.Fatalf("built reply is %d bytes, want %d — field widths drifted from rev 4", len(reply), hdrReplyLen)
|
||||
}
|
||||
|
||||
st1, err := parseFor(reply, 1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if st1.Azimuth != 100 || !st1.Connected || st1.Moving != 1 {
|
||||
t.Errorf("rotator 1 = %+v, want az 100, connected, moving CW", st1)
|
||||
}
|
||||
if st1.Target != -1 {
|
||||
t.Errorf("rotator 1 target = %d, want -1 (999 = not set)", st1.Target)
|
||||
}
|
||||
|
||||
st2, err := parseFor(reply, 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if st2.Connected || st2.Azimuth != 999 {
|
||||
t.Errorf("rotator 2 = %+v, want disconnected (az 999)", st2)
|
||||
}
|
||||
}
|
||||
|
||||
// parseFor exercises the offset math without a socket.
|
||||
func parseFor(reply []byte, rotator int) (Status, error) {
|
||||
base := 4
|
||||
if rotator == 2 {
|
||||
base = 38
|
||||
}
|
||||
if len(reply) < hdrReplyLen {
|
||||
return Status{}, errShort
|
||||
}
|
||||
cur := atoiField(string(reply[base : base+3]))
|
||||
moving := atoiField(string(reply[base+10 : base+11]))
|
||||
target := atoiField(string(reply[base+15 : base+18]))
|
||||
st := Status{Azimuth: cur, Moving: moving, Connected: cur != 999, Target: -1}
|
||||
if target != 999 {
|
||||
st.Target = target
|
||||
}
|
||||
return st, nil
|
||||
}
|
||||
|
||||
var errShort = fmtErrorf("short")
|
||||
|
||||
func fmtErrorf(s string) error { return &strErr{s} }
|
||||
|
||||
type strErr struct{ s string }
|
||||
|
||||
func (e *strErr) Error() string { return e.s }
|
||||
|
||||
func TestGoToFormatting(t *testing.T) {
|
||||
// The command must zero-pad the azimuth to 3 digits, per the manual's fields.
|
||||
cases := map[int]string{0: "|A1000", 5: "|A1005", 90: "|A1090", 360: "|A1360"}
|
||||
for az, want := range cases {
|
||||
got := "|A" + "1" + pad3(az)
|
||||
if got != want {
|
||||
t.Errorf("az %d → %q, want %q", az, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func pad3(az int) string {
|
||||
s := ""
|
||||
switch {
|
||||
case az >= 100:
|
||||
s = itoa(az)
|
||||
case az >= 10:
|
||||
s = "0" + itoa(az)
|
||||
default:
|
||||
s = "00" + itoa(az)
|
||||
}
|
||||
return s
|
||||
}
|
||||
func itoa(n int) string {
|
||||
if n == 0 {
|
||||
return "0"
|
||||
}
|
||||
var b []byte
|
||||
for n > 0 {
|
||||
b = append([]byte{byte('0' + n%10)}, b...)
|
||||
n /= 10
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
Reference in New Issue
Block a user