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

feature: add GNS sentence #232

Merged
merged 5 commits into from
Jan 4, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
- [DBK - Depth Below Keel](https://gpsd.gitlab.io/gpsd/NMEA.html#_dbk_depth_below_keel)
- [DPT - Depth of Water](https://gpsd.gitlab.io/gpsd/NMEA.html#_dpt_depth_of_water)
- [DSC - Digital Selective Calling Class-D Radios](http://continuouswave.com/whaler/reference/DSC_Datagrams.html)
- [GNS - Fix Data](https://gpsd.gitlab.io/gpsd/NMEA.html#_gns_fix_data)
- [GGA - Global Positioning System Fix Data](https://gpsd.gitlab.io/gpsd/NMEA.html#_gga_global_positioning_system_fix_data)
- [GLL - Geographic Position - Latitude/Longitude](https://gpsd.gitlab.io/gpsd/NMEA.html#_gll_geographic_position_latitude_longitude)
- [HDG - Heading - Deviation & Variation](https://gpsd.gitlab.io/gpsd/NMEA.html#_hdg_heading_deviation_amp_variation)
Expand Down
193 changes: 193 additions & 0 deletions hooks/GNS.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
'use strict'

/**
* Copyright 2016 Signal K and Fabian Tollenaar <fabian@signalk.org>.
RoeeDev marked this conversation as resolved.
Show resolved Hide resolved
*
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

const debug = require('debug')('signalk-parser-nmea0183/GGA')
const utils = require('@signalk/nmea0183-utilities')
const moment = require('moment-timezone')

/*
=== GNS - Fix Data ===
Time, Position and fix related data for a GPS receiver.
------------------------------------------------------------------------------
0 1 2 3 4 5 6 7 8 9 10 11 12
| | | | | | | | | | | | |
$--GNS,hhmmss.ss,ddmm.mm,a,dddmm.mm,a,c--c,xx,x.x,x.x,x.x,x.x,x.x*hh<CR><LF>
------------------------------------------------------------------------------
Field Number:
0. UTC of position, hh is hours, mm is minutes, ss.ss is seconds
1. Latitude, dd is minutes, mm.mm is minutes
2. N or S (North or South)
3. Longitude, dd is minutes, mm.mm is minutes
4. E or W (East or West)
5. Mode indicator (non-null) - Variable character field with one character for each supported constellation:
* First character is for GPS
* Second character is for GLONASS
* Third character is Galileo
* Fourth character is for BeiDou
* Fifth character is for QZSS
* Subsequent characters will be added for new constellations
Each character will be one of the following
- N = No fix. Satellite system not used in position fix, or fix not valid
- A = Autonomous. Satellite system used in non-differential mode in position fix
- D = Differential (including all OmniSTAR services). Satellite system used in differential mode in position fix
- P = Precise. Satellite system used in precision mode. Precision mode is defined as: no deliberate degradation (such as Selective Availability) and higher resolution code (P-code) is used to compute position fix
- R = Real-Time Kinematic. Satellite system used in RTK mode with fixed integers
- F = Float RTK. Satellite system used in real-time kinematic mode with floating integers
- E = Estimated (dead reckoning) mode
- M = Manual Input mode
- S = Simulator mode
6. Total number of satellites in use, 00-99
7. Horizontal Dilution of Precision (HDOP), calculated using all the satellites (GPS, GLONASS, and any future satellites) and used in computing the solution reported in each GNS sentence
8. Antenna altitude, meters, re:mean-sea-level (geoid)
9. Goeidal separation meters - The difference between the earth ellipsoid surface and mean-sea-level (geoid) surface defined by the reference datum used in the position solution
10. Age of differential data - Null if talker ID is GN, additional GNS messages follow with Age of differential data
11. Differential reference station ID, 0000-4095 - Null if Talker ID is GN, Additional GNS messages follow with Reference station ID
12. Navigational status (added when the IEC61162-1:2010/NMEA 0183 V4.10 option is selected in the NMEA I/O configuration):
- S = Safe
- C = Caution
- U = Unsafe
- V = Not valid for navigation
13. Checksum
*/

function isEmpty(mixed) {
return (
(typeof mixed !== 'string' && typeof mixed !== 'number') ||
(typeof mixed === 'string' && mixed.trim() === '')
)
}

function indicator(chars, modes) {
RoeeDev marked this conversation as resolved.
Show resolved Hide resolved
const systems = ["GPS","GNSS","Galileo","BeiDou","QZSS"]
RoeeDev marked this conversation as resolved.
Show resolved Hide resolved
const indications = chars.map(function (val, i) { return `${systems[i]}: ${modes[val]}` })
return indications.join(",")
}
RoeeDev marked this conversation as resolved.
Show resolved Hide resolved

module.exports = function (input) {
const { id, sentence, parts, tags } = input

const empty = parts.reduce((e, val) => {
if (isEmpty(val)) {
++e
}
return e
}, 0)

if (empty > 4) {
return null
}

const time = parts[0].indexOf('.') === -1 ? parts[0] : parts[0].split('.')[0]
const timestamp = utils.timestamp(time, moment.tz('UTC').format('DDMMYY'))

const mode = {
"A": "Autonomous (non-differential)",
"D": "Differential mode",
"E": "Estimated (dead reckoning) Mode",
"F": "RTK Float",
"M": "Manual Input Mode",
"N": "Constellation not in use, or no valid fix",
"P": "Precise (no degradation, like Selective Availability, and hires)",
"R": "RTK Integer",
"S": "Simulator Mode"
}

const state = {
"S": "Safe",
"C": "Caution",
"U": "Unsafe",
"V": "Not valid for navigation"
}

const delta = {
updates: [
{
source: tags.source,
timestamp: timestamp,
values: [
{
path: 'navigation.position',
value: {
longitude: utils.coordinate(parts[3], parts[4]),
latitude: utils.coordinate(parts[1], parts[2]),
},
},
{
path: 'navigation.gnss.methodQuality',
value: indicator(parts[5].split(""), mode),
},

{
path: 'navigation.gnss.satellites',
value: utils.int(parts[6]),
},

{
path: 'navigation.gnss.antennaAltitude',
value: utils.float(parts[8]),
},

{
path: 'navigation.gnss.horizontalDilution',
value: utils.float(parts[7]),
},

{
path: 'navigation.gnss.geoidalSeparation',
value: utils.float(parts[9]),
},

{
path: 'navigation.gnss.differentialAge',
value: utils.float(parts[10]),
},

{
path: 'navigation.gnss.differentialReference',
value: Number(parts[11]),
},
{
path: 'navigation.state',
RoeeDev marked this conversation as resolved.
Show resolved Hide resolved
value: state[(parts[12])],
},
],
},
],
}

const toRemove = []

delta.updates[0].values.forEach((update, index) => {
if (
typeof update.value === 'undefined' ||
update.value === null ||
(typeof update.value === 'string' && update.value.trim() === '') ||
(typeof update.value === 'number' && isNaN(update.value))
) {
toRemove.push(index)
}
})

if (toRemove.length > 0) {
toRemove.forEach((index) => {
delta.updates[0].values.splice(index, 1)
})
}

return delta
}
1 change: 1 addition & 0 deletions hooks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module.exports = {
DBS: require('./DBS.js'),
DPT: require('./DPT.js'),
DSC: require('./DSC.js'),
GNS: require('./GNS.js'),
GGA: require('./GGA.js'),
GLL: require('./GLL.js'),
GSV: require('./GSV.js'),
Expand Down
92 changes: 92 additions & 0 deletions test/GNS.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
'use strict'

/**
* Copyright 2016 Signal K and Fabian Tollenaar <fabian@signalk.org>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

const Parser = require('../lib')
const chai = require('chai')
const should = chai.Should()

chai.use(require('chai-things'))
chai.use(require('@signalk/signalk-schema').chaiModule)

const toFull = require('./toFull')

describe('GGA', () => {
it('Converts OK using individual parser', () => {
const delta = new Parser().parse(
'$GPGNS,111648.00,0235.0379,S,04422.1450,W,ANN,12,0.8,8.5,-22.3,,,S*5D'
)

// Paths
delta.updates[0].values.should.contain.an.item.with.property(
'path',
'navigation.position'
)
delta.updates[0].values.should.contain.an.item.with.property(
'path',
'navigation.gnss.methodQuality'
)
delta.updates[0].values.should.contain.an.item.with.property(
'path',
'navigation.gnss.satellites'
)
delta.updates[0].values.should.contain.an.item.with.property(
'path',
'navigation.gnss.antennaAltitude'
)
delta.updates[0].values.should.contain.an.item.with.property(
'path',
'navigation.gnss.horizontalDilution'
)
delta.updates[0].values.should.contain.an.item.with.property(
'path',
'navigation.gnss.geoidalSeparation'
)
delta.updates[0].values.should.contain.an.item.with.property(
'path',
'navigation.gnss.differentialAge'
)
delta.updates[0].values.should.contain.an.item.with.property(
'path',
'navigation.gnss.differentialReference'
)
delta.updates[0].values.should.contain.an.item.with.property(
'path',
'navigation.state'
)

// Values
delta.updates[0].values[0].value.should.deep.equal({
longitude: -44.369083333333336,
latitude: -2.583965,
})
delta.updates[0].values[1].value.should.equal('GPS: Autonomous (non-differential),GNSS: Constellation not in use, or no valid fix,Galileo: Constellation not in use, or no valid fix')
delta.updates[0].values[2].value.should.equal(12)
delta.updates[0].values[3].value.should.equal(8.5)
delta.updates[0].values[4].value.should.equal(0.8)
delta.updates[0].values[5].value.should.equal(-22.3)
delta.updates[0].values[6].value.should.equal(0)
delta.updates[0].values[7].value.should.equal(0)
delta.updates[0].values[8].value.should.equal("Safe")
toFull(delta).should.be.validSignalK
})

it("Doesn't choke on empty sentences", () => {
const delta = new Parser().parse('$GPGNS,,,,,,,,,,,,,S*59')
should.equal(delta, null)
})
})