Skip to content

Commit

Permalink
Add Kenwood proprietary sentences $PKLDS, $PKNDS, $PKLID, $PKNID, $PK…
Browse files Browse the repository at this point in the history
…LSH, $PKNSH, $PKWDWPL (#118)

These proprietary NMEA sentences have been captured from Various Kenwood LMR radios. and is parity with pynema2
  • Loading branch information
dBitech authored Dec 9, 2024
1 parent cd0f55b commit e4d24e3
Show file tree
Hide file tree
Showing 16 changed files with 716 additions and 0 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ on [IEC 61162-1:2016 (Edition 5.0 2016-08)](https://webstore.iec.ch/publication/
| HSS | Hull stress surveillance systems | |
| HTC | Heading/track control command | |
| HTD | Heading /track control data | |
| KLDS | Kenwood LMR - FleetSync AVL | |
| KLID | Kenwood LMR - FleetSync | |
| KLSH | Kenwood LMR - FleetSync AVL | |
| KNDS | Kenwood LMR - Digital AVL | |
| KNID | Kenwood LMR - Digital | |
| KNSH | Kenwood LMR - Digital AVL | |
| KWDWPL | Kenwood Waypoint Location - Amateur Radio | [direwolf](https://github.com/wb2osz/direwolf/blob/master/src/waypoint.c) |
| LR1 | AIS long-range reply sentence 1 | |
| LR2 | AIS long-range reply sentence 2 | |
| LR3 | AIS long-range reply sentence 3 | |
Expand Down
59 changes: 59 additions & 0 deletions pklds.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package nmea

import (
"strings"
)

const (
// TypePKLDS type for PKLDS sentences
TypePKLDS = "KLDS"
)

// PKLDS is Kenwood propirtary sentance it is RMC with the addition of Fleetsync ID and status information.
// http://aprs.gids.nl/nmea/#rmc
//
// Format: $PKLDS,hhmmss.ss,A,ddmm.mm,a,dddmm.mm,a,x.x,x.x,xxxx,x.x,a*hh<CR><LF>
// Example: $PKLDS,220516,A,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W00,100,2000,25,00*6E
type PKLDS struct {
BaseSentence
Time Time // Time Stamp
Validity string // validity - A-ok, V-invalid
Latitude float64 // Latitude
Longitude float64 // Longitude
Speed float64 // Speed in knots
Course float64 // True course
Date Date // Date
Variation float64 // Magnetic variation
SentanceVersion string // 00 to 15
Fleet string // 100 to 349
UnitID string // 1000 to 4999
Status string // 10 to 99
Extension string // 00 to 99
}

// newPKLDS constructor
func newPKLDS(s BaseSentence) (Sentence, error) {
p := NewParser(s)
p.AssertType(TypePKLDS)
m := PKLDS{
BaseSentence: s,
Time: p.Time(0, "time"),
Validity: p.EnumString(1, "validity", ValidRMC, InvalidRMC),
Latitude: p.LatLong(2, 3, "latitude"),
Longitude: p.LatLong(4, 5, "longitude"),
Speed: p.Float64(6, "speed"),
Course: p.Float64(7, "course"),
Date: p.Date(8, "date"),
Variation: p.Float64(9, "variation"),
SentanceVersion: p.String(10, "sentance version, range of 00 to 15"),
Fleet: p.String(11, "fleet, range of 100 to 349"),
UnitID: p.String(12, "subscriber unit id, range of 1000 to 4999"),
Status: p.String(13, "subscriber unit status id, range of 10 to 99"),
Extension: p.String(14, "reserved for future use, range of 00 to 99"),
}
if strings.HasPrefix(m.SentanceVersion, "W") == true {
m.Variation = 0 - m.Variation
}
m.SentanceVersion = strings.TrimPrefix(strings.TrimPrefix(m.SentanceVersion, "W"), "E")
return m, p.Err()
}
76 changes: 76 additions & 0 deletions pklds_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package nmea

import (
"testing"

"github.com/stretchr/testify/assert"
)

var pkldstests = []struct {
name string
raw string
err string
msg PKLDS
}{
{
name: "good sentence West",
raw: "$PKLDS,220516,A,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W00,100,2000,15,00,*60",
msg: PKLDS{
Time: Time{true, 22, 05, 16, 0},
Validity: "A",
Latitude: MustParseGPS("5133.82 N"),
Longitude: MustParseGPS("00042.24 W"),
Speed: 173.8,
Course: 231.8,
Date: Date{true, 13, 06, 94},
Variation: -4.2,
SentanceVersion: "00",
Fleet: "100",
UnitID: "2000",
Status: "15",
Extension: "00",
},
},
{
name: "good sentence East",
raw: "$PKLDS,220516,A,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,E00,100,2000,15,00,*72",
msg: PKLDS{
Time: Time{true, 22, 05, 16, 0},
Validity: "A",
Latitude: MustParseGPS("5133.82 N"),
Longitude: MustParseGPS("00042.24 W"),
Speed: 173.8,
Course: 231.8,
Date: Date{true, 13, 06, 94},
Variation: 4.2,
SentanceVersion: "00",
Fleet: "100",
UnitID: "2000",
Status: "15",
Extension: "00",
},
},

{
name: "bad sentence",
raw: "$PKLDS,220516,D,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W00,100,2000,15,00,*65",
err: "nmea: PKLDS invalid validity: D",
},
}

func TestPKLDS(t *testing.T) {
for _, tt := range pkldstests {
t.Run(tt.name, func(t *testing.T) {
m, err := Parse(tt.raw)
if tt.err != "" {
assert.Error(t, err)
assert.EqualError(t, err, tt.err)
} else {
assert.NoError(t, err)
pklds := m.(PKLDS)
pklds.BaseSentence = BaseSentence{}
assert.Equal(t, tt.msg, pklds)
}
})
}
}
34 changes: 34 additions & 0 deletions pklid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package nmea

const (
// TypePKLID type for PKLID sentances
TypePKLID = "KLID"
)

// PKLID is a Kenwood Propritary sentance used for GPS data communications in FleetSync.
// $PKLID,<0>,<1>,<2>,<3>,<4>*hh<CR><LF>
// Format: $PKLID,xx,xxx,xxxx,xx,xx,*xx<CR><LF>
// Example: $PKLID,00,100,2000,15,00,*??
type PKLID struct {
BaseSentence
SentanceVersion string // 00 to 15
Fleet string // 100 to 349
UnitID string // 1000 to 4999
Status string // 10 to 99
Extension string // 00 to 99
}

// newPKLID constructor
func newPKLID(s BaseSentence) (Sentence, error) {
p := NewParser(s)
p.AssertType(TypePKLID)

return PKLID{
BaseSentence: s,
SentanceVersion: p.String(0, "sentance version, range of 00 to 15"),
Fleet: p.String(1, "fleet, range of 100 to 349"),
UnitID: p.String(2, "subscriber unit id, range of 1000 to 4999"),
Status: p.String(3, "subscriber unit status id, range of 10 to 99"),
Extension: p.String(4, "reserved for future use, range of 00 to 99"),
}, p.Err()
}
43 changes: 43 additions & 0 deletions pklid_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package nmea

import (
"testing"

"github.com/stretchr/testify/assert"
)

var pklidtests = []struct {
name string
raw string
err string
msg PKLID
}{
{
name: "typical sentance",
raw: "$PKLID,00,100,2000,15,00,*6D",
msg: PKLID{
SentanceVersion: "00",
Fleet: "100",
UnitID: "2000",
Status: "15",
Extension: "00",
},
},
}

func TestPKLID(t *testing.T) {
for _, tt := range pklidtests {
t.Run(tt.name, func(t *testing.T) {
m, err := Parse(tt.raw)
if tt.err != "" {
assert.Error(t, err)
assert.EqualError(t, err, tt.err)
} else {
assert.NoError(t, err)
pklid := m.(PKLID)
pklid.BaseSentence = BaseSentence{}
assert.Equal(t, tt.msg, pklid)
}
})
}
}
39 changes: 39 additions & 0 deletions pklsh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package nmea

const (
// TypePKLSH type for PKLSH sentances
TypePKLSH = "KLSH"
)

// PKLSH is a Kenwood Propritary sentance used for GPS data communications in FleetSync.
//
// adds UnitID and Fleet to $GPGLL sentance
//
// $PKLSH,<0>,<1>,<2>,<3>,<4>,<5>,<6>,<7>*hh<CR><LF>
// Format: $PKLSH,xxxx.xxxx,x,xxxxx.xxxx,x,xxxxxx,x,xxx,xxxx,*xx<CR><LF>
// Example: $PKLSH,4000.0000,N,13500.0000,E,021720,A,100,2000,*??
type PKLSH struct {
BaseSentence
Latitude float64 // Latitude
Longitude float64 // Longitude
Time Time // Time Stamp
Validity string // validity - A=valid, V=invalid
Fleet string // 100 to 349
UnitID string // 1000 to 4999
}

// newPKLSH constructor
func newPKLSH(s BaseSentence) (Sentence, error) {
p := NewParser(s)
p.AssertType(TypePKLSH)

return PKLSH{
BaseSentence: s,
Latitude: p.LatLong(0, 1, "latitude"),
Longitude: p.LatLong(2, 3, "longitude"),
Time: p.Time(4, "time"),
Validity: p.EnumString(5, "validity", ValidGLL, InvalidGLL),
Fleet: p.String(6, "fleet, range of 100 to 349"),
UnitID: p.String(7, "subscriber unit id, range of 1000 to 4999"),
}, p.Err()
}
55 changes: 55 additions & 0 deletions pklsh_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package nmea

import (
"testing"

"github.com/stretchr/testify/assert"
)

var pklshtests = []struct {
name string
raw string
err string
msg PKLSH
}{
{
name: "good sentence",
raw: "$PKLSH,3926.7952,N,12000.5947,W,022732,A,100,2000*1A",
msg: PKLSH{
Latitude: MustParseLatLong("3926.7952 N"),
Longitude: MustParseLatLong("12000.5947 W"),
Time: Time{
Valid: true,
Hour: 2,
Minute: 27,
Second: 32,
Millisecond: 0,
},
Validity: "A",
Fleet: "100",
UnitID: "2000",
},
},
{
name: "bad validity",
raw: "$PKLSH,3926.7952,N,12000.5947,W,022732,D,100,2000*1F",
err: "nmea: PKLSH invalid validity: D",
},
}

func TestPKLSH(t *testing.T) {
for _, tt := range pklshtests {
t.Run(tt.name, func(t *testing.T) {
m, err := Parse(tt.raw)
if tt.err != "" {
assert.Error(t, err)
assert.EqualError(t, err, tt.err)
} else {
assert.NoError(t, err)
pklsh := m.(PKLSH)
pklsh.BaseSentence = BaseSentence{}
assert.Equal(t, tt.msg, pklsh)
}
})
}
}
57 changes: 57 additions & 0 deletions pknds.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package nmea

import (
"strings"
)

const (
// TypePKNDS type for PKLDS sentences
TypePKNDS = "KNDS"
)

// PKNDS is Kenwood propirtary sentance it is RMC with the addition of NEXTEDGE and status information.
// http://aprs.gids.nl/nmea/#rmc
//
// Format: $PKNDS,hhmmss.ss,A,ddmm.mm,a,dddmm.mm,a,xxx.x,x,x.x,xxx,Uxxxx,xxx.xx,*hh<CR><LF>
// Example: $PKNDS,220516,A,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W00,U00001,207,00,*6E
type PKNDS struct {
BaseSentence
Time Time // Time Stamp
Validity string // validity - A-ok, V-invalid
Latitude float64 // Latitude
Longitude float64 // Longitude
Speed float64 // Speed in knots
Course float64 // True course
Date Date // Date
Variation float64 // Magnetic variation
SentanceVersion string // 00 to 15
UnitID string // U00001 to U65519 or U00000001 to U16776415 (U is FIXED)
Status string // 001 to 255
Extension string // 00 to 99
}

// newPKNDS constructor
func newPKNDS(s BaseSentence) (Sentence, error) {
p := NewParser(s)
p.AssertType(TypePKNDS)
m := PKNDS{
BaseSentence: s,
Time: p.Time(0, "time"),
Validity: p.EnumString(1, "validity", ValidRMC, InvalidRMC),
Latitude: p.LatLong(2, 3, "latitude"),
Longitude: p.LatLong(4, 5, "longitude"),
Speed: p.Float64(6, "speed"),
Course: p.Float64(7, "course"),
Date: p.Date(8, "date"),
Variation: p.Float64(9, "variation"),
SentanceVersion: p.String(10, "sentance version, range of 00 to 15"),
UnitID: p.String(11, "unit ID, NXDN range U00001 to U65519, DMR range of U00000001 to U16776415"),
Status: p.String(12, "subscriber unit status id, range of 001 to 255"),
Extension: p.String(13, "reserved for future use, range of 00 to 99"),
}
if strings.HasPrefix(m.SentanceVersion, "W") == true {
m.Variation = 0 - m.Variation
}
m.SentanceVersion = strings.TrimPrefix(strings.TrimPrefix(m.SentanceVersion, "W"), "E")
return m, p.Err()
}
Loading

0 comments on commit e4d24e3

Please sign in to comment.