package steppir import ( "encoding/binary" "testing" ) // The exact bytes are the correctness checksum. If buildSet ever drifts from the // three-source-agreed layout, this fails — a wrong packet is a silently mistuned // antenna, far worse than a compile error. func TestBuildSetLayout(t *testing.T) { // 14.074 MHz, normal, set-freq. freq/10 = 1_407_400 = 0x00 0x15 0x79 0xA8. pkt := buildSet(14_074_000, DirNormal, '1') want := []byte{'@', 'A', 0x00, 0x15, 0x79, 0xA8, 0x00, 0x00, '1', 0x00, 0x0D} if len(pkt) != 11 { t.Fatalf("packet is %d bytes, want 11", len(pkt)) } for i := range want { if pkt[i] != want[i] { t.Fatalf("byte %d = 0x%02X, want 0x%02X\n got %X\nwant %X", i, pkt[i], want[i], pkt, want) } } // Frequency must round-trip: bytes [2:6] × 10 = Hz. if got := int(binary.BigEndian.Uint32(pkt[2:6])) * 10; got != 14_074_000 { t.Fatalf("freq round-trip = %d, want 14074000", got) } } func TestBuildSetDirectionAndCommand(t *testing.T) { cases := []struct { dir int cmd byte wantDir byte wantCmd byte }{ {DirNormal, '1', 0x00, '1'}, {Dir180, '1', 0x40, '1'}, {DirBi, '1', 0x80, '1'}, {DirNormal, 'S', 0x00, 'S'}, // retract / home {DirNormal, 'R', 0x00, 'R'}, // autotrack on } for _, c := range cases { pkt := buildSet(21_000_000, c.dir, c.cmd) if pkt[7] != c.wantDir { t.Errorf("dir %d → byte 0x%02X, want 0x%02X", c.dir, pkt[7], c.wantDir) } if pkt[8] != c.wantCmd { t.Errorf("cmd %q → byte 0x%02X, want 0x%02X", c.cmd, pkt[8], c.wantCmd) } } } // parseStatus decodes what the controller sends back — the inverse of buildSet's // frequency field, plus the direction nibble. func TestParseStatus(t *testing.T) { frame := []byte{0x00, 0x00, 0x00, 0x15, 0x79, 0xA8, 0x01, 0x40, '1', '2', 0x0D} st, err := parseStatus(frame) if err != nil { t.Fatal(err) } if st.Frequency != 14074 { t.Errorf("freq = %d kHz, want 14074", st.Frequency) } if st.Direction != Dir180 { t.Errorf("direction = %d, want %d (180°)", st.Direction, Dir180) } if st.MotorsMoving != 0 { // 0x01 is the always-set bit, not motion t.Errorf("moving = %d, want 0 (only the always-on bit set)", st.MotorsMoving) } // Motors busy: a bit beyond 0x01 is set. frame[6] = 0x07 st, _ = parseStatus(frame) if st.MotorsMoving == 0 { t.Error("active-motors 0x07 should read as moving") } // 0xFF is "command received", not motion. frame[6] = 0xFF st, _ = parseStatus(frame) if st.MotorsMoving != 0 { t.Error("active-motors 0xFF (command received) must not read as moving") } }