Skip to content

Commit 2740e68

Browse files
authored
Added ABM,ACK,ACN,ALC,ALF,ALR,ARC,BBM,HBT,TLB,TTD,VSD sentences (#100)
1 parent 96ff744 commit 2740e68

32 files changed

+2380
-439
lines changed

.github/workflows/ci.yml

+5-5
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,19 @@ jobs:
1515
strategy:
1616
fail-fast: false
1717
matrix:
18-
go: ["1.17", "1.16", "1.15", "1.14"]
18+
go: ["1.20", "1.19", "1.18", "1.17"]
1919
steps:
20-
- uses: actions/checkout@v2
20+
- uses: actions/checkout@v3
2121

2222
- name: Set up Go
23-
uses: actions/setup-go@v2
23+
uses: actions/setup-go@v3
2424
with:
2525
go-version: ${{ matrix.go }}
2626

2727
- name: Install dependencies
2828
run: |
29-
go get -u golang.org/x/lint/golint@latest
30-
go get -u github.com/mattn/[email protected].9
29+
go install golang.org/x/lint/golint@latest
30+
go install github.com/mattn/[email protected].11
3131
3232
- name: Lint
3333
run: |

Makefile

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ test: ## Run tests with data race detector
1111
@go test -race ./...
1212

1313
init:
14-
@go get -u golang.org/x/lint/golint@latest
14+
@go install golang.org/x/lint/golint@latest
1515

16-
goversion ?= "1.17"
17-
test_version: ## Run tests inside Docker with given version (defaults to 1.17). Example for Go1.15: make test_version goversion=1.15
16+
goversion ?= "1.19"
17+
test_version: ## Run tests inside Docker with given version (defaults to 1.19). Example for Go1.15: make test_version goversion=1.15
1818
@docker run --rm -it -v $(shell pwd):/project golang:$(goversion) /bin/sh -c "cd /project && make test"

README.md

+146-69
Large diffs are not rendered by default.

abm.go

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package nmea
2+
3+
const (
4+
// TypeABM type of ABM sentence for AIS addressed binary and safety related message
5+
TypeABM = "ABM"
6+
)
7+
8+
// ABM - AIS addressed binary and safety related message
9+
// https://fcc.report/FCC-ID/ADB9ZWRTR100/2768717.pdf (page 6) FURUNO MARINE RADAR, model FAR-15XX manual
10+
//
11+
// Format: !--ABM,x,x,x,xxxxxxxxx,x,xx,s--s,x,*hh<CR><LF>
12+
// Example: !AIABM,26,2,1,3381581370,3,8,177KQJ5000G?tO`K>RA1wUbN0TKH,0*02
13+
type ABM struct {
14+
BaseSentence
15+
16+
// NumFragments is total number of fragments/sentences need to transfer the message (1 - 9)
17+
NumFragments int64 // 0
18+
19+
// FragmentNumber is current fragment/sentence number (1 - 9)
20+
FragmentNumber int64 // 1
21+
22+
// MessageID is sequential message identifier (0 - 3)
23+
MessageID int64 // 2
24+
25+
// MMSI is The MMSI of destination AIS unit for the ITU-R M.1371 message (10 digits or empty)
26+
MMSI string // 3
27+
28+
// Channel is AIS channel for broadcast of the radio message (0 - 3)
29+
// 0 - no broadcast
30+
// 1 - on AIS channel A
31+
// 2 - on AIS channel B
32+
// 3 - broadcast on both AIS channels
33+
Channel string // 4
34+
35+
// VDLMessageNumber is VDL message number (6/12), see ITU-R M.1371
36+
VDLMessageNumber int64 // 5
37+
38+
// Payload is encapsulated data (6 bit binary-converted data) (1 - 63 bytes)
39+
Payload []byte // 6
40+
// 7 - Number of fill bits (0 - 5)
41+
}
42+
43+
// newABM constructor
44+
func newABM(s BaseSentence) (ABM, error) {
45+
p := NewParser(s)
46+
p.AssertType(TypeABM)
47+
return ABM{
48+
BaseSentence: s,
49+
NumFragments: p.Int64(0, "number of fragments"),
50+
FragmentNumber: p.Int64(1, "fragment number"),
51+
MessageID: p.Int64(2, "message ID"),
52+
MMSI: p.String(3, "MMSI"),
53+
Channel: p.String(4, "channel"),
54+
VDLMessageNumber: p.Int64(5, "VDL message number"),
55+
Payload: p.SixBitASCIIArmour(6, int(p.Int64(7, "number of padding bits")), "payload"),
56+
}, p.Err()
57+
}

abm_test.go

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package nmea
2+
3+
import (
4+
"github.com/stretchr/testify/assert"
5+
"testing"
6+
)
7+
8+
func TestABM(t *testing.T) {
9+
var tests = []struct {
10+
name string
11+
raw string
12+
err string
13+
msg ABM
14+
}{
15+
{
16+
name: "Good single fragment message",
17+
raw: "!AIABM,26,2,1,3381581370,3,8,177KQJ5000G?tO`K>RA1wUbN0TKH,0*02",
18+
msg: ABM{
19+
NumFragments: 26,
20+
FragmentNumber: 2,
21+
MessageID: 1,
22+
MMSI: "3381581370",
23+
Channel: "3",
24+
VDLMessageNumber: 8,
25+
Payload: []byte{
26+
0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, // 10
27+
0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x0, 0x1, // 20
28+
0x1, 0x0, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, // 30
29+
0x0, 0x1, 0x1, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, // 40
30+
0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // 50
31+
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // 60
32+
0x0, 0x1, 0x0, 0x1, 0x1, 0x1, 0x0, 0x0, 0x1, 0x1, // 70
33+
0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, // 80
34+
0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x1, 0x0, 0x0, 0x0, // 90
35+
0x0, 0x1, 0x1, 0x0, 0x1, 0x1, 0x0, 0x0, 0x1, 0x1, // 100
36+
0x1, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x1, // 110
37+
0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, // 120
38+
0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x1, // 130
39+
0x0, 0x1, 0x1, 0x0, 0x1, 0x0, 0x1, 0x0, 0x0, 0x1, // 140
40+
0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // 150
41+
0x1, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, // 160
42+
0x1, 0x1, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, // 168
43+
},
44+
},
45+
},
46+
{
47+
name: "Good single fragment message with padding",
48+
raw: "!AIABM,26,2,1,3381581370,3,8,H77nSfPh4U=<E`H4U8G;:222220,2*42",
49+
msg: ABM{
50+
NumFragments: 26,
51+
FragmentNumber: 2,
52+
MessageID: 1,
53+
MMSI: "3381581370",
54+
Channel: "3",
55+
VDLMessageNumber: 8,
56+
Payload: []byte{
57+
0, 1, 1, 0, 0, 0, 0, 0, 0, 1,
58+
1, 1, 0, 0, 0, 1, 1, 1, 1, 1,
59+
0, 1, 1, 0, 1, 0, 0, 0, 1, 1,
60+
1, 0, 1, 1, 1, 0, 1, 0, 0, 0,
61+
0, 0, 1, 1, 0, 0, 0, 0, 0, 0,
62+
0, 1, 0, 0, 1, 0, 0, 1, 0, 1,
63+
0, 0, 1, 1, 0, 1, 0, 0, 1, 1,
64+
0, 0, 0, 1, 0, 1, 0, 1, 1, 0,
65+
1, 0, 0, 0, 0, 1, 1, 0, 0, 0,
66+
0, 0, 0, 1, 0, 0, 1, 0, 0, 1,
67+
0, 1, 0, 0, 1, 0, 0, 0, 0, 1,
68+
0, 1, 1, 1, 0, 0, 1, 0, 1, 1,
69+
0, 0, 1, 0, 1, 0, 0, 0, 0, 0,
70+
1, 0, 0, 0, 0, 0, 1, 0, 0, 0,
71+
0, 0, 1, 0, 0, 0, 0, 0, 1, 0,
72+
0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
73+
},
74+
},
75+
},
76+
{
77+
name: "Empty payload",
78+
raw: "!AIABM,26,2,1,3381581370,3,8,,0*7b",
79+
msg: ABM{
80+
NumFragments: 26,
81+
FragmentNumber: 2,
82+
MessageID: 1,
83+
MMSI: "3381581370",
84+
Channel: "3",
85+
VDLMessageNumber: 8,
86+
Payload: []byte{},
87+
},
88+
},
89+
{
90+
name: "Invalid number of fragments",
91+
raw: "!AIABM,x,2,1,3381581370,3,8,177KQJ5000G?tO`K>RA1wUbN0TKH,0*7e",
92+
err: "nmea: AIABM invalid number of fragments: x",
93+
},
94+
{
95+
name: "Invalid VDLMessageNumber",
96+
raw: "!AIABM,26,2,1,3381581370,3,x,177KQJ5000G?tO`K>RA1wUbN0TKH,0*42",
97+
err: "nmea: AIABM invalid VDL message number: x",
98+
},
99+
{
100+
name: "Invalid symbol in payload",
101+
raw: "!AIABM,26,2,1,3381581370,3,8,1 1,0*5b",
102+
err: "nmea: AIABM invalid payload: data byte",
103+
},
104+
{
105+
name: "Negative number of fill bits",
106+
raw: "!AIABM,26,2,1,3381581370,3,8,177KQJ5000G?tO`K>RA1wUbN0TKH,-1*2e",
107+
err: "nmea: AIABM invalid payload: fill bits",
108+
},
109+
{
110+
name: "Too high number of fill bits",
111+
raw: "!AIABM,26,2,1,3381581370,3,8,177KQJ5000G?tO`K>RA1wUbN0TKH,20*30",
112+
err: "nmea: AIABM invalid payload: fill bits",
113+
},
114+
{
115+
name: "Negative number of bits",
116+
raw: "!AIABM,26,2,1,3381581370,3,8,,2*79",
117+
err: "nmea: AIABM invalid payload: num bits",
118+
},
119+
}
120+
for _, tt := range tests {
121+
t.Run(tt.name, func(t *testing.T) {
122+
m, err := Parse(tt.raw)
123+
if tt.err != "" {
124+
assert.Error(t, err)
125+
assert.EqualError(t, err, tt.err)
126+
} else {
127+
assert.NoError(t, err)
128+
abm := m.(ABM)
129+
abm.BaseSentence = BaseSentence{}
130+
assert.Equal(t, tt.msg, abm)
131+
}
132+
})
133+
}
134+
}

ack.go

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package nmea
2+
3+
const (
4+
// TypeACK type of ACK sentence for alert acknowledge
5+
TypeACK = "ACK"
6+
)
7+
8+
// ACK - Acknowledge. This sentence is used to acknowledge an alarm condition reported by a device.
9+
// http://www.nmea.de/nmea0183datensaetze.html#ack
10+
// https://www.furuno.it/docs/INSTALLATION%20MANUALgp170_installation_manual.pdf GPS NAVIGATOR Model GP-170 (page 42)
11+
// https://www.manualslib.com/manual/2226813/Jrc-Jln-900.html?page=239#manual (JRC JLN-900: Installation And Instruction Manual)
12+
//
13+
// Format: $--ACK,xxx*hh<CR><LF>
14+
// Example: $VRACK,001*50
15+
type ACK struct {
16+
BaseSentence
17+
18+
// AlertIdentifier is alert identifier (001 to 99999)
19+
AlertIdentifier int64 // 0
20+
}
21+
22+
// newACKN constructor
23+
func newACK(s BaseSentence) (ACK, error) {
24+
p := NewParser(s)
25+
p.AssertType(TypeACK)
26+
return ACK{
27+
BaseSentence: s,
28+
AlertIdentifier: p.Int64(0, "alert identifier"),
29+
}, p.Err()
30+
}

ack_test.go

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package nmea
2+
3+
import (
4+
"github.com/stretchr/testify/assert"
5+
"testing"
6+
)
7+
8+
func TestACK(t *testing.T) {
9+
var tests = []struct {
10+
name string
11+
raw string
12+
err string
13+
msg ACK
14+
}{
15+
{
16+
name: "good sentence",
17+
raw: "$VRACK,001*50",
18+
msg: ACK{
19+
AlertIdentifier: 1,
20+
},
21+
},
22+
{
23+
name: "invalid nmea: AlertIdentifier",
24+
raw: "$VRACK,x*19",
25+
err: "nmea: VRACK invalid alert identifier: x",
26+
},
27+
}
28+
for _, tt := range tests {
29+
t.Run(tt.name, func(t *testing.T) {
30+
m, err := Parse(tt.raw)
31+
if tt.err != "" {
32+
assert.Error(t, err)
33+
assert.EqualError(t, err, tt.err)
34+
} else {
35+
assert.NoError(t, err)
36+
ack := m.(ACK)
37+
ack.BaseSentence = BaseSentence{}
38+
assert.Equal(t, tt.msg, ack)
39+
}
40+
})
41+
}
42+
}

acn.go

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package nmea
2+
3+
const (
4+
// TypeACN type of ACN sentence for alert command
5+
TypeACN = "ACN"
6+
)
7+
8+
// ACN - Alert command. Used for acknowledge, silence, responsibility transfer and to request repeat of alert.
9+
// https://www.furuno.it/docs/INSTALLATION%20MANUALIME44900D_FA170.pdf Furuno CLASS A AIS Model FA-170 (page 49)
10+
// https://www.furuno.it/docs/INSTALLATION%20MANUALgp170_installation_manual.pdf GPS NAVIGATOR Model GP-170 (page 42)
11+
//
12+
// Format: $--ACN,hhmmss.ss,AAA,x.x,x.x,A,A*hh<CR><LF>
13+
// Example: $VRACN,220516,BPMP1,A,A,Bilge pump alarm1*43
14+
type ACN struct {
15+
BaseSentence
16+
17+
// Time is time of alarm condition change, UTC (000000.00 - 240001.00)
18+
Time Time // 0
19+
20+
// ManufacturerMnemonicCode is manufacturer mnemonic code
21+
ManufacturerMnemonicCode string // 1
22+
23+
// AlertIdentifier is alert identifier (001 to 99999)
24+
AlertIdentifier int64 // 2
25+
26+
// AlertInstance is alert instance
27+
AlertInstance int64 // 3
28+
29+
// Command is Alert command
30+
// * A - acknowledge,
31+
// * Q - request/repeat information
32+
// * O - responsibility transfer
33+
// * S - silence
34+
Command string // 4
35+
36+
// State is alarm state
37+
// * C - command
38+
// * possible more classifier values but these are not mentioned in manual
39+
State string // 5
40+
}
41+
42+
// newACN constructor
43+
func newACN(s BaseSentence) (ACN, error) {
44+
p := NewParser(s)
45+
p.AssertType(TypeACN)
46+
return ACN{
47+
BaseSentence: s,
48+
Time: p.Time(0, "time"),
49+
ManufacturerMnemonicCode: p.String(1, "manufacturer mnemonic code"),
50+
AlertIdentifier: p.Int64(2, "alert identifier"),
51+
AlertInstance: p.Int64(3, "alert instance"),
52+
Command: p.EnumString(4, "alert command", AlertCommandAcknowledge, AlertCommandRequestRepeatInformation, AlertCommandResponsibilityTransfer, AlertCommandSilence),
53+
State: p.String(5, "alarm state"),
54+
}, p.Err()
55+
}

0 commit comments

Comments
 (0)