-
Notifications
You must be signed in to change notification settings - Fork 80
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
sentences for NMEA2000 over NMEA0183 (#106)
* sentences for NMEA2000 over NMEA0183
- Loading branch information
Showing
6 changed files
with
286 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package nmea | ||
|
||
import ( | ||
"encoding/hex" | ||
"fmt" | ||
"strconv" | ||
) | ||
|
||
const ( | ||
// TypePCDIN is type of PCDIN sentence for SeaSmart.Net Protocol | ||
TypePCDIN = "CDIN" | ||
) | ||
|
||
// PCDIN - SeaSmart.Net Protocol transfers NMEA2000 message as NMEA0183 sentence | ||
// http://www.seasmart.net/pdf/SeaSmart_HTTP_Protocol_RevG_043012.pdf (SeaSmart.Net Protocol Specification Version 1.7) | ||
// | ||
// Note: older SeaSmart.Net Protocol versions have different amount of fields | ||
// | ||
// Format: $PCDIN,hhhhhh,hhhhhhhh,hh,h--h*hh<CR><LF> | ||
// Example: $PCDIN,01F112,000C72EA,09,28C36A0000B40AFD*56 | ||
type PCDIN struct { | ||
BaseSentence | ||
PGN uint32 // PGN of NMEA2000 packet | ||
Timestamp uint32 // ticks since something | ||
Source uint8 // 0-255 | ||
Data []byte // can be more than 8 bytes i.e can contain assembled fast packets | ||
} | ||
|
||
// newPCDIN constructor | ||
func newPCDIN(s BaseSentence) (Sentence, error) { | ||
p := NewParser(s) | ||
p.AssertType(TypePCDIN) | ||
|
||
if len(p.Fields) != 4 { | ||
p.SetErr("fields", "invalid number of fields in sentence") | ||
return nil, p.Err() | ||
} | ||
pgn, err := strconv.ParseUint(p.Fields[0], 16, 24) | ||
if err != nil { | ||
p.err = fmt.Errorf("nmea: %s failed to parse PGN field: %w", p.Prefix(), err) | ||
return nil, p.Err() | ||
} | ||
timestamp, err := strconv.ParseUint(p.Fields[1], 16, 32) | ||
if err != nil { | ||
p.err = fmt.Errorf("nmea: %s failed to parse timestamp field: %w", p.Prefix(), err) | ||
return nil, p.Err() | ||
} | ||
source, err := strconv.ParseUint(p.Fields[2], 16, 8) | ||
if err != nil { | ||
p.err = fmt.Errorf("nmea: %s failed to parse source field: %w", p.Prefix(), err) | ||
return nil, p.Err() | ||
} | ||
data, err := hex.DecodeString(p.Fields[3]) | ||
if err != nil { | ||
p.err = fmt.Errorf("nmea: %s failed to decode data: %w", p.Prefix(), err) | ||
return nil, p.Err() | ||
} | ||
|
||
return PCDIN{ | ||
BaseSentence: s, | ||
PGN: uint32(pgn), | ||
Timestamp: uint32(timestamp), | ||
Source: uint8(source), | ||
Data: data, | ||
}, p.Err() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package nmea | ||
|
||
import ( | ||
"github.com/stretchr/testify/assert" | ||
"testing" | ||
) | ||
|
||
func TestPCDIN(t *testing.T) { | ||
var tests = []struct { | ||
name string | ||
raw string | ||
err string | ||
msg PCDIN | ||
}{ | ||
{ | ||
name: "good sentence", | ||
raw: "$PCDIN,01F112,000C72EA,09,28C36A0000B40AFD*56", | ||
msg: PCDIN{ | ||
PGN: 127250, // 0x1F112 Vessel Heading | ||
Timestamp: 815850, | ||
Source: 9, | ||
Data: []byte{0x28, 0xC3, 0x6A, 0x00, 0x00, 0xB4, 0x0A, 0xFD}, | ||
}, | ||
}, | ||
{ | ||
name: "invalid number of fields", | ||
raw: "$PCDIN,01F112,000C72EA,28C36A0000B40AFD*73", | ||
err: "nmea: PCDIN invalid fields: invalid number of fields in sentence", | ||
}, | ||
{ | ||
name: "invalid PGN field", | ||
raw: "$PCDIN,x1F112,000C72EA,09,28C36A0000B40AFD*1e", | ||
err: "nmea: PCDIN failed to parse PGN field: strconv.ParseUint: parsing \"x1F112\": invalid syntax", | ||
}, | ||
{ | ||
name: "invalid timestamp field", | ||
raw: "$PCDIN,01F112,x00C72EA,09,28C36A0000B40AFD*1e", | ||
err: "nmea: PCDIN failed to parse timestamp field: strconv.ParseUint: parsing \"x00C72EA\": invalid syntax", | ||
}, | ||
{ | ||
name: "invalid source field", | ||
raw: "$PCDIN,01F112,000C72EA,x9,28C36A0000B40AFD*1e", | ||
err: "nmea: PCDIN failed to parse source field: strconv.ParseUint: parsing \"x9\": invalid syntax", | ||
}, | ||
{ | ||
name: "invalid hex data", | ||
raw: "$PCDIN,01F112,000C72EA,09,x8C36A0000B40AFD*1c", | ||
err: "nmea: PCDIN failed to decode data: encoding/hex: invalid byte: U+0078 'x'", | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
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) | ||
pgrme := m.(PCDIN) | ||
pgrme.BaseSentence = BaseSentence{} | ||
assert.Equal(t, tt.msg, pgrme) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package nmea | ||
|
||
import ( | ||
"encoding/hex" | ||
"fmt" | ||
"strconv" | ||
) | ||
|
||
const ( | ||
// TypePGN is type of PGN sentence for transferring single NMEA2000 frame as NMEA0183 sentence | ||
TypePGN = "PGN" | ||
) | ||
|
||
// PGN - transferring single NMEA2000 frame as NMEA0183 sentence | ||
// https://opencpn.org/wiki/dokuwiki/lib/exe/fetch.php?media=opencpn:software:mxpgn_sentence.pdf | ||
// | ||
// Format: $--PGN,pppppp,aaaa,c--c*hh<CR><LF> | ||
// Example: $MXPGN,01F112,2807,FC7FFF7FFF168012*11 | ||
type PGN struct { | ||
BaseSentence | ||
PGN uint32 // PGN of NMEA2000 packet | ||
IsSend bool // is this sentence received or for sending | ||
Priority uint8 // 0-7 | ||
Address uint8 // depending on the IsSend field this is Source Address of received packet or Destination for send packet | ||
Data []byte // 1-8 bytes. This is single N2K frame. N2K Fast-packets should be assembled from individual frames | ||
} | ||
|
||
// newPGN constructor | ||
func newPGN(s BaseSentence) (Sentence, error) { | ||
p := NewParser(s) | ||
p.AssertType(TypePGN) | ||
|
||
if len(p.Fields) != 3 { | ||
p.SetErr("fields", "invalid number of fields in sentence") | ||
return nil, p.Err() | ||
} | ||
pgn, err := strconv.ParseUint(p.Fields[0], 16, 24) | ||
if err != nil { | ||
p.err = fmt.Errorf("nmea: %s failed to parse PGN field: %w", p.Prefix(), err) | ||
return nil, p.Err() | ||
} | ||
attributes, err := strconv.ParseUint(p.Fields[1], 16, 16) | ||
if err != nil { | ||
p.err = fmt.Errorf("nmea: %s failed to parse attributes field: %w", p.Prefix(), err) | ||
return nil, p.Err() | ||
} | ||
dataLength := int((attributes >> 8) & 0b1111) // bits 8-11 | ||
if dataLength*2 != (len(p.Fields[2])) { | ||
p.SetErr("dlc", "data length does not match actual data length") | ||
return nil, p.Err() | ||
} | ||
data, err := hex.DecodeString(p.Fields[2]) | ||
if err != nil { | ||
p.err = fmt.Errorf("nmea: %s failed to decode data: %w", p.Prefix(), err) | ||
return nil, p.Err() | ||
} | ||
|
||
return PGN{ | ||
BaseSentence: s, | ||
PGN: uint32(pgn), | ||
IsSend: attributes>>15 == 1, // bit 15 | ||
Priority: uint8((attributes >> 12) & 0b111), // bits 12,13,14 | ||
Address: uint8(attributes), // bits 0-7 | ||
Data: data, | ||
}, p.Err() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package nmea | ||
|
||
import ( | ||
"github.com/stretchr/testify/assert" | ||
"testing" | ||
) | ||
|
||
func TestPGN(t *testing.T) { | ||
var tests = []struct { | ||
name string | ||
raw string | ||
err string | ||
msg PGN | ||
}{ | ||
{ | ||
name: "good sentence", | ||
raw: "$MXPGN,01F112,2807,FC7FFF7FFF168012*11", | ||
msg: PGN{ | ||
PGN: 127250, // 0x1F112 Vessel Heading | ||
IsSend: false, | ||
Priority: 2, | ||
Address: 7, | ||
Data: []byte{0xFC, 0x7f, 0xFF, 0x7f, 0xFF, 0x16, 0x80, 0x12}, | ||
}, | ||
}, | ||
{ | ||
name: "invalid number of fields", | ||
raw: "$MXPGN,01F112,FC7FFF7FFF168012*30", | ||
err: "nmea: MXPGN invalid fields: invalid number of fields in sentence", | ||
}, | ||
{ | ||
name: "invalid PGN field", | ||
raw: "$MXPGN,0xF112,2807,FC7FFF7FFF168012*58", | ||
err: "nmea: MXPGN failed to parse PGN field: strconv.ParseUint: parsing \"0xF112\": invalid syntax", | ||
}, | ||
{ | ||
name: "invalid attributes field", | ||
raw: "$MXPGN,01F112,x807,FC7FFF7FFF168012*5b", | ||
err: "nmea: MXPGN failed to parse attributes field: strconv.ParseUint: parsing \"x807\": invalid syntax", | ||
}, | ||
{ | ||
name: "invalid data length field", | ||
raw: "$MXPGN,01F112,2207,FC7FFF7FFF168012*1b", | ||
err: "nmea: MXPGN invalid dlc: data length does not match actual data length", | ||
}, | ||
{ | ||
name: "invalid hex data", | ||
raw: "$MXPGN,01F112,2807,xC7FFF7FFF168012*2f", | ||
err: "nmea: MXPGN failed to decode data: encoding/hex: invalid byte: U+0078 'x'", | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
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) | ||
pgrme := m.(PGN) | ||
pgrme.BaseSentence = BaseSentence{} | ||
assert.Equal(t, tt.msg, pgrme) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters