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

Meteorological and hydrographic data from AIS VDM binary message 8 #245

Merged
merged 29 commits into from
Feb 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
47774ba
feature: aton meteorological data
KEGustafsson May 16, 2023
8e8d859
fix: use SI units
KEGustafsson May 22, 2023
c226243
fix: ice table correction
KEGustafsson May 22, 2023
c9938f9
fixes to meteo data functions
KEGustafsson Jun 7, 2023
90d176a
Merge branch 'SignalK:master' into met-hyd-data
KEGustafsson Dec 25, 2023
b727a2b
fix: contextPrefix and paths changed
KEGustafsson Dec 25, 2023
9ce8418
fix: meteo data path modifications
KEGustafsson Dec 25, 2023
f30118a
fix: testing
KEGustafsson Dec 25, 2023
f00ce24
fix: paths for surfaceCurrents
KEGustafsson Dec 26, 2023
82ebd91
fix: regrouping paths
KEGustafsson Dec 26, 2023
51f24e0
fix: paths updated and using new features from ggencoder
KEGustafsson Jan 12, 2024
57072d6
fix: cleanup
KEGustafsson Jan 12, 2024
aac5656
fix: new paths and logic for meteo data
KEGustafsson Jan 17, 2024
82b35cf
fix: declaration for delta
KEGustafsson Jan 17, 2024
f16568f
Merge branch 'SignalK:master' into met-hyd-data
KEGustafsson Jan 17, 2024
b96217d
fix: error corrections and fine tunings
KEGustafsson Jan 17, 2024
bdb0e42
fix: regrouping path values and meteo date added
KEGustafsson Jan 19, 2024
f56dc2a
fix: water.waves.direction to True
KEGustafsson Jan 20, 2024
401d140
fix: combine functions
KEGustafsson Jan 20, 2024
70c9567
fix: minor changes to paths
KEGustafsson Jan 21, 2024
406f45d
fix: meteo observation date to ISO string
KEGustafsson Jan 21, 2024
f119621
fix: overRange for horizontalVisibility
KEGustafsson Jan 21, 2024
cb2c208
fix: format
KEGustafsson Jan 22, 2024
9821a26
fix: paths adjustments and minor formats
KEGustafsson Jan 26, 2024
9b0b352
fix: ouside added to horizontalVisibility paths
KEGustafsson Feb 4, 2024
6887a9d
add meteo test
tkurki Feb 10, 2024
f7ad7c5
fix: VDM meteo tests
KEGustafsson Feb 11, 2024
c852815
add: more VDM meteo test cases
KEGustafsson Feb 11, 2024
6b943d7
ggencoder@1.0.8
tkurki Feb 17, 2024
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
161 changes: 150 additions & 11 deletions hooks/VDM.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ const debug = require('debug')('signalk-parser-nmea0183/VDM')
const utils = require('@signalk/nmea0183-utilities')
const Decoder = require('ggencoder').AisDecode
const schema = require('@signalk/signalk-schema')
const knotsToMs = (v) =>
parseFloat(utils.transform(v, 'knots', 'ms').toFixed(2))
const degToRad = (v) => utils.transform(v, 'deg', 'rad')
const cToK = (v) => parseFloat(utils.transform(v, 'c', 'k').toFixed(2))
const nmToM = (v) => parseFloat(utils.transform(v, 'nm', 'm').toFixed(2))

const stateMapping = {
0: 'motoring',
Expand Down Expand Up @@ -67,6 +72,50 @@ const specialManeuverMapping = {
3: 'reserved',
}

const beaufortScale = {
0: 'calm, 0-0.2 m/s',
1: 'light air, 0.3-1.5 m/s',
2: 'light breeze, 1.6-3.3 m/s',
3: 'gentle breeze, 3.4-5.4 m/s',
4: 'moderate breeze, 5.5-7.9 m/s',
5: 'fresh breeze, 8-10.7 m/s',
6: 'strong breeze, 10.8-13.8 m/s',
7: 'high wind, 13.9-17.1 m/s',
8: 'gale, 17.2-20.7 m/s',
9: 'strong gale, 20.8-24.4 m/s',
10: 'storm, 24.5-28.4 m/s',
11: 'violent storm, 28.5-32.6 m/s',
12: 'hurricane-force, ≥ 32.7 m/s',
13: 'not available',
14: 'reserved',
15: 'reserved',
}

const statusTable = {
0: 'steady',
1: 'decreasing',
2: 'increasing',
3: 'not available',
}

const precipitationType = {
0: 'reserved',
1: 'rain',
2: 'thunderstorm',
3: 'freezing rain',
4: 'mixed/ice',
5: 'snow',
6: 'reserved',
7: 'not available',
}

const iceTable = {
0: 'no',
1: 'yes',
2: 'reserved',
3: 'not available',
}

module.exports = function (input, session) {
const { id, sentence, parts, tags } = input
const data = new Decoder(sentence, session)
Expand Down Expand Up @@ -115,16 +164,6 @@ module.exports = function (input, session) {
})
}

if (data.lon && data.lat) {
values.push({
path: 'navigation.position',
value: {
longitude: data.lon,
latitude: data.lat,
},
})
}

if (data.length) {
values.push({
path: 'design.length',
Expand Down Expand Up @@ -262,18 +301,118 @@ module.exports = function (input, session) {
}

if (typeof data.fid !== 'undefined') {
if (data.fid == 31 || data.fid == 11 || data.fid == 33) {
contextPrefix = 'meteo.'
}
values.push({
path: 'sensors.ais.functionalId',
value: data.fid,
})
}

if (data.lon && data.lat) {
values.push({
path: 'navigation.position',
value: {
longitude: data.lon,
latitude: data.lat,
},
})
}

;[
['avgwindspd', 'wind.averageSpeed', knotsToMs],
['windgust', 'wind.gust', knotsToMs],
['winddir', 'wind.directionTrue', degToRad],
['windgustdir', 'wind.gustDirectionTrue', degToRad],
['airtemp', 'outside.temperature', cToK],
['relhumid', 'outside.relativeHumidity', (v) => v],
['dewpoint', 'outside.dewPointTemperature', cToK],
['airpress', 'outside.pressure', (v) => v * 100],
['waterlevel', 'water.level', (v) => v],
['signwavewhgt', 'water.waves.significantHeight', (v) => v],
['waveperiod', 'water.waves.period', (v) => v],
['wavedir', 'water.waves.directionTrue', degToRad],
['swellhgt', 'water.swell.height', (v) => v],
['swellperiod', 'water.swell.period', (v) => v],
['swelldir', 'water.swell.directionTrue', degToRad],
['watertemp', 'water.temperature', cToK],
['salinity', 'water.salinity', (v) => v],
['surfcurrspd', 'water.current.drift', knotsToMs],
['surfcurrdir', 'water.current.set', degToRad],
].forEach(([propName, path, f]) => {
if (data[propName] !== undefined) {
contextPrefix = 'meteo.'
values.push({
path: `environment.` + path,
value: f(data[propName]),
})
}
})
;[
['ice', 'water.ice', iceTable],
['precipitation', 'outside.precipitation', precipitationType],
['seastate', 'water.seaState', beaufortScale],
['waterlevelten', 'water.levelTendency', statusTable],
['airpressten', 'outside.pressureTendency', statusTable],
].forEach(([propName, path, f]) => {
if (data[propName] !== undefined) {
contextPrefix = 'meteo.'
values.push(
{
path: `environment.` + path,
value: f[data[propName]],
},
{
path: `environment.` + path + `Value`,
value: data[propName],
}
)
}
})

if (data.horvisib !== undefined && data.horvisibrange !== undefined) {
contextPrefix = 'meteo.'
values.push(
{
path: 'environment.outside.horizontalVisibility',
value: utils.transform(data.horvisib, 'nm', 'm'),
},
{
path: 'environment.outside.horizontalVisibility.overRange',
value: data.horvisibrange,
}
)
}

if (
data.utcday !== undefined &&
data.utchour !== undefined &&
data.utcminute !== undefined
) {
contextPrefix = 'meteo.'
const y = new Date().getUTCFullYear()
const m = new Date().getUTCMonth() + 1
const d = data.utcday
const h = data.utchour
const min = data.utcminute
const date = `${y}-${m.toString().padStart(2, '0')}-${d
.toString()
.padStart(2, '0')}T${h.toString().padStart(2, '0')}:${min
.toString()
.padStart(2, '0')}:00.000Z`
values.push({
path: 'environment.date',
value: date,
})
}

if (values.length === 0) {
return null
}

const delta = {
context: contextPrefix + `urn:mrn:imo:mmsi:${data.mmsi}`,
context: contextPrefix + `urn:mrn:imo:mmsi:${data.mmsikey || data.mmsi}`,
updates: [
{
source: tags.source,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"dependencies": {
"@signalk/nmea0183-utilities": "^0.8.0",
"@signalk/signalk-schema": "^1.7.1",
"ggencoder": "^1.0.4",
"ggencoder": "^1.0.8",
"moment-timezone": "^0.5.21",
"split": "^1.0.1"
},
Expand Down
48 changes: 48 additions & 0 deletions test/VDM.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,4 +237,52 @@ describe('VDM', function () {
.filter((pathValue) => pathValue.path === '')[3]
.value.registrations.imo.should.equal('IMO 1010258')
})

it('meteo single sentence converts ok', () => {
const delta = new Parser().parse(
'!AIVDM,1,1,,A,8@2R5Ph0GhOCT1a2VvkrgwvlFR06EuOwgqrqwnSwe7wvlOwwsAwwnSGmwvwt,0*40'
)
delta.context.should.equal('meteo.urn:mrn:imo:mmsi:002655619:366097')
const currentYear = new Date().getFullYear();
const output = [
['environment.water.level', -0.17],
['environment.water.levelTendency', 'steady'],
['environment.water.levelTendencyValue', 0],
['environment.date', currentYear + '-02-22T15:42:00.000Z']
]
output.forEach(([path, value]) =>
delta.updates[0].values
.find((pathValue) => pathValue.path === path)
.value.should.equal(value)
)
})

it('meteo dual sentence converts ok', () => {
const meteoSentences = [
'!AIVDM,2,1,4,A,8@2R5Ph0GhENJAb8wnScjAJ:AB06EuOwgwl?wnSwe7wvlOwwsAwwnSGm,0*15',
'!AIVDM,2,2,4,A,wvwt,0*10',
]
const parser = new Parser()
let delta = parser.parse(meteoSentences[0])
should.equal(delta, null)
delta = parser.parse(meteoSentences[1])
delta.context.should.equal('meteo.urn:mrn:imo:mmsi:002655619:967728')
delta.updates[0].values[3].value.longitude.should.equal(11.7283)
delta.updates[0].values[3].value.latitude.should.equal(57.9669)
const currentYear = new Date().getFullYear()
const output = [
['sensors.ais.designatedAreaCode', 1],
['sensors.ais.functionalId', 31],
['environment.wind.averageSpeed', 9.26],
['environment.wind.gust', 11.32],
['environment.wind.directionTrue', 4.817108736604238],
['environment.wind.gustDirectionTrue', 4.817108736604238],
['environment.date', currentYear + '-02-20T14:47:00.000Z'],
]
output.forEach(([path, value]) =>
delta.updates[0].values
.find((pathValue) => pathValue.path === path)
.value.should.equal(value)
)
})
})
Loading