Skip to content

Commit e4d24e3

Browse files
authored
Add Kenwood proprietary sentences $PKLDS, $PKNDS, $PKLID, $PKNID, $PKLSH, $PKNSH, $PKWDWPL (#118)
These proprietary NMEA sentences have been captured from Various Kenwood LMR radios. and is parity with pynema2
1 parent cd0f55b commit e4d24e3

16 files changed

+716
-0
lines changed

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@ on [IEC 61162-1:2016 (Edition 5.0 2016-08)](https://webstore.iec.ch/publication/
9090
| HSS | Hull stress surveillance systems | |
9191
| HTC | Heading/track control command | |
9292
| HTD | Heading /track control data | |
93+
| KLDS | Kenwood LMR - FleetSync AVL | |
94+
| KLID | Kenwood LMR - FleetSync | |
95+
| KLSH | Kenwood LMR - FleetSync AVL | |
96+
| KNDS | Kenwood LMR - Digital AVL | |
97+
| KNID | Kenwood LMR - Digital | |
98+
| KNSH | Kenwood LMR - Digital AVL | |
99+
| KWDWPL | Kenwood Waypoint Location - Amateur Radio | [direwolf](https://github.com/wb2osz/direwolf/blob/master/src/waypoint.c) |
93100
| LR1 | AIS long-range reply sentence 1 | |
94101
| LR2 | AIS long-range reply sentence 2 | |
95102
| LR3 | AIS long-range reply sentence 3 | |

pklds.go

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package nmea
2+
3+
import (
4+
"strings"
5+
)
6+
7+
const (
8+
// TypePKLDS type for PKLDS sentences
9+
TypePKLDS = "KLDS"
10+
)
11+
12+
// PKLDS is Kenwood propirtary sentance it is RMC with the addition of Fleetsync ID and status information.
13+
// http://aprs.gids.nl/nmea/#rmc
14+
//
15+
// Format: $PKLDS,hhmmss.ss,A,ddmm.mm,a,dddmm.mm,a,x.x,x.x,xxxx,x.x,a*hh<CR><LF>
16+
// Example: $PKLDS,220516,A,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W00,100,2000,25,00*6E
17+
type PKLDS struct {
18+
BaseSentence
19+
Time Time // Time Stamp
20+
Validity string // validity - A-ok, V-invalid
21+
Latitude float64 // Latitude
22+
Longitude float64 // Longitude
23+
Speed float64 // Speed in knots
24+
Course float64 // True course
25+
Date Date // Date
26+
Variation float64 // Magnetic variation
27+
SentanceVersion string // 00 to 15
28+
Fleet string // 100 to 349
29+
UnitID string // 1000 to 4999
30+
Status string // 10 to 99
31+
Extension string // 00 to 99
32+
}
33+
34+
// newPKLDS constructor
35+
func newPKLDS(s BaseSentence) (Sentence, error) {
36+
p := NewParser(s)
37+
p.AssertType(TypePKLDS)
38+
m := PKLDS{
39+
BaseSentence: s,
40+
Time: p.Time(0, "time"),
41+
Validity: p.EnumString(1, "validity", ValidRMC, InvalidRMC),
42+
Latitude: p.LatLong(2, 3, "latitude"),
43+
Longitude: p.LatLong(4, 5, "longitude"),
44+
Speed: p.Float64(6, "speed"),
45+
Course: p.Float64(7, "course"),
46+
Date: p.Date(8, "date"),
47+
Variation: p.Float64(9, "variation"),
48+
SentanceVersion: p.String(10, "sentance version, range of 00 to 15"),
49+
Fleet: p.String(11, "fleet, range of 100 to 349"),
50+
UnitID: p.String(12, "subscriber unit id, range of 1000 to 4999"),
51+
Status: p.String(13, "subscriber unit status id, range of 10 to 99"),
52+
Extension: p.String(14, "reserved for future use, range of 00 to 99"),
53+
}
54+
if strings.HasPrefix(m.SentanceVersion, "W") == true {
55+
m.Variation = 0 - m.Variation
56+
}
57+
m.SentanceVersion = strings.TrimPrefix(strings.TrimPrefix(m.SentanceVersion, "W"), "E")
58+
return m, p.Err()
59+
}

pklds_test.go

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package nmea
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
var pkldstests = []struct {
10+
name string
11+
raw string
12+
err string
13+
msg PKLDS
14+
}{
15+
{
16+
name: "good sentence West",
17+
raw: "$PKLDS,220516,A,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W00,100,2000,15,00,*60",
18+
msg: PKLDS{
19+
Time: Time{true, 22, 05, 16, 0},
20+
Validity: "A",
21+
Latitude: MustParseGPS("5133.82 N"),
22+
Longitude: MustParseGPS("00042.24 W"),
23+
Speed: 173.8,
24+
Course: 231.8,
25+
Date: Date{true, 13, 06, 94},
26+
Variation: -4.2,
27+
SentanceVersion: "00",
28+
Fleet: "100",
29+
UnitID: "2000",
30+
Status: "15",
31+
Extension: "00",
32+
},
33+
},
34+
{
35+
name: "good sentence East",
36+
raw: "$PKLDS,220516,A,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,E00,100,2000,15,00,*72",
37+
msg: PKLDS{
38+
Time: Time{true, 22, 05, 16, 0},
39+
Validity: "A",
40+
Latitude: MustParseGPS("5133.82 N"),
41+
Longitude: MustParseGPS("00042.24 W"),
42+
Speed: 173.8,
43+
Course: 231.8,
44+
Date: Date{true, 13, 06, 94},
45+
Variation: 4.2,
46+
SentanceVersion: "00",
47+
Fleet: "100",
48+
UnitID: "2000",
49+
Status: "15",
50+
Extension: "00",
51+
},
52+
},
53+
54+
{
55+
name: "bad sentence",
56+
raw: "$PKLDS,220516,D,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W00,100,2000,15,00,*65",
57+
err: "nmea: PKLDS invalid validity: D",
58+
},
59+
}
60+
61+
func TestPKLDS(t *testing.T) {
62+
for _, tt := range pkldstests {
63+
t.Run(tt.name, func(t *testing.T) {
64+
m, err := Parse(tt.raw)
65+
if tt.err != "" {
66+
assert.Error(t, err)
67+
assert.EqualError(t, err, tt.err)
68+
} else {
69+
assert.NoError(t, err)
70+
pklds := m.(PKLDS)
71+
pklds.BaseSentence = BaseSentence{}
72+
assert.Equal(t, tt.msg, pklds)
73+
}
74+
})
75+
}
76+
}

pklid.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package nmea
2+
3+
const (
4+
// TypePKLID type for PKLID sentances
5+
TypePKLID = "KLID"
6+
)
7+
8+
// PKLID is a Kenwood Propritary sentance used for GPS data communications in FleetSync.
9+
// $PKLID,<0>,<1>,<2>,<3>,<4>*hh<CR><LF>
10+
// Format: $PKLID,xx,xxx,xxxx,xx,xx,*xx<CR><LF>
11+
// Example: $PKLID,00,100,2000,15,00,*??
12+
type PKLID struct {
13+
BaseSentence
14+
SentanceVersion string // 00 to 15
15+
Fleet string // 100 to 349
16+
UnitID string // 1000 to 4999
17+
Status string // 10 to 99
18+
Extension string // 00 to 99
19+
}
20+
21+
// newPKLID constructor
22+
func newPKLID(s BaseSentence) (Sentence, error) {
23+
p := NewParser(s)
24+
p.AssertType(TypePKLID)
25+
26+
return PKLID{
27+
BaseSentence: s,
28+
SentanceVersion: p.String(0, "sentance version, range of 00 to 15"),
29+
Fleet: p.String(1, "fleet, range of 100 to 349"),
30+
UnitID: p.String(2, "subscriber unit id, range of 1000 to 4999"),
31+
Status: p.String(3, "subscriber unit status id, range of 10 to 99"),
32+
Extension: p.String(4, "reserved for future use, range of 00 to 99"),
33+
}, p.Err()
34+
}

pklid_test.go

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package nmea
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
var pklidtests = []struct {
10+
name string
11+
raw string
12+
err string
13+
msg PKLID
14+
}{
15+
{
16+
name: "typical sentance",
17+
raw: "$PKLID,00,100,2000,15,00,*6D",
18+
msg: PKLID{
19+
SentanceVersion: "00",
20+
Fleet: "100",
21+
UnitID: "2000",
22+
Status: "15",
23+
Extension: "00",
24+
},
25+
},
26+
}
27+
28+
func TestPKLID(t *testing.T) {
29+
for _, tt := range pklidtests {
30+
t.Run(tt.name, func(t *testing.T) {
31+
m, err := Parse(tt.raw)
32+
if tt.err != "" {
33+
assert.Error(t, err)
34+
assert.EqualError(t, err, tt.err)
35+
} else {
36+
assert.NoError(t, err)
37+
pklid := m.(PKLID)
38+
pklid.BaseSentence = BaseSentence{}
39+
assert.Equal(t, tt.msg, pklid)
40+
}
41+
})
42+
}
43+
}

pklsh.go

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package nmea
2+
3+
const (
4+
// TypePKLSH type for PKLSH sentances
5+
TypePKLSH = "KLSH"
6+
)
7+
8+
// PKLSH is a Kenwood Propritary sentance used for GPS data communications in FleetSync.
9+
//
10+
// adds UnitID and Fleet to $GPGLL sentance
11+
//
12+
// $PKLSH,<0>,<1>,<2>,<3>,<4>,<5>,<6>,<7>*hh<CR><LF>
13+
// Format: $PKLSH,xxxx.xxxx,x,xxxxx.xxxx,x,xxxxxx,x,xxx,xxxx,*xx<CR><LF>
14+
// Example: $PKLSH,4000.0000,N,13500.0000,E,021720,A,100,2000,*??
15+
type PKLSH struct {
16+
BaseSentence
17+
Latitude float64 // Latitude
18+
Longitude float64 // Longitude
19+
Time Time // Time Stamp
20+
Validity string // validity - A=valid, V=invalid
21+
Fleet string // 100 to 349
22+
UnitID string // 1000 to 4999
23+
}
24+
25+
// newPKLSH constructor
26+
func newPKLSH(s BaseSentence) (Sentence, error) {
27+
p := NewParser(s)
28+
p.AssertType(TypePKLSH)
29+
30+
return PKLSH{
31+
BaseSentence: s,
32+
Latitude: p.LatLong(0, 1, "latitude"),
33+
Longitude: p.LatLong(2, 3, "longitude"),
34+
Time: p.Time(4, "time"),
35+
Validity: p.EnumString(5, "validity", ValidGLL, InvalidGLL),
36+
Fleet: p.String(6, "fleet, range of 100 to 349"),
37+
UnitID: p.String(7, "subscriber unit id, range of 1000 to 4999"),
38+
}, p.Err()
39+
}

pklsh_test.go

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package nmea
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
var pklshtests = []struct {
10+
name string
11+
raw string
12+
err string
13+
msg PKLSH
14+
}{
15+
{
16+
name: "good sentence",
17+
raw: "$PKLSH,3926.7952,N,12000.5947,W,022732,A,100,2000*1A",
18+
msg: PKLSH{
19+
Latitude: MustParseLatLong("3926.7952 N"),
20+
Longitude: MustParseLatLong("12000.5947 W"),
21+
Time: Time{
22+
Valid: true,
23+
Hour: 2,
24+
Minute: 27,
25+
Second: 32,
26+
Millisecond: 0,
27+
},
28+
Validity: "A",
29+
Fleet: "100",
30+
UnitID: "2000",
31+
},
32+
},
33+
{
34+
name: "bad validity",
35+
raw: "$PKLSH,3926.7952,N,12000.5947,W,022732,D,100,2000*1F",
36+
err: "nmea: PKLSH invalid validity: D",
37+
},
38+
}
39+
40+
func TestPKLSH(t *testing.T) {
41+
for _, tt := range pklshtests {
42+
t.Run(tt.name, func(t *testing.T) {
43+
m, err := Parse(tt.raw)
44+
if tt.err != "" {
45+
assert.Error(t, err)
46+
assert.EqualError(t, err, tt.err)
47+
} else {
48+
assert.NoError(t, err)
49+
pklsh := m.(PKLSH)
50+
pklsh.BaseSentence = BaseSentence{}
51+
assert.Equal(t, tt.msg, pklsh)
52+
}
53+
})
54+
}
55+
}

pknds.go

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package nmea
2+
3+
import (
4+
"strings"
5+
)
6+
7+
const (
8+
// TypePKNDS type for PKLDS sentences
9+
TypePKNDS = "KNDS"
10+
)
11+
12+
// PKNDS is Kenwood propirtary sentance it is RMC with the addition of NEXTEDGE and status information.
13+
// http://aprs.gids.nl/nmea/#rmc
14+
//
15+
// Format: $PKNDS,hhmmss.ss,A,ddmm.mm,a,dddmm.mm,a,xxx.x,x,x.x,xxx,Uxxxx,xxx.xx,*hh<CR><LF>
16+
// Example: $PKNDS,220516,A,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W00,U00001,207,00,*6E
17+
type PKNDS struct {
18+
BaseSentence
19+
Time Time // Time Stamp
20+
Validity string // validity - A-ok, V-invalid
21+
Latitude float64 // Latitude
22+
Longitude float64 // Longitude
23+
Speed float64 // Speed in knots
24+
Course float64 // True course
25+
Date Date // Date
26+
Variation float64 // Magnetic variation
27+
SentanceVersion string // 00 to 15
28+
UnitID string // U00001 to U65519 or U00000001 to U16776415 (U is FIXED)
29+
Status string // 001 to 255
30+
Extension string // 00 to 99
31+
}
32+
33+
// newPKNDS constructor
34+
func newPKNDS(s BaseSentence) (Sentence, error) {
35+
p := NewParser(s)
36+
p.AssertType(TypePKNDS)
37+
m := PKNDS{
38+
BaseSentence: s,
39+
Time: p.Time(0, "time"),
40+
Validity: p.EnumString(1, "validity", ValidRMC, InvalidRMC),
41+
Latitude: p.LatLong(2, 3, "latitude"),
42+
Longitude: p.LatLong(4, 5, "longitude"),
43+
Speed: p.Float64(6, "speed"),
44+
Course: p.Float64(7, "course"),
45+
Date: p.Date(8, "date"),
46+
Variation: p.Float64(9, "variation"),
47+
SentanceVersion: p.String(10, "sentance version, range of 00 to 15"),
48+
UnitID: p.String(11, "unit ID, NXDN range U00001 to U65519, DMR range of U00000001 to U16776415"),
49+
Status: p.String(12, "subscriber unit status id, range of 001 to 255"),
50+
Extension: p.String(13, "reserved for future use, range of 00 to 99"),
51+
}
52+
if strings.HasPrefix(m.SentanceVersion, "W") == true {
53+
m.Variation = 0 - m.Variation
54+
}
55+
m.SentanceVersion = strings.TrimPrefix(strings.TrimPrefix(m.SentanceVersion, "W"), "E")
56+
return m, p.Err()
57+
}

0 commit comments

Comments
 (0)