Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for TagBlocks #59

Closed
wants to merge 10 commits into from
8 changes: 8 additions & 0 deletions sentence.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,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 @@ -57,6 +58,11 @@ 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 @@ -77,12 +83,14 @@ func parseSentence(raw string) (BaseSentence, error) {
"nmea: sentence checksum mismatch [%s != %s]", checksum, checksumRaw)
}
talker, typ := parsePrefix(fields[0])

return BaseSentence{
Talker: talker,
Type: typ,
Fields: fields[1:],
Checksum: checksumRaw,
Raw: raw,
TagBlock: tagBlock,
}, nil
}

Expand Down
114 changes: 114 additions & 0 deletions tagblock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package nmea

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

const (
// TypeUnixTime unix timestamp, parameter: -c
TypeUnixTime = "c"
// TypeDestinationID destination identification 15char max, parameter: -d
TypeDestinationID = "d"
// TypeGrouping sentence grouping, parameter: -g
TypeGrouping = "g"
// TypeLineCount linecount, parameter: -n
TypeLineCount = "n"
// TypeRelativeTime relative time time, paremeter: -r
TypeRelativeTime = "r"
// TypeSourceID source identification 15char max, paremter: -s
TypeSourceID = "s"
// TypeTextString valid character string, parameter -t
TypeTextString = "t"
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need these constants in the public API. Since they're only used in the one spot, I think it would be better to just inline them into the switch.

Move the comments from these types to the TagBlock struct fields.


var (
// tagBlockRegexp matches nmea tag blocks
tagBlockRegexp = regexp.MustCompile(`^(.*)\\(\S+)\\(.*)`)
)

// TagBlock struct
type TagBlock struct {
Head string // *
Time int64 // -c
RelativeTime int64 // -r
Destination string // -d 15 char max
Grouping string // -g nummeric string
LineCount int64 // -n int
Source string // -s 15 char max
Text string // -t Variable length text
}

func parseUint(raw string) (int64, error) {
klyve marked this conversation as resolved.
Show resolved Hide resolved
i, err := strconv.ParseInt(raw[2:], 10, 32)
if err != nil {
return 0, fmt.Errorf("nmea: tagblock unable to parse uint32 [%s]", raw)
}
return i, nil
}

// parseTagBlock adds support for tagblocks
// https://rietman.wordpress.com/2016/09/17/nemastudio-now-supports-the-nmea-0183-tag-block/
func parseTagBlock(raw string) (TagBlock, string, error) {
matches := tagBlockRegexp.FindStringSubmatch(raw)
if matches == nil {
return TagBlock{}, raw, nil
}

tagBlock := TagBlock{}
raw = matches[3]
tags := matches[2]
tagBlock.Head = matches[1]

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 = xorChecksum(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 {
if len(item) == 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be if len(item) < 2 { ?

Actually, I think a cleaner way would be to just use strings.Split on it.

parts := strings.SplitN(item, ":", 2)
if len(parts) != 2 {
	continue
}
key, value := parts[0], parts[1]

continue
}
switch item[:1] {
case TypeUnixTime:
tagBlock.Time, err = parseUint(item)
if err != nil {
return tagBlock, raw, err
}
case TypeDestinationID:
tagBlock.Destination = item[2:]
case TypeGrouping:
tagBlock.Grouping = item[2:]
case TypeLineCount:
tagBlock.LineCount, err = parseUint(item)
if err != nil {
return tagBlock, raw, err
}
case TypeRelativeTime:
tagBlock.RelativeTime, err = parseUint(item)
if err != nil {
return tagBlock, raw, err
}
case TypeSourceID:
tagBlock.Source = item[2:]
case TypeTextString:
tagBlock.Text = item[2:]
}
}
return tagBlock, raw, nil
}
121 changes: 121 additions & 0 deletions tagblock_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
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 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",
msg: TagBlock{
Time: 0,
RelativeTime: 1553390539,
Destination: "ara",
Grouping: "bulk",
Source: "satelite",
Head: "UdPbC?",
Text: "helloworld",
LineCount: 13,
},
//err: "nmea: tagblock checksum mismatch [25 != 49]",
Copy link
Collaborator

@icholy icholy Aug 8, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't commit commented-out code

},
{

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 uint32 [c: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 uint32 [n: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 uint32 [r: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)
}
})
}
}