package gps import ( "testing" ) func TestAppendChecksum(t *testing.T) { testCases := []struct { name string input []byte expected []byte }{ { name: "simple message", input: []byte{0xB5, 0x62, 0x06, 0x24, 0x00, 0x00}, expected: []byte{0xB5, 0x62, 0x06, 0x24, 0x00, 0x00, 0x2A, 0x84}, }, { name: "CFG-NAV5 header only", input: []byte{0xB5, 0x62, 0x06, 0x24, 0x24, 0x00}, expected: []byte{0xB5, 0x62, 0x06, 0x24, 0x24, 0x00, 0x4E, 0xCC}, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { result := appendChecksum(tc.input) if len(result) != len(tc.expected) { t.Errorf("expected length %d, got %d", len(tc.expected), len(result)) return } // Check checksum bytes (last two bytes) ckA := result[len(result)-2] ckB := result[len(result)-1] expectedCkA := tc.expected[len(tc.expected)-2] expectedCkB := tc.expected[len(tc.expected)-1] if ckA != expectedCkA || ckB != expectedCkB { t.Errorf("expected checksum 0x%02X 0x%02X, got 0x%02X 0x%02X", expectedCkA, expectedCkB, ckA, ckB) } }) } } func TestAppendChecksumPreservesOriginal(t *testing.T) { input := []byte{0xB5, 0x62, 0x06, 0x24, 0x00, 0x00} original := make([]byte, len(input)) copy(original, input) result := appendChecksum(input) // Verify original bytes are preserved for i := range input { if result[i] != original[i] { t.Errorf("byte %d changed: expected 0x%02X, got 0x%02X", i, original[i], result[i]) } } // Verify two bytes were appended if len(result) != len(input)+2 { t.Errorf("expected length %d, got %d", len(input)+2, len(result)) } } func TestNav5CmdConfig(t *testing.T) { // Verify nav5Cmd has expected values if nav5Cmd.DynModel != 6 { t.Errorf("expected DynModel 6 (airborne <1g), got %d", nav5Cmd.DynModel) } if nav5Cmd.FixMode != 3 { t.Errorf("expected FixMode 3 (auto 2D/3D), got %d", nav5Cmd.FixMode) } expectedMask := CfgNav5Dyn | CfgNav5MinEl | CfgNav5PosFixMode if nav5Cmd.Mask != expectedMask { t.Errorf("expected Mask 0x%04X, got 0x%04X", expectedMask, nav5Cmd.Mask) } if nav5Cmd.MinElev_deg != 5 { t.Errorf("expected MinElev_deg 5, got %d", nav5Cmd.MinElev_deg) } } func TestGNSSDisableCmdConfig(t *testing.T) { // Verify GNSSDisableCmd has expected structure if gnssDisableCmd.MsgVer != 0 { t.Errorf("expected MsgVer 0, got %d", gnssDisableCmd.MsgVer) } if gnssDisableCmd.NumTrkChHw != 0x20 { t.Errorf("expected NumTrkChHw 0x20, got 0x%02X", gnssDisableCmd.NumTrkChHw) } if len(gnssDisableCmd.ConfigBlocks) != 5 { t.Errorf("expected 5 config blocks, got %d", len(gnssDisableCmd.ConfigBlocks)) return } // Verify GPS is enabled gpsBlock := gnssDisableCmd.ConfigBlocks[0] if gpsBlock.GnssId != 0 { t.Errorf("expected first block GnssId 0 (GPS), got %d", gpsBlock.GnssId) } if gpsBlock.Flags&CfgGnssEnable == 0 { t.Error("expected GPS to be enabled") } // Verify other GNSS are disabled for i := 1; i < len(gnssDisableCmd.ConfigBlocks); i++ { block := gnssDisableCmd.ConfigBlocks[i] if block.Flags&CfgGnssEnable != 0 { t.Errorf("expected block %d (GnssId %d) to be disabled", i, block.GnssId) } } } func TestNav5CmdWrite(t *testing.T) { buf := make([]byte, 64) nav5Cmd.Put42Bytes(buf) // Verify sync chars if buf[0] != 0xB5 || buf[1] != 0x62 { t.Errorf("expected sync 0xB5 0x62, got 0x%02X 0x%02X", buf[0], buf[1]) } // Verify class/id if buf[2] != 0x06 || buf[3] != 0x24 { t.Errorf("expected class/id 0x06 0x24, got 0x%02X 0x%02X", buf[2], buf[3]) } // Verify DynModel at offset 8 if buf[8] != 6 { t.Errorf("expected DynModel 6, got %d", buf[8]) } } func TestGNSSDisableCmdWrite(t *testing.T) { buf := make([]byte, 64) err := gnssDisableCmd.Put(buf) if err != nil { t.Errorf("unexpected error, likely buffer too short for data: %v", err) } // 6 header + 4 payload header + 5*8 blocks = 50 bytes const expectedLen = 6 + 4 + 5*8 sz := gnssDisableCmd.Size() if sz != expectedLen { t.Errorf("expected %d bytes, got %d", expectedLen, sz) } // Verify sync chars if buf[0] != 0xB5 || buf[1] != 0x62 { t.Errorf("expected sync 0xB5 0x62, got 0x%02X 0x%02X", buf[0], buf[1]) } // Verify class/id if buf[2] != 0x06 || buf[3] != 0x3E { t.Errorf("expected class/id 0x06 0x3E, got 0x%02X 0x%02X", buf[2], buf[3]) } // Verify number of blocks if buf[9] != 5 { t.Errorf("expected 5 blocks, got %d", buf[9]) } } func TestChecksumCalculation(t *testing.T) { // Test with known UBX message and expected checksum // This is a minimal CFG-NAV5 poll message msg := []byte{0xB5, 0x62, 0x06, 0x24, 0x00, 0x00} result := appendChecksum(msg) // Verify checksum by recalculating var ckA, ckB byte for i := 2; i < len(msg); i++ { ckA += msg[i] ckB += ckA } if result[6] != ckA || result[7] != ckB { t.Errorf("checksum mismatch: expected 0x%02X 0x%02X, got 0x%02X 0x%02X", ckA, ckB, result[6], result[7]) } } func TestMessageRateCmdConfigs(t *testing.T) { testCases := []struct { name string cmd CfgMsg1 msgClass byte msgID byte rate byte }{ {"GGA", messageRateGGACmd, 0xF0, 0x00, 1}, {"GLL", messageRateGLLCmd, 0xF0, 0x01, 1}, {"GSA", messageRateGSACmd, 0xF0, 0x02, 0}, {"GSV", messageRateGSVCmd, 0xF0, 0x03, 0}, {"RMC", messageRateRMCCmd, 0xF0, 0x04, 1}, {"VTG", messageRateVTGCmd, 0xF0, 0x05, 0}, {"ZDA", messageRateZDACmd, 0xF0, 0x08, 0}, {"TXT", messageRateTXTCmd, 0xF0, 0x41, 0}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { if tc.cmd.MsgClass != tc.msgClass { t.Errorf("expected MsgClass 0x%02X, got 0x%02X", tc.msgClass, tc.cmd.MsgClass) } if tc.cmd.MsgID != tc.msgID { t.Errorf("expected MsgID 0x%02X, got 0x%02X", tc.msgID, tc.cmd.MsgID) } if tc.cmd.Rate != tc.rate { t.Errorf("expected Rate %d, got %d", tc.rate, tc.cmd.Rate) } }) } } func TestCfgMsg1Write(t *testing.T) { cmd := CfgMsg1{ MsgClass: 0xF0, MsgID: 0x00, Rate: 1, } buf := make([]byte, 16) cmd.Put9Bytes(buf) // Verify sync chars if buf[0] != 0xB5 || buf[1] != 0x62 { t.Errorf("expected sync 0xB5 0x62, got 0x%02X 0x%02X", buf[0], buf[1]) } // Verify class/id (0x06 0x01 for CFG-MSG) if buf[2] != 0x06 || buf[3] != 0x01 { t.Errorf("expected class/id 0x06 0x01, got 0x%02X 0x%02X", buf[2], buf[3]) } // Verify length (3 bytes payload) if buf[4] != 3 || buf[5] != 0 { t.Errorf("expected length 3, got %d", uint16(buf[4])|uint16(buf[5])<<8) } // Verify payload if buf[6] != 0xF0 { t.Errorf("expected MsgClass 0xF0, got 0x%02X", buf[6]) } if buf[7] != 0x00 { t.Errorf("expected MsgID 0x00, got 0x%02X", buf[7]) } if buf[8] != 1 { t.Errorf("expected Rate 1, got %d", buf[8]) } } func TestCfgMsg1ClassID(t *testing.T) { cmd := CfgMsg1{} if got := cmd.classID(); got != 0x0106 { t.Errorf("expected 0x0106, got 0x%04x", got) } } func TestMinimalMessageRatesConfig(t *testing.T) { // Verify the minimal config has correct rates set // GGA and RMC should be enabled (rate=1), others disabled (rate=0) expectedRates := map[byte]byte{ 0x00: 1, // GGA - enabled 0x01: 1, // GLL - enabled 0x02: 0, // GSA - disabled 0x03: 0, // GSV - disabled 0x04: 1, // RMC - enabled 0x05: 0, // VTG - disabled 0x08: 0, // ZDA - disabled 0x41: 0, // TXT - disabled } commands := []CfgMsg1{ messageRateGGACmd, messageRateGLLCmd, messageRateGSACmd, messageRateGSVCmd, messageRateRMCCmd, messageRateVTGCmd, messageRateZDACmd, messageRateTXTCmd, } for _, cmd := range commands { expectedRate, ok := expectedRates[cmd.MsgID] if !ok { t.Errorf("unexpected MsgID 0x%02X", cmd.MsgID) continue } if cmd.Rate != expectedRate { t.Errorf("MsgID 0x%02X: expected rate %d, got %d", cmd.MsgID, expectedRate, cmd.Rate) } } } func TestAllMessageRatesWriteCorrectBytes(t *testing.T) { // Test that each message rate command writes the correct bytes commands := []CfgMsg1{ messageRateGGACmd, messageRateGLLCmd, messageRateGSACmd, messageRateGSVCmd, messageRateRMCCmd, messageRateVTGCmd, messageRateZDACmd, messageRateTXTCmd, } for _, cmd := range commands { buf := make([]byte, 16) cmd.Put9Bytes(buf) // Verify MsgClass in payload if buf[6] != 0xF0 { t.Errorf("MsgID 0x%02X: expected MsgClass 0xF0, got 0x%02X", cmd.MsgID, buf[6]) } // Verify MsgID in payload if buf[7] != cmd.MsgID { t.Errorf("expected MsgID 0x%02X in payload, got 0x%02X", cmd.MsgID, buf[7]) } // Verify Rate in payload if buf[8] != cmd.Rate { t.Errorf("MsgID 0x%02X: expected Rate %d, got %d", cmd.MsgID, cmd.Rate, buf[8]) } } } func TestSetMessageRatesAllEnabledModifiesRate(t *testing.T) { // Verify that when we copy a command and set Rate=1, it works correctly cmd := messageRateGSACmd // This one is disabled by default if cmd.Rate != 0 { t.Errorf("expected GSA default rate 0, got %d", cmd.Rate) } // Simulate what SetMessageRatesAllEnabled does cmd.Rate = 1 buf := make([]byte, 16) cmd.Put9Bytes(buf) if buf[8] != 1 { t.Errorf("expected Rate 1 in buffer, got %d", buf[8]) } }