Skip to content

Commit

Permalink
Added ABM,ACK,ACN,ALC,ALF,ALR,ARC,BBM,HBT,TLB,TTD,VSD sentences (#100)
Browse files Browse the repository at this point in the history
  • Loading branch information
aldas committed Feb 4, 2023
1 parent 96ff744 commit 2740e68
Show file tree
Hide file tree
Showing 32 changed files with 2,380 additions and 439 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,19 @@ jobs:
strategy:
fail-fast: false
matrix:
go: ["1.17", "1.16", "1.15", "1.14"]
go: ["1.20", "1.19", "1.18", "1.17"]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v2
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go }}

- name: Install dependencies
run: |
go get -u golang.org/x/lint/golint@latest
go get -u github.com/mattn/[email protected].9
go install golang.org/x/lint/golint@latest
go install github.com/mattn/[email protected].11
- name: Lint
run: |
Expand Down
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ test: ## Run tests with data race detector
@go test -race ./...

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

goversion ?= "1.17"
test_version: ## Run tests inside Docker with given version (defaults to 1.17). Example for Go1.15: make test_version goversion=1.15
goversion ?= "1.19"
test_version: ## Run tests inside Docker with given version (defaults to 1.19). Example for Go1.15: make test_version goversion=1.15
@docker run --rm -it -v $(shell pwd):/project golang:$(goversion) /bin/sh -c "cd /project && make test"
215 changes: 146 additions & 69 deletions README.md

Large diffs are not rendered by default.

57 changes: 57 additions & 0 deletions abm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package nmea

const (
// TypeABM type of ABM sentence for AIS addressed binary and safety related message
TypeABM = "ABM"
)

// ABM - AIS addressed binary and safety related message
// https://fcc.report/FCC-ID/ADB9ZWRTR100/2768717.pdf (page 6) FURUNO MARINE RADAR, model FAR-15XX manual
//
// Format: !--ABM,x,x,x,xxxxxxxxx,x,xx,s--s,x,*hh<CR><LF>
// Example: !AIABM,26,2,1,3381581370,3,8,177KQJ5000G?tO`K>RA1wUbN0TKH,0*02
type ABM struct {
BaseSentence

// NumFragments is total number of fragments/sentences need to transfer the message (1 - 9)
NumFragments int64 // 0

// FragmentNumber is current fragment/sentence number (1 - 9)
FragmentNumber int64 // 1

// MessageID is sequential message identifier (0 - 3)
MessageID int64 // 2

// MMSI is The MMSI of destination AIS unit for the ITU-R M.1371 message (10 digits or empty)
MMSI string // 3

// Channel is AIS channel for broadcast of the radio message (0 - 3)
// 0 - no broadcast
// 1 - on AIS channel A
// 2 - on AIS channel B
// 3 - broadcast on both AIS channels
Channel string // 4

// VDLMessageNumber is VDL message number (6/12), see ITU-R M.1371
VDLMessageNumber int64 // 5

// Payload is encapsulated data (6 bit binary-converted data) (1 - 63 bytes)
Payload []byte // 6
// 7 - Number of fill bits (0 - 5)
}

// newABM constructor
func newABM(s BaseSentence) (ABM, error) {
p := NewParser(s)
p.AssertType(TypeABM)
return ABM{
BaseSentence: s,
NumFragments: p.Int64(0, "number of fragments"),
FragmentNumber: p.Int64(1, "fragment number"),
MessageID: p.Int64(2, "message ID"),
MMSI: p.String(3, "MMSI"),
Channel: p.String(4, "channel"),
VDLMessageNumber: p.Int64(5, "VDL message number"),
Payload: p.SixBitASCIIArmour(6, int(p.Int64(7, "number of padding bits")), "payload"),
}, p.Err()
}
134 changes: 134 additions & 0 deletions abm_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package nmea

import (
"github.com/stretchr/testify/assert"
"testing"
)

func TestABM(t *testing.T) {
var tests = []struct {
name string
raw string
err string
msg ABM
}{
{
name: "Good single fragment message",
raw: "!AIABM,26,2,1,3381581370,3,8,177KQJ5000G?tO`K>RA1wUbN0TKH,0*02",
msg: ABM{
NumFragments: 26,
FragmentNumber: 2,
MessageID: 1,
MMSI: "3381581370",
Channel: "3",
VDLMessageNumber: 8,
Payload: []byte{
0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, // 10
0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x0, 0x1, // 20
0x1, 0x0, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, // 30
0x0, 0x1, 0x1, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, // 40
0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // 50
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // 60
0x0, 0x1, 0x0, 0x1, 0x1, 0x1, 0x0, 0x0, 0x1, 0x1, // 70
0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, // 80
0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x1, 0x0, 0x0, 0x0, // 90
0x0, 0x1, 0x1, 0x0, 0x1, 0x1, 0x0, 0x0, 0x1, 0x1, // 100
0x1, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x1, // 110
0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, // 120
0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x1, // 130
0x0, 0x1, 0x1, 0x0, 0x1, 0x0, 0x1, 0x0, 0x0, 0x1, // 140
0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // 150
0x1, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, // 160
0x1, 0x1, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, // 168
},
},
},
{
name: "Good single fragment message with padding",
raw: "!AIABM,26,2,1,3381581370,3,8,H77nSfPh4U=<E`H4U8G;:222220,2*42",
msg: ABM{
NumFragments: 26,
FragmentNumber: 2,
MessageID: 1,
MMSI: "3381581370",
Channel: "3",
VDLMessageNumber: 8,
Payload: []byte{
0, 1, 1, 0, 0, 0, 0, 0, 0, 1,
1, 1, 0, 0, 0, 1, 1, 1, 1, 1,
0, 1, 1, 0, 1, 0, 0, 0, 1, 1,
1, 0, 1, 1, 1, 0, 1, 0, 0, 0,
0, 0, 1, 1, 0, 0, 0, 0, 0, 0,
0, 1, 0, 0, 1, 0, 0, 1, 0, 1,
0, 0, 1, 1, 0, 1, 0, 0, 1, 1,
0, 0, 0, 1, 0, 1, 0, 1, 1, 0,
1, 0, 0, 0, 0, 1, 1, 0, 0, 0,
0, 0, 0, 1, 0, 0, 1, 0, 0, 1,
0, 1, 0, 0, 1, 0, 0, 0, 0, 1,
0, 1, 1, 1, 0, 0, 1, 0, 1, 1,
0, 0, 1, 0, 1, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0, 1, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
},
},
},
{
name: "Empty payload",
raw: "!AIABM,26,2,1,3381581370,3,8,,0*7b",
msg: ABM{
NumFragments: 26,
FragmentNumber: 2,
MessageID: 1,
MMSI: "3381581370",
Channel: "3",
VDLMessageNumber: 8,
Payload: []byte{},
},
},
{
name: "Invalid number of fragments",
raw: "!AIABM,x,2,1,3381581370,3,8,177KQJ5000G?tO`K>RA1wUbN0TKH,0*7e",
err: "nmea: AIABM invalid number of fragments: x",
},
{
name: "Invalid VDLMessageNumber",
raw: "!AIABM,26,2,1,3381581370,3,x,177KQJ5000G?tO`K>RA1wUbN0TKH,0*42",
err: "nmea: AIABM invalid VDL message number: x",
},
{
name: "Invalid symbol in payload",
raw: "!AIABM,26,2,1,3381581370,3,8,1 1,0*5b",
err: "nmea: AIABM invalid payload: data byte",
},
{
name: "Negative number of fill bits",
raw: "!AIABM,26,2,1,3381581370,3,8,177KQJ5000G?tO`K>RA1wUbN0TKH,-1*2e",
err: "nmea: AIABM invalid payload: fill bits",
},
{
name: "Too high number of fill bits",
raw: "!AIABM,26,2,1,3381581370,3,8,177KQJ5000G?tO`K>RA1wUbN0TKH,20*30",
err: "nmea: AIABM invalid payload: fill bits",
},
{
name: "Negative number of bits",
raw: "!AIABM,26,2,1,3381581370,3,8,,2*79",
err: "nmea: AIABM invalid payload: num bits",
},
}
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)
abm := m.(ABM)
abm.BaseSentence = BaseSentence{}
assert.Equal(t, tt.msg, abm)
}
})
}
}
30 changes: 30 additions & 0 deletions ack.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package nmea

const (
// TypeACK type of ACK sentence for alert acknowledge
TypeACK = "ACK"
)

// ACK - Acknowledge. This sentence is used to acknowledge an alarm condition reported by a device.
// http://www.nmea.de/nmea0183datensaetze.html#ack
// https://www.furuno.it/docs/INSTALLATION%20MANUALgp170_installation_manual.pdf GPS NAVIGATOR Model GP-170 (page 42)
// https://www.manualslib.com/manual/2226813/Jrc-Jln-900.html?page=239#manual (JRC JLN-900: Installation And Instruction Manual)
//
// Format: $--ACK,xxx*hh<CR><LF>
// Example: $VRACK,001*50
type ACK struct {
BaseSentence

// AlertIdentifier is alert identifier (001 to 99999)
AlertIdentifier int64 // 0
}

// newACKN constructor
func newACK(s BaseSentence) (ACK, error) {
p := NewParser(s)
p.AssertType(TypeACK)
return ACK{
BaseSentence: s,
AlertIdentifier: p.Int64(0, "alert identifier"),
}, p.Err()
}
42 changes: 42 additions & 0 deletions ack_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package nmea

import (
"github.com/stretchr/testify/assert"
"testing"
)

func TestACK(t *testing.T) {
var tests = []struct {
name string
raw string
err string
msg ACK
}{
{
name: "good sentence",
raw: "$VRACK,001*50",
msg: ACK{
AlertIdentifier: 1,
},
},
{
name: "invalid nmea: AlertIdentifier",
raw: "$VRACK,x*19",
err: "nmea: VRACK invalid alert identifier: 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)
ack := m.(ACK)
ack.BaseSentence = BaseSentence{}
assert.Equal(t, tt.msg, ack)
}
})
}
}
55 changes: 55 additions & 0 deletions acn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package nmea

const (
// TypeACN type of ACN sentence for alert command
TypeACN = "ACN"
)

// ACN - Alert command. Used for acknowledge, silence, responsibility transfer and to request repeat of alert.
// https://www.furuno.it/docs/INSTALLATION%20MANUALIME44900D_FA170.pdf Furuno CLASS A AIS Model FA-170 (page 49)
// https://www.furuno.it/docs/INSTALLATION%20MANUALgp170_installation_manual.pdf GPS NAVIGATOR Model GP-170 (page 42)
//
// Format: $--ACN,hhmmss.ss,AAA,x.x,x.x,A,A*hh<CR><LF>
// Example: $VRACN,220516,BPMP1,A,A,Bilge pump alarm1*43
type ACN struct {
BaseSentence

// Time is time of alarm condition change, UTC (000000.00 - 240001.00)
Time Time // 0

// ManufacturerMnemonicCode is manufacturer mnemonic code
ManufacturerMnemonicCode string // 1

// AlertIdentifier is alert identifier (001 to 99999)
AlertIdentifier int64 // 2

// AlertInstance is alert instance
AlertInstance int64 // 3

// Command is Alert command
// * A - acknowledge,
// * Q - request/repeat information
// * O - responsibility transfer
// * S - silence
Command string // 4

// State is alarm state
// * C - command
// * possible more classifier values but these are not mentioned in manual
State string // 5
}

// newACN constructor
func newACN(s BaseSentence) (ACN, error) {
p := NewParser(s)
p.AssertType(TypeACN)
return ACN{
BaseSentence: s,
Time: p.Time(0, "time"),
ManufacturerMnemonicCode: p.String(1, "manufacturer mnemonic code"),
AlertIdentifier: p.Int64(2, "alert identifier"),
AlertInstance: p.Int64(3, "alert instance"),
Command: p.EnumString(4, "alert command", AlertCommandAcknowledge, AlertCommandRequestRepeatInformation, AlertCommandResponsibilityTransfer, AlertCommandSilence),
State: p.String(5, "alarm state"),
}, p.Err()
}
Loading

0 comments on commit 2740e68

Please sign in to comment.