protocol/aprs: refactored
Some checks failed
Run tests / test (1.25) (push) Failing after 1m37s
Run tests / test (stable) (push) Failing after 1m37s

This commit is contained in:
2026-03-02 22:28:17 +01:00
parent 452f521866
commit 63040a44b3
24 changed files with 3506 additions and 1533 deletions

View File

@@ -0,0 +1,433 @@
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)
}
}