Skip to content

Commit d882190

Browse files
authored
Add DTM, OSD, RSD, TLL, TTM, VBW sentences (#93)
1 parent e6fa0a7 commit d882190

15 files changed

+1016
-4
lines changed

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ At this moment, this library supports the following sentence types:
4646
| [DPT](https://gpsd.gitlab.io/gpsd/NMEA.html#_dpt_depth_of_water) | Depth of Water |
4747
| [DSC](./dsc.go) | Digital Selective Calling Information |
4848
| [DSE](./dse.go) | Expanded digital selective calling |
49+
| [DTM](https://gpsd.gitlab.io/gpsd/NMEA.html#_dtm_datum_reference) | Datum Reference |
4950
| [EVE](./eve.go) | General Event Message |
5051
| [FIR](./fir.go) | Fire Detection event with time and location |
5152
| [GGA](http://aprs.gids.nl/nmea/#gga) | GPS Positioning System Fix Data |
@@ -62,14 +63,19 @@ At this moment, this library supports the following sentence types:
6263
| [MTW](https://gpsd.gitlab.io/gpsd/NMEA.html#_mtw_mean_temperature_of_water) | Mean Temperature of Water |
6364
| [MWD](https://www.tronico.fi/OH6NT/docs/NMEA0183.pdf) | Wind Direction and Speed |
6465
| [MWV](https://gpsd.gitlab.io/gpsd/NMEA.html#_mwv_wind_speed_and_angle) | Wind Speed and Angle |
66+
| [OSD](https://gpsd.gitlab.io/gpsd/NMEA.html#_osd_own_ship_data) | Own Ship Data |
6567
| [RMB](https://gpsd.gitlab.io/gpsd/NMEA.html#_rmb_recommended_minimum_navigation_information) | Recommended Minimum Navigation Information |
6668
| [RMC](http://aprs.gids.nl/nmea/#rmc) | Recommended Minimum Specific GPS/Transit data |
6769
| [ROT](https://gpsd.gitlab.io/gpsd/NMEA.html#_rot_rate_of_turn) | Rate of turn |
6870
| [RPM](https://gpsd.gitlab.io/gpsd/NMEA.html#_rpm_revolutions) | Engine or Shaft revolutions and pitch |
6971
| [RSA](https://gpsd.gitlab.io/gpsd/NMEA.html#_rsa_rudder_sensor_angle) | Rudder Sensor Angle |
72+
| [RSD](https://gpsd.gitlab.io/gpsd/NMEA.html#_rsd_radar_system_data) | RADAR System Data |
7073
| [RTE](http://aprs.gids.nl/nmea/#rte) | Route |
7174
| [THS](http://www.nuovamarea.net/pytheas_9.html) | Actual vessel heading in degrees True and status |
75+
| [TLL](https://gpsd.gitlab.io/gpsd/NMEA.html#_tll_target_latitude_and_longitude) | Target latitude and longitude |
76+
| [TTM](https://gpsd.gitlab.io/gpsd/NMEA.html#_ttm_tracked_target_message) | Tracked Target Message |
7277
| [TXT](https://www.nmea.org/Assets/20160520%20txt%20amendment.pdf) | Sentence is for the transmission of text messages |
78+
| [VBW](https://gpsd.gitlab.io/gpsd/NMEA.html#_vbw_dual_groundwater_speed) | Dual Ground/Water Speed |
7379
| [VDM/VDO](https://gpsd.gitlab.io/gpsd/AIVDM.html) | Encapsulated binary payload (commonly used with AIS data) |
7480
| [VDR](https://gpsd.gitlab.io/gpsd/NMEA.html#_vdr_set_and_drift) | Set and Drift |
7581
| [VHW](https://www.tronico.fi/OH6NT/docs/NMEA0183.pdf) | Water Speed and Heading |

dtm.go

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package nmea
2+
3+
const (
4+
// TypeDTM type of DTM sentence for Datum Reference
5+
TypeDTM = "DTM"
6+
)
7+
8+
// DTM - Datum Reference
9+
// https://gpsd.gitlab.io/gpsd/NMEA.html#_dtm_datum_reference
10+
//
11+
// Format: $--DTM,ref,x,llll,c,llll,c,aaa,ref*hh<CR><LF>
12+
// Example: $GPDTM,W84,,0.0,N,0.0,E,0.0,W84*6F
13+
// Example: $GPDTM,W84,,00.0000,N,00.0000,W,,W84*53
14+
type DTM struct {
15+
BaseSentence
16+
LocalDatumCode string // Local datum code (W84,W72,S85,P90,999)
17+
LocalDatumSubcode string // Local datum subcode. May be blank.
18+
19+
LatitudeOffsetMinute float64 // Latitude offset (minutes) (negative if south)
20+
LongitudeOffsetMinute float64 // Longitude offset (minutes) (negative if west)
21+
22+
AltitudeOffsetMeters float64 // Altitude offset in meters
23+
DatumName string // Reference datum name. What’s usually seen here is "W84", the standard WGS84 datum used by GPS.
24+
}
25+
26+
// newDTM constructor
27+
func newDTM(s BaseSentence) (DTM, error) {
28+
p := NewParser(s)
29+
p.AssertType(TypeDTM)
30+
m := DTM{
31+
BaseSentence: s,
32+
LocalDatumCode: p.String(0, "local datum code"),
33+
LocalDatumSubcode: p.String(1, "local datum subcode"),
34+
35+
LatitudeOffsetMinute: p.Float64(2, "latitude offset minutes"),
36+
LongitudeOffsetMinute: p.Float64(4, "longitude offset minutes"),
37+
38+
AltitudeOffsetMeters: p.Float64(6, "altitude offset offset"),
39+
DatumName: p.String(7, "datum name"),
40+
}
41+
if p.String(3, "latitude offset direction") == South {
42+
m.LatitudeOffsetMinute *= -1
43+
}
44+
if p.String(5, "longitude offset direction") == West {
45+
m.LongitudeOffsetMinute *= -1
46+
}
47+
return m, p.Err()
48+
}

dtm_test.go

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package nmea
2+
3+
import (
4+
"github.com/stretchr/testify/assert"
5+
"testing"
6+
)
7+
8+
func TestDTM(t *testing.T) {
9+
var tests = []struct {
10+
name string
11+
raw string
12+
err string
13+
msg DTM
14+
}{
15+
{
16+
name: "good sentence 1",
17+
raw: "$GPDTM,W84,,0.0,N,0.0,E,0.0,W84*6F",
18+
msg: DTM{
19+
BaseSentence: BaseSentence{},
20+
LocalDatumCode: "W84",
21+
LocalDatumSubcode: "",
22+
LatitudeOffsetMinute: 0,
23+
LongitudeOffsetMinute: 0,
24+
AltitudeOffsetMeters: 0,
25+
DatumName: "W84",
26+
},
27+
},
28+
{
29+
name: "good sentence 2",
30+
raw: "$GPDTM,W84,X,00.1200,S,12.0000,W,100,W84*27",
31+
msg: DTM{
32+
BaseSentence: BaseSentence{},
33+
LocalDatumCode: "W84",
34+
LocalDatumSubcode: "X",
35+
LatitudeOffsetMinute: -0.12,
36+
LongitudeOffsetMinute: -12,
37+
AltitudeOffsetMeters: 100,
38+
DatumName: "W84",
39+
},
40+
},
41+
{
42+
name: "invalid nmea: LatitudeOffsetMinute",
43+
raw: "$GPDTM,W84,,x,N,0.0,E,0.0,W84*39",
44+
err: "nmea: GPDTM invalid latitude offset minutes: x",
45+
},
46+
{
47+
name: "invalid nmea: LongitudeOffsetMinute",
48+
raw: "$GPDTM,W84,,0.0,N,x,E,0.0,W84*39",
49+
err: "nmea: GPDTM invalid longitude offset minutes: x",
50+
},
51+
{
52+
name: "invalid nmea: AltitudeOffsetMeters",
53+
raw: "$GPDTM,W84,,0.0,N,0.0,E,x,W84*39",
54+
err: "nmea: GPDTM invalid altitude offset offset: x",
55+
},
56+
}
57+
for _, tt := range tests {
58+
t.Run(tt.name, func(t *testing.T) {
59+
m, err := Parse(tt.raw)
60+
if tt.err != "" {
61+
assert.Error(t, err)
62+
assert.EqualError(t, err, tt.err)
63+
} else {
64+
assert.NoError(t, err)
65+
mm := m.(DTM)
66+
mm.BaseSentence = BaseSentence{}
67+
assert.Equal(t, tt.msg, mm)
68+
}
69+
})
70+
}
71+
}

osd.go

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package nmea
2+
3+
const (
4+
// TypeOSD type for OSD sentence for Own Ship Data
5+
TypeOSD = "OSD"
6+
7+
// OSDReferenceBottomTrackingLog is reference for bottom tracking log
8+
OSDReferenceBottomTrackingLog = "B"
9+
// OSDReferenceManual is reference for manually entered
10+
OSDReferenceManual = "M"
11+
// OSDReferenceWaterReferenced is reference for water referenced
12+
OSDReferenceWaterReferenced = "W"
13+
// OSDReferenceRadarTracking is reference for radar tracking of fixed target
14+
OSDReferenceRadarTracking = "R"
15+
// OSDReferencePositioningSystemGroundReference is reference for positioning system ground reference
16+
OSDReferencePositioningSystemGroundReference = "P"
17+
)
18+
19+
// OSD - Own Ship Data
20+
// https://gpsd.gitlab.io/gpsd/NMEA.html#_osd_own_ship_data
21+
// https://github.com/nohal/OpenCPN/wiki/ARPA-targets-tracking-implementation#osd---own-ship-data
22+
//
23+
// Format: $--OSD,x.x,A,x.x,a,x.x,a,x.x,x.x,a*hh<CR><LF>
24+
// Example: $RAOSD,179.0,A,179.0,M,00.0,M,,,N*76
25+
type OSD struct {
26+
BaseSentence
27+
// Heading is Heading in degrees
28+
Heading float64
29+
30+
// HeadingStatus is Heading status
31+
// * A - data valid
32+
// * V - data invalid
33+
HeadingStatus string
34+
35+
// VesselTrueCourse is Vessel Course, degrees True
36+
VesselTrueCourse float64
37+
38+
// CourseReference is Course Reference, B/M/W/R/P
39+
// * B - bottom tracking log
40+
// * M - manually entered
41+
// * W - water referenced
42+
// * R - radar tracking of fixed target
43+
// * P - positioning system ground reference
44+
CourseReference string
45+
46+
// VesselSpeed is Vessel Speed
47+
VesselSpeed float64
48+
49+
// SpeedReference is Speed Reference, B/M/W/R/P
50+
// * B - bottom tracking log
51+
// * M - manually entered
52+
// * W - water referenced
53+
// * R - radar tracking of fixed target
54+
// * P - positioning system ground reference.
55+
SpeedReference string
56+
57+
// VesselSetTrue is Vessel Set, degrees True - Manually entered
58+
VesselSetTrue float64
59+
60+
// VesselDrift is Vessel drift (speed) - Manually entered
61+
VesselDrift float64
62+
63+
// SpeedUnits is Speed Units
64+
// * K - km/h
65+
// * N - Knots
66+
// * S - statute miles/h
67+
SpeedUnits string
68+
}
69+
70+
// newOSD constructor
71+
func newOSD(s BaseSentence) (OSD, error) {
72+
p := NewParser(s)
73+
p.AssertType(TypeOSD)
74+
m := OSD{
75+
BaseSentence: s,
76+
Heading: p.Float64(0, "heading"),
77+
HeadingStatus: p.EnumString(1, "heading status", StatusValid, StatusInvalid),
78+
VesselTrueCourse: p.Float64(2, "vessel course true"),
79+
CourseReference: p.EnumString(
80+
3,
81+
"course reference",
82+
OSDReferenceBottomTrackingLog,
83+
OSDReferenceManual,
84+
OSDReferenceWaterReferenced,
85+
OSDReferenceRadarTracking,
86+
OSDReferencePositioningSystemGroundReference,
87+
),
88+
VesselSpeed: p.Float64(4, "vessel speed"),
89+
SpeedReference: p.EnumString(
90+
5,
91+
"speed reference",
92+
OSDReferenceBottomTrackingLog,
93+
OSDReferenceManual,
94+
OSDReferenceWaterReferenced,
95+
OSDReferenceRadarTracking,
96+
OSDReferencePositioningSystemGroundReference,
97+
),
98+
VesselSetTrue: p.Float64(6, "vessel set"),
99+
VesselDrift: p.Float64(7, "vessel drift"),
100+
SpeedUnits: p.EnumString(8, "speed units", DistanceUnitKilometre, DistanceUnitNauticalMile, DistanceUnitStatuteMile),
101+
}
102+
return m, p.Err()
103+
}

osd_test.go

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package nmea
2+
3+
import (
4+
"github.com/stretchr/testify/assert"
5+
"testing"
6+
)
7+
8+
func TestOSD(t *testing.T) {
9+
var tests = []struct {
10+
name string
11+
raw string
12+
err string
13+
msg OSD
14+
}{
15+
{
16+
name: "good sentence",
17+
raw: "$RAOSD,179.0,A,179.0,M,00.0,M,,,N*76",
18+
msg: OSD{
19+
BaseSentence: BaseSentence{},
20+
Heading: 179,
21+
HeadingStatus: "A",
22+
VesselTrueCourse: 179,
23+
CourseReference: "M",
24+
VesselSpeed: 0,
25+
SpeedReference: "M",
26+
VesselSetTrue: 0,
27+
VesselDrift: 0,
28+
SpeedUnits: "N",
29+
},
30+
},
31+
{
32+
name: "invalid nmea: Heading",
33+
raw: "$RAOSD,x179.0,A,179.0,M,00.0,M,,,N*0e",
34+
err: "nmea: RAOSD invalid heading: x179.0",
35+
},
36+
{
37+
name: "invalid nmea: HeadingStatus",
38+
raw: "$RAOSD,179.0,xA,179.0,M,00.0,M,,,N*0e",
39+
err: "nmea: RAOSD invalid heading status: xA",
40+
},
41+
{
42+
name: "invalid nmea: VesselTrueCourse",
43+
raw: "$RAOSD,179.0,A,x179.0,M,00.0,M,,,N*0e",
44+
err: "nmea: RAOSD invalid vessel course true: x179.0",
45+
},
46+
{
47+
name: "invalid nmea: CourseReference",
48+
raw: "$RAOSD,179.0,A,179.0,xM,00.0,M,,,N*0e",
49+
err: "nmea: RAOSD invalid course reference: xM",
50+
},
51+
{
52+
name: "invalid nmea: VesselSpeed",
53+
raw: "$RAOSD,179.0,A,179.0,M,x00.0,M,,,N*0e",
54+
err: "nmea: RAOSD invalid vessel speed: x00.0",
55+
},
56+
{
57+
name: "invalid nmea: SpeedReference",
58+
raw: "$RAOSD,179.0,A,179.0,M,00.0,xM,,,N*0e",
59+
err: "nmea: RAOSD invalid speed reference: xM",
60+
},
61+
{
62+
name: "invalid nmea: VesselSetTrue",
63+
raw: "$RAOSD,179.0,A,179.0,M,00.0,M,x,,N*0e",
64+
err: "nmea: RAOSD invalid vessel set: x",
65+
},
66+
{
67+
name: "invalid nmea: VesselDrift",
68+
raw: "$RAOSD,179.0,A,179.0,M,00.0,M,,x,N*0e",
69+
err: "nmea: RAOSD invalid vessel drift: x",
70+
},
71+
{
72+
name: "invalid nmea: SpeedUnits",
73+
raw: "$RAOSD,179.0,A,179.0,M,00.0,M,,,xN*0e",
74+
err: "nmea: RAOSD invalid speed units: xN",
75+
},
76+
}
77+
for _, tt := range tests {
78+
t.Run(tt.name, func(t *testing.T) {
79+
m, err := Parse(tt.raw)
80+
if tt.err != "" {
81+
assert.Error(t, err)
82+
assert.EqualError(t, err, tt.err)
83+
} else {
84+
assert.NoError(t, err)
85+
mm := m.(OSD)
86+
mm.BaseSentence = BaseSentence{}
87+
assert.Equal(t, tt.msg, mm)
88+
}
89+
})
90+
}
91+
}

0 commit comments

Comments
 (0)