package gs232 import ( "strconv" "strings" "testing" ) // Real replies, as the controllers actually send them — a K3NG answering "C" // with "+0140" and CR+LF among them (reported from a live controller). func TestAzimuthReplies(t *testing.T) { cases := []struct { raw string want int }{ {"+0140\r\n", 140}, // GS-232A, K3NG firmware {"+0000\r", 0}, // due north {"+0359\r\n", 359}, // just short of it {"AZ=140\r\n", 140}, // GS-232B flavour {"AZ=140 EL=000\r\n", 140}, // GS-232B with elevation on the same line {"\r\n+0075\r\n", 75}, // a leftover terminator ahead of the answer } for _, c := range cases { m := azRe.FindStringSubmatch(strings.TrimSpace(c.raw)) if m == nil { t.Errorf("no azimuth found in %q", c.raw) continue } got, _ := strconv.Atoi(m[1]) if got%360 != c.want { t.Errorf("%q parsed as %d, want %d", c.raw, got%360, c.want) } } } // The az/el replies an ERC-M sends back to C2, in both flavours. The GS-232A // form is two identical "+0nnn" groups running together with nothing between // them, which is exactly the shape that makes a naive azimuth regex match the // ELEVATION when the azimuth is read a second time. func TestPositionReplies(t *testing.T) { cases := []struct { raw string wantAz, wantEl int }{ {"+0140+0032\r\n", 140, 32}, // GS-232A, the ERC-M's own form {"+0000+0000\r", 0, 0}, // parked {"AZ=140 EL=032\r\n", 140, 32}, // GS-232B flavour {"AZ=005 EL=090\r\n", 5, 90}, // straight up {"+0270+0180\r\n", 270, 180}, // past the zenith, still counting {"\r\n+0075+0005\r\n", 75, 5}, // a leftover terminator ahead of it {"+0450+0045\r\n", 90, 45}, // 450° mast in its overlap } for _, c := range cases { m := bothRe.FindStringSubmatch(strings.TrimSpace(c.raw)) if m == nil { t.Errorf("no position found in %q", c.raw) continue } az, _ := strconv.Atoi(m[1]) el, _ := strconv.Atoi(m[2]) if az%360 != c.wantAz || el != c.wantEl { t.Errorf("%q → az %d el %d, want az %d el %d", c.raw, az%360, el, c.wantAz, c.wantEl) } } } // A controller that answers C2 with the azimuth alone must NOT be read as // "elevation zero" — that is a plausible-looking wrong answer, the antenna // sitting on the horizon, and it would send the tracker chasing it. func TestPositionRejectsAzimuthOnlyReply(t *testing.T) { for _, raw := range []string{"+0140\r\n", "AZ=140\r\n", "?>\r\n"} { if m := bothRe.FindStringSubmatch(strings.TrimSpace(raw)); m != nil { t.Errorf("%q parsed as a two-axis reply (%v) — it is not one", raw, m[1:]) } } } // The reply to a bare B, for the controllers that do not answer C2. func TestElevationReplies(t *testing.T) { cases := []struct { raw string want int }{ {"+0032\r\n", 32}, {"EL=032\r\n", 32}, {"+0000\r", 0}, {"+0090\r\n", 90}, } for _, c := range cases { m := elRe.FindStringSubmatch(strings.TrimSpace(c.raw)) if m == nil { t.Errorf("no elevation found in %q", c.raw) continue } if got, _ := strconv.Atoi(m[1]); got != c.want { t.Errorf("%q → %d, want %d", c.raw, got, c.want) } } }