Skip to content

Commit

Permalink
Merge 018a46d into 9e4de63
Browse files Browse the repository at this point in the history
  • Loading branch information
simeonmiteff committed Aug 21, 2020
2 parents 9e4de63 + 018a46d commit a61c13d
Show file tree
Hide file tree
Showing 4 changed files with 267 additions and 0 deletions.
36 changes: 36 additions & 0 deletions README.md
Expand Up @@ -7,6 +7,7 @@ This is a NMEA library for the Go programming language (Golang).
## Features

- Parse individual NMEA 0183 sentences
- Support for sentences with NMEA 4.10 "TAG Blocks"
- Register custom parser for unsupported sentence types
- User-friendly MIT license

Expand Down Expand Up @@ -106,6 +107,41 @@ Date: 13/06/94
Variation: -4.200000
```

### TAG Blocks

NMEA 4.10 TAG Block values can be accessed via the message's `TagBlock` struct:

```go
package main

import (
"fmt"
"log"
"time"
"github.com/adrianmo/go-nmea"
)

func main() {
sentence := "\\s:Satelite_1,c:1553390539*62\\!AIVDM,1,1,,A,13M@ah0025QdPDTCOl`K6`nV00Sv,0*52"
s, err := nmea.Parse(sentence)
if err != nil {
log.Fatal(err)
}
parsed := s.(nmea.VDMVDO)
fmt.Printf("TAG Block timestamp: %v\n", time.Unix(parsed.TagBlock.Time, 0))
fmt.Printf("TAG Block source: %v\n", parsed.TagBlock.Source)
}
```

Output (locale/time zone dependent):

```
$ go run main/main.go
TAG Block timestamp: 2019-03-24 14:22:19 +1300 NZDT
TAG Block source: Satelite_1
```

### Custom message parsing

If you need to parse a message not supported by the library you can implement your own message parsing.
Expand Down
6 changes: 6 additions & 0 deletions sentence.go
Expand Up @@ -43,6 +43,7 @@ type BaseSentence struct {
Fields []string // Array of fields
Checksum string // The Checksum
Raw string // The raw NMEA sentence received
TagBlock TagBlock // NMEA tagblock
}

// Prefix returns the talker and type of message
Expand All @@ -66,6 +67,10 @@ func (s BaseSentence) String() string { return s.Raw }
// parseSentence parses a raw message into it's fields
func parseSentence(raw string) (BaseSentence, error) {
raw = strings.TrimSpace(raw)
tagBlock, raw, err := parseTagBlock(raw)
if err != nil {
return BaseSentence{}, err
}
startIndex := strings.IndexAny(raw, SentenceStart+SentenceStartEncapsulated)
if startIndex != 0 {
return BaseSentence{}, fmt.Errorf("nmea: sentence does not start with a '$' or '!'")
Expand All @@ -92,6 +97,7 @@ func parseSentence(raw string) (BaseSentence, error) {
Fields: fields[1:],
Checksum: checksumRaw,
Raw: raw,
TagBlock: tagBlock,
}, nil
}

Expand Down
95 changes: 95 additions & 0 deletions tagblock.go
@@ -0,0 +1,95 @@
package nmea

import (
"fmt"
"strconv"
"strings"
)

// TagBlock struct
type TagBlock struct {
Head string // *
Time int64 // TypeUnixTime unix timestamp (unit is likely to be s, but might be ms, YMMV), parameter: -c
RelativeTime int64 // TypeRelativeTime relative time, parameter: -r
Destination string // TypeDestinationID destination identification 15 char max, parameter: -d
Grouping string // TypeGrouping sentence grouping, parameter: -g
LineCount int64 // TypeLineCount line count, parameter: -n
Source string // TypeSourceID source identification 15 char max, parameter: -s
Text string // TypeTextString valid character string, parameter -t
}

func parseInt64(raw string) (int64, error) {
i, err := strconv.ParseInt(raw, 10, 64)
if err != nil {
return 0, fmt.Errorf("nmea: tagblock unable to parse uint64 [%s]", raw)
}
return i, nil
}

// parseTagBlock adds support for tagblocks
// https://gpsd.gitlab.io/gpsd/AIVDM.html#_nmea_tag_blocks
func parseTagBlock(raw string) (TagBlock, string, error) {
parts := strings.SplitN(raw, `\`, 3)
if len(parts) != 3 {
// Not a TAG Block sentence, so don't return error and pass through raw unmodified
return TagBlock{}, raw, nil
}

tagBlock := TagBlock{}
tagBlock.Head = parts[0]
tags := parts[1]
raw = parts[2] // Pass through the rest of the sentence without the TAG Block

sumSepIndex := strings.Index(tags, ChecksumSep)
if sumSepIndex == -1 {
return TagBlock{}, "", fmt.Errorf("nmea: tagblock does not contain checksum separator")
}

var (
fieldsRaw = tags[0:sumSepIndex]
checksumRaw = strings.ToUpper(tags[sumSepIndex+1:])
checksum = Checksum(fieldsRaw)
err error
)

// Validate the checksum
if checksum != checksumRaw {
return TagBlock{}, "", fmt.Errorf("nmea: tagblock checksum mismatch [%s != %s]", checksum, checksumRaw)
}

items := strings.Split(tags[:sumSepIndex], ",")
for _, item := range items {
parts := strings.SplitN(item, ":", 2)
if len(parts) != 2 {
return TagBlock{}, "",
fmt.Errorf("nmea: tagblock field is malformed (should be <key>:<value>) [%s]", item)
}
key, value := parts[0], parts[1]
switch key {
case "c": // UNIX timestamp
tagBlock.Time, err = parseInt64(value)
if err != nil {
return TagBlock{}, "", err
}
case "d": // Destination ID
tagBlock.Destination = value
case "g": // Grouping
tagBlock.Grouping = value
case "n": // Line count
tagBlock.LineCount, err = parseInt64(value)
if err != nil {
return TagBlock{}, "", err
}
case "r": // Relative time
tagBlock.RelativeTime, err = parseInt64(value)
if err != nil {
return TagBlock{}, "", err
}
case "s": // Source ID
tagBlock.Source = value
case "t": // Text string
tagBlock.Text = value
}
}
return tagBlock, raw, nil
}
130 changes: 130 additions & 0 deletions tagblock_test.go
@@ -0,0 +1,130 @@
package nmea

import (
"testing"

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

var tagblocktests = []struct {
name string
raw string
err string
msg TagBlock
}{
{

name: "Test NMEA tag block",
raw: "\\s:Satelite_1,c:1553390539*62\\!AIVDM,1,1,,A,13M@ah0025QdPDTCOl`K6`nV00Sv,0*52",
msg: TagBlock{
Time: 1553390539,
Source: "Satelite_1",
},
},
{

name: "Test NMEA tag block with head",
raw: "UdPbC?\\s:satelite,c:1564827317*25\\!AIVDM,1,1,,A,19NSRaP02A0fo91kwnaMKbjR08:J,0*15",
msg: TagBlock{
Time: 1564827317,
Source: "satelite",
Head: "UdPbC?",
},
},
{

name: "Test unknown tag",
raw: "UdPbC?\\x:NorSat_1,c:1564827317*42\\!AIVDM,1,1,,A,19NSRaP02A0fo91kwnaMKbjR08:J,0*15",
msg: TagBlock{
Time: 1564827317,
Source: "",
Head: "UdPbC?",
},
},
{
name: "Test unix timestamp",
raw: "UdPbC?\\x:NorSat_1,c:1564827317*42\\!AIVDM,1,1,,A,19NSRaP02A0fo91kwnaMKbjR08:J,0*15",
msg: TagBlock{
Time: 1564827317,
Source: "",
Head: "UdPbC?",
},
},
{

name: "Test milliseconds timestamp",
raw: "UdPbC?\\x:NorSat_1,c:1564827317000*72\\!AIVDM,1,1,,A,19NSRaP02A0fo91kwnaMKbjR08:J,0*15",
msg: TagBlock{
Time: 1564827317000,
Source: "",
Head: "UdPbC?",
},
},
{

name: "Test all input types",
raw: "UdPbC?\\s:satelite,c:1564827317,r:1553390539,d:ara,g:bulk,n:13,t:helloworld*3F\\!AIVDM,1,1,,A,19NSRaP02A0fo91kwnaMKbjR08:J,0*15",
msg: TagBlock{
Time: 1564827317,
RelativeTime: 1553390539,
Destination: "ara",
Grouping: "bulk",
Source: "satelite",
Head: "UdPbC?",
Text: "helloworld",
LineCount: 13,
},
},
{

name: "Test empty tag in tagblock",
raw: "UdPbC?\\s:satelite,,r:1553390539,d:ara,g:bulk,n:13,t:helloworld*68\\!AIVDM,1,1,,A,19NSRaP02A0fo91kwnaMKbjR08:J,0*15",
err: "nmea: tagblock field is malformed (should be <key>:<value>) []",
},
{

name: "Test Invalid checksum",
raw: "UdPbC?\\s:satelite,c:1564827317*49\\!AIVDM,1,1,,A,19NSRaP02A0fo91kwnaMKbjR08:J,0*15",
err: "nmea: tagblock checksum mismatch [25 != 49]",
},
{

name: "Test no checksum",
raw: "UdPbC?\\s:satelite,c:156482731749\\!AIVDM,1,1,,A,19NSRaP02A0fo91kwnaMKbjR08:J,0*15",
err: "nmea: tagblock does not contain checksum separator",
},
{

name: "Test invalid timestamp",
raw: "UdPbC?\\s:satelite,c:gjadslkg*30\\!AIVDM,1,1,,A,19NSRaP02A0fo91kwnaMKbjR08:J,0*15",
err: "nmea: tagblock unable to parse uint64 [gjadslkg]",
},
{

name: "Test invalid linecount",
raw: "UdPbC?\\s:satelite,n:gjadslkg*3D\\!AIVDM,1,1,,A,19NSRaP02A0fo91kwnaMKbjR08:J,0*15",
err: "nmea: tagblock unable to parse uint64 [gjadslkg]",
},
{

name: "Test invalid relative time",
raw: "UdPbC?\\s:satelite,r:gjadslkg*21\\!AIVDM,1,1,,A,19NSRaP02A0fo91kwnaMKbjR08:J,0*15",
err: "nmea: tagblock unable to parse uint64 [gjadslkg]",
},
}

func TestTagBlock(t *testing.T) {
for _, tt := range tagblocktests {
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)
vdm := m.(VDMVDO)
assert.Equal(t, tt.msg, vdm.BaseSentence.TagBlock)
}
})
}
}

0 comments on commit a61c13d

Please sign in to comment.