package aprs import ( "reflect" "testing" "time" ) func TestParseBase91Telemetry(t *testing.T) { tests := []struct { Test string Telemetry *Telemetry Comment string }{ { "|!!!!|", &Telemetry{Analog: []int{0}}, "", }, { "|ss11|", &Telemetry{ID: 7544, Analog: []int{1472}}, "", }, { "|ss112233|", &Telemetry{ID: 7544, Analog: []int{1472, 1564, 1656}}, "", }, { "|ss1122334455!\"|", &Telemetry{ID: 7544, Analog: []int{1472, 1564, 1656, 1748, 1840}, Digital: []bool{true, false, false, false, false, false, false, false}}, "", }, { "|ss11|73's de N0CALL", &Telemetry{ID: 7544, Analog: []int{1472}}, "73's de N0CALL", }, { "`pZ3l-B]/'\"6{}|!9'X$u|!wr8!|3", &Telemetry{ID: 24, Analog: []int{601, 357}}, "`pZ3l-B]/'\"6{}!wr8!|3", }, { "!/0%3RTh<6>dS_http://aprs.fi/|\"p%T'.ag|", &Telemetry{ID: 170, Analog: []int{415, 559, 5894}}, "!/0%3RTh<6>dS_http://aprs.fi/", }, { "!6304.03NN02739.63E#PHG26303/Siilinjarvi|\"p%T'.agff|", &Telemetry{ID: 170, Analog: []int{415, 559, 5894, 6348}}, "!6304.03NN02739.63E#PHG26303/Siilinjarvi", }, } for _, test := range tests { t.Run(test.Test, func(t *testing.T) { v, comment, err := parseBase91Telemetry(test.Test) if err != nil { t.Fatal(err) } if (v == nil) != (test.Telemetry == nil) { t.Fatalf("expected telemetry %#+v, got %#+v", test.Telemetry, v) } if test.Telemetry != nil { if v.ID != test.Telemetry.ID { t.Errorf("expected id %d, got %d", test.Telemetry.ID, v.ID) } if !reflect.DeepEqual(v.Analog, test.Telemetry.Analog) { t.Errorf("expected analog values %d, got %d", test.Telemetry.Analog, v.Analog) } if !reflect.DeepEqual(v.Digital, test.Telemetry.Digital) { t.Errorf("expected digital values %t, got %t", test.Telemetry.Digital, v.Digital) } } if comment != test.Comment { t.Errorf("expected comment %q, got %q", test.Comment, comment) } }) } } func TestParsePosition(t *testing.T) { localTime := time.Now() tests := []struct { Name string Raw Raw Want *Position }{ { "no timestamp, no APRS messaging, with comment", "!4903.50N/07201.75W-Test 001234", &Position{ Latitude: 49.058333, Longitude: -72.029167, Symbol: "/-", Comment: "Test 001234", }, }, { "no timestamp, no APRS messaging, altitude = 1234 ft", "!4903.50N/07201.75W-Test /A=001234", &Position{ Latitude: 49.058333, Longitude: -72.029167, Altitude: 376.1232, Symbol: "/-", Comment: "Test ", }, }, { "no timestamp, no APRS messaging, location to nearest degree", "!49 . N/072 . W-", &Position{ Latitude: 49, Longitude: -72, Symbol: "/-", }, }, { "with timestamp, no APRS messaging, zulu time, with comment", "/092345z4903.50N/07201.75W>Test1234", &Position{ Latitude: 49.058333, Longitude: -72.029167, Symbol: "/>", Comment: "Test1234", Time: time.Date(localTime.Year(), localTime.Month(), 9, 23, 45, 0, 0, time.UTC), }, }, { "with timestamp, with APRS messaging, local time, with comment", "@092345/4903.50N/07201.75W>Test1234", &Position{ HasMessaging: true, Latitude: 49.058333, Longitude: -72.029167, Symbol: "/>", Comment: "Test1234", Time: time.Date(localTime.Year(), localTime.Month(), 9, 23, 45, 0, 0, localTime.Location()), }, }, { "no timestamp, with APRS messaging, with PHG", "=4903.50N/07201.75W#PHG5132", &Position{ HasMessaging: true, Latitude: 49.058333, Longitude: -72.029167, Symbol: "/#", }, }, { "weather report", "=4903.50N/07201.75W 225/000g000t050r000p001h00b10138dU2k", &Position{ HasMessaging: true, Latitude: 49.058333, Longitude: -72.029167, Symbol: "/ ", Comment: "dU2k", Velocity: &Velocity{Course: 225}, Weather: &Weather{Temperature: 10, Rain24h: 0.254, Pressure: 1013.8}, }, }, { "with timestamp, with APRS messaging, local time, course/speed", "@092345/4903.50N/07201.75W>088/036", &Position{ HasMessaging: true, Latitude: 49.058333, Longitude: -72.029167, Symbol: "/>", Time: time.Date(localTime.Year(), localTime.Month(), 9, 23, 45, 0, 0, localTime.Location()), Velocity: &Velocity{Course: 88, Speed: 18.519999984000002}, }, }, { "with timestamp, with APRS messaging, hours/mins/secs time, PHG", "@234517h4903.50N/07201.75W>PHG5132", &Position{ HasMessaging: true, Latitude: 49.058333, Longitude: -72.029167, Symbol: "/>", Time: time.Date(0, 0, 0, 23, 45, 17, 0, time.UTC), }, }, { "with timestamp, with APRS messaging, zulu time, radio range", "@092345z4903.50N/07201.75W>RNG0050", &Position{ HasMessaging: true, Latitude: 49.058333, Longitude: -72.029167, Symbol: "/>", Time: time.Date(localTime.Year(), localTime.Month(), 9, 23, 45, 0, 0, time.UTC), }, }, { "with timestamp, hours/mins/secs time, DF, no APRS messaging", "/234517h4903.50N/07201.75W>DFS2360", &Position{ Latitude: 49.058333, Longitude: -72.029167, Symbol: "/>", Time: time.Date(0, 0, 0, 23, 45, 17, 0, localTime.Location()), }, }, { "with timestamp, APRS messaging, zulu time, weather report", "@092345z4903.50N/07201.75W 090/000g000t066r000p000dUII", &Position{ HasMessaging: true, Latitude: 49.058333, Longitude: -72.029167, Symbol: "/ ", Comment: "dUII", Time: time.Date(localTime.Year(), localTime.Month(), 9, 23, 45, 0, 0, time.UTC), Velocity: &Velocity{Course: 90}, Weather: &Weather{Temperature: 18.88888888888889}, }, }, { "no timestamp, course/speed/bearing/NRQ, with APRS messaging, DF station moving", "=4903.50N/07201.75W\\088/036/270/729", &Position{ HasMessaging: true, Latitude: 49.058333, Longitude: -72.029167, Symbol: "/\\", Velocity: &Velocity{Course: 88, Speed: knotsToMetersPerSecond(36)}, Wind: &Wind{Direction: 270, Speed: knotsToMetersPerSecond(729)}, }, }, { "no timestamp, course/speed/bearing/NRQ, with APRS messaging, DF station fixed", "=4903.50N/07201.75W\\000/036/270/729", &Position{ HasMessaging: true, Latitude: 49.058333, Longitude: -72.029167, Symbol: "/\\", Velocity: &Velocity{Course: 0, Speed: knotsToMetersPerSecond(36)}, Wind: &Wind{Direction: 270, Speed: knotsToMetersPerSecond(729)}, }, }, { "with timestamp, course/speed/bearing/NRQ, with APRS messaging", "@092345z4903.50N/07201.75W\\088/036/270/729", &Position{ HasMessaging: true, Latitude: 49.058333, Longitude: -72.029167, Symbol: "/\\", Time: time.Date(localTime.Year(), localTime.Month(), 9, 23, 45, 0, 0, time.UTC), Velocity: &Velocity{Course: 88, Speed: knotsToMetersPerSecond(36)}, Wind: &Wind{Direction: 270, Speed: knotsToMetersPerSecond(729)}, }, }, { "with timestamp, bearing/NRQ, no course/speed, no APRS messaging", "/092345z4903.50N/07201.75W\\000/000/270/729", &Position{ Latitude: 49.058333, Longitude: -72.029167, Symbol: "/\\", Time: time.Date(localTime.Year(), localTime.Month(), 9, 23, 45, 0, 0, time.UTC), Velocity: &Velocity{}, Wind: &Wind{Direction: 270, Speed: knotsToMetersPerSecond(729)}, }, }, // compressed positions: { "compressed, with APRS messaging", "=/5L!!<*e7> sTComment", &Position{ HasMessaging: true, Latitude: 49.5, Longitude: -72.750004, Symbol: "/>", Comment: "Comment", }, }, { "compressed, with APRS messaging, RMC sentence, with course/speed", "=/5L!!<*e7>7P[", &Position{ HasMessaging: true, Latitude: 49.5, Longitude: -72.750004, Symbol: "/>", }, }, { "compressed, with APRS messaging, with radio range", "=/5L!!<*e7>{?!", &Position{ HasMessaging: true, Latitude: 49.5, Longitude: -72.750004, Symbol: "/>", }, }, { "compressed, with APRS messaging, GGA sentence, altitude", "=/5L!!<*e7OS]S", &Position{ HasMessaging: true, Latitude: 49.5, Longitude: -72.750004, Symbol: "/O", }, }, { "compressed, with APRS messaging, timestamp, radio range", "@092345z/5L!!<*e7>{?!", &Position{ HasMessaging: true, Latitude: 49.5, Longitude: -72.750004, Symbol: "/>", Time: time.Date(localTime.Year(), localTime.Month(), 9, 23, 45, 0, 0, time.UTC), }, }, } var decoder positionDecoder for _, test := range tests { t.Run(test.Name, func(t *testing.T) { frame := &Frame{Raw: test.Raw} if !decoder.CanDecode(frame) { t.Fatalf("%T can't decode %q", decoder, test.Raw) } v, err := decoder.Decode(frame) if err != nil { t.Fatal(err) } testComparePosition(t, test.Want, v) }) } } func testComparePosition(t *testing.T, want *Position, value any) { t.Helper() p, ok := value.(*Position) if !ok { t.Fatalf("expected data to be a %T, got %T", want, value) return } if p.HasMessaging != want.HasMessaging { t.Errorf("expected to have messaging: %t, got %t", want.HasMessaging, p.HasMessaging) } if !testAlmostEqual(p.Latitude, want.Latitude) { t.Errorf("expected latitude %f, got %f", want.Latitude, p.Latitude) } if !testAlmostEqual(p.Longitude, want.Longitude) { t.Errorf("expected longitude %f, got %f", want.Longitude, p.Longitude) } if !testAlmostEqual(p.Altitude, want.Altitude) { t.Errorf("expected altitude %f, got %f", want.Altitude, p.Altitude) } if p.Symbol != want.Symbol { t.Errorf("expected symbol %q, got %q", want.Symbol, p.Symbol) } if p.Comment != want.Comment { t.Errorf("expected comment %q, got %q", want.Comment, p.Comment) } if want.Time.Equal(time.Time{}) { if !p.Time.Equal(time.Time{}) { t.Errorf("expected no time stamp, got %s", p.Time) } } else if want.Time.Year() == -1 { if p.Time.Hour() != want.Time.Hour() || p.Time.Minute() != want.Time.Minute() || p.Time.Second() != want.Time.Second() { t.Errorf("expected time %s, got %s", want.Time.Format("15:04:05"), p.Time.Format("15:04:05")) } } else if !want.Time.Equal(p.Time) { t.Errorf("expected time %s, got %s", want.Time, p.Time) } if want.Velocity != nil { if p.Velocity == nil { t.Errorf("expected velocity, got none") } else if !reflect.DeepEqual(p.Velocity, want.Velocity) { t.Errorf("expected velocity %#+v, got %#+v", want.Velocity, p.Velocity) } } else if p.Velocity != nil { t.Errorf("expected no velocity, got %#+v", p.Velocity) } if want.Wind != nil { if p.Wind == nil { t.Errorf("expected wind, got none") } else if !reflect.DeepEqual(p.Wind, want.Wind) { t.Errorf("expected wind %#+v, got %#+v", want.Wind, p.Wind) } } else if p.Wind != nil { t.Errorf("expected no wind, got %#+v", p.Wind) } if want.Telemetry != nil { if p.Telemetry == nil { t.Errorf("expected telemetry, got none") } else if !reflect.DeepEqual(p.Telemetry, want.Telemetry) { t.Errorf("expected telemetry %#+v, got %#+v", want.Telemetry, p.Telemetry) } } else if p.Telemetry != nil { t.Errorf("expected no telemetry, got %#+v", p.Telemetry) } if want.Weather != nil { if p.Weather == nil { t.Errorf("expected weather, got none") } else if !reflect.DeepEqual(p.Weather, want.Weather) { t.Errorf("expected weather %#+v, got %#+v", want.Weather, p.Weather) } } else if p.Weather != nil { t.Errorf("expected no weather, got %#+v", p.Weather) } }