package easycomm import "testing" // Every one of these is a shape a real controller has been seen to answer with. // The point of the parser is that none of them is special-cased. func TestParseHeading(t *testing.T) { for _, tc := range []struct { line string az, el float64 ok bool }{ {"AZ123.4 EL45.0", 123.4, 45, true}, {"AZ=123.4 EL=45.0", 123.4, 45, true}, {"az 123.4 el 45.0", 123.4, 45, true}, {"AZ123.4\tEL45.0\r\n", 123.4, 45, true}, {"AZ012.0 EL000.0", 12, 0, true}, {"AZ370.5 EL05.5", 370.5, 5.5, true}, {"AZ123.4", 123.4, 0, true}, // azimuth-only controller {"RPRT 0", 0, 0, false}, {"", 0, 0, false}, } { az, el, ok := parseHeading(tc.line) if ok != tc.ok { t.Errorf("%q: ok=%v, wanted %v", tc.line, ok, tc.ok) continue } if ok && (az != tc.az || el != tc.el) { t.Errorf("%q: got %.1f/%.1f, wanted %.1f/%.1f", tc.line, az, el, tc.az, tc.el) } } } // The 450° overlap is the whole reason a satellite rotator is worth having: a // pass crossing north must continue past 360 instead of unwinding through the // entire scale with the antenna sweeping the ground. func TestWrapAz450(t *testing.T) { c := &Client{MaxAz: 450} // Nothing commanded yet: no history to be near, so the plain bearing. if got := c.wrapAz(10); got != 10 { t.Errorf("first move: got %.1f, wanted 10", got) } c.lastAz, c.commanded = 350, true // Crossing north: 370 is 20° away, 10 is 340° away. if got := c.wrapAz(10); got != 370 { t.Errorf("crossing north from 350: got %.1f, wanted 370", got) } // Coming back down the same way, the overlap stays the near answer. c.lastAz = 370 if got := c.wrapAz(350); got != 350 { t.Errorf("back from 370: got %.1f, wanted 350", got) } // Beyond the rotator's reach there is no overlap to use. c.lastAz = 440 if got := c.wrapAz(100); got != 100 { t.Errorf("past the end of the scale: got %.1f, wanted 100", got) } } func TestWrapAz360(t *testing.T) { c := &Client{MaxAz: 360} c.lastAz, c.commanded = 350, true if got := c.wrapAz(10); got != 10 { t.Errorf("a 360 rotator has no overlap: got %.1f, wanted 10", got) } if got := c.wrapAz(-10); got != 350 { t.Errorf("negative bearing: got %.1f, wanted 350", got) } if got := c.wrapAz(725); got != 5 { t.Errorf("two turns and five degrees: got %.1f, wanted 5", got) } } // A controller that never answers must not be treated as a broken one: the last // commanded position is reported, marked as not live. func TestSilentControllerReportsCommanded(t *testing.T) { c := &Client{MaxAz: 360, silent: true} if _, _, _, err := c.Heading(); err == nil { t.Error("a silent controller with nothing commanded should say it cannot report") } c.lastAz, c.lastEl, c.commanded = 120, 30, true az, el, live, err := c.Heading() if err != nil { t.Fatalf("after a command: %v", err) } if live { t.Error("a commanded position must not be reported as a live reading") } if az != 120 || el != 30 { t.Errorf("got %.1f/%.1f, wanted 120/30", az, el) } }