mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 12:17:08 +00:00
Add decoders for Hydronav, LABOv3 navigation headers
This commit is contained in:
10
lib/www/server/lib/headers/error.js
Normal file
10
lib/www/server/lib/headers/error.js
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
class NavHeaderError extends Error {
|
||||
constructor (message, payload) {
|
||||
super (message);
|
||||
this.name = "NavHeaderError";
|
||||
this.payload = payload;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NavHeaderError;
|
||||
225
lib/www/server/lib/headers/hydronav.js
Normal file
225
lib/www/server/lib/headers/hydronav.js
Normal file
@@ -0,0 +1,225 @@
|
||||
|
||||
const NavHeaderError = require('./error');
|
||||
|
||||
/**
|
||||
* Detect if a Hydronav header is present in `buffer`.
|
||||
*/
|
||||
function detect (buffer) {
|
||||
const s = buffer.indexOf("@@@UHEAD");
|
||||
return s != -1 ? s : false;
|
||||
};
|
||||
|
||||
function parse (buffer) {
|
||||
const s = buffer.indexOf("@@@UHEAD");
|
||||
|
||||
if (s == -1) {
|
||||
return; // undefined
|
||||
}
|
||||
|
||||
const parser = [
|
||||
// @@@UHEAD marker
|
||||
(buf, ctx) => {
|
||||
if (buf.subarray(s, s+8).toString('ascii') != "@@@UHEAD") {
|
||||
console.log(s, buf.subarray(s, s+8).toString('ascii'));
|
||||
throw new NavHeaderError("Expected @@@UHEAD marker not found at position "+s, buf);
|
||||
}
|
||||
},
|
||||
|
||||
// Timestamp. We also need to pick the date from further down the message
|
||||
(buf, ctx) => {
|
||||
const t = buf.subarray(s+8, s+8+15).toString('ascii');
|
||||
const d = buf.subarray(s+105, s+105+10).toString('ascii');
|
||||
ctx.tstamp = new Date(d.replace(/(.{2})\/(.{2})\/(.{4})/, "$3-$2-$1") + t.replace(/(.{8}).(.*)/, "T$1.$2Z"));
|
||||
if (isNaN(ctx.tstamp)) {
|
||||
throw new NavHeaderError(`Invalid timestamp from '${d}' + '${t}'`, buf);
|
||||
}
|
||||
},
|
||||
|
||||
// GPS clock, whatever that means
|
||||
(buf, ctx) => {
|
||||
ctx.gpsClock = Number(String.fromCharCode(buf[s+24]));
|
||||
},
|
||||
|
||||
// Vessel name
|
||||
(buf, ctx) => {
|
||||
ctx.vesselName = buf.subarray(s+26, s+26+8).toString('ascii');
|
||||
},
|
||||
|
||||
// Station number (aka shotpoint)
|
||||
// NOTE this waypoint will not be valid if mode is offline
|
||||
(buf, ctx) => {
|
||||
ctx.shotPoint = Number(buf.subarray(s+35, s+35+7).toString('ascii'))
|
||||
},
|
||||
|
||||
// FSID
|
||||
// TODO Find out what this is!
|
||||
(buf, ctx) => {
|
||||
ctx.fsid = Number(buf.subarray(s+43, s+43+10).toString('ascii'))
|
||||
},
|
||||
|
||||
// Northing
|
||||
// NOTE We have no clue which CRS
|
||||
(buf, ctx) => {
|
||||
ctx.northing = Number(buf.subarray(s+54, s+54+11).toString('ascii'))
|
||||
const hemisphere = String.fromCharCode(buf[s+65]);
|
||||
switch (hemisphere) {
|
||||
case "N":
|
||||
case "n":
|
||||
break;
|
||||
case "S":
|
||||
case "s":
|
||||
ctx.northing *= -1;
|
||||
break;
|
||||
default:
|
||||
throw new NavHeaderError("Unexpected hemisphere marker for northings: "+hemisphere, buf);
|
||||
}
|
||||
},
|
||||
|
||||
// Easting
|
||||
// NOTE We have no clue which CRS
|
||||
(buf, ctx) => {
|
||||
ctx.easting = Number(buf.subarray(s+67, s+67+11).toString('ascii'))
|
||||
const hemisphere = String.fromCharCode(buf[s+78]);
|
||||
switch (hemisphere) {
|
||||
case "E":
|
||||
case "e":
|
||||
break;
|
||||
case "W":
|
||||
case "w":
|
||||
ctx.easting *= -1;
|
||||
break;
|
||||
default:
|
||||
throw new NavHeaderError("Unexpected hemisphere marker for eastings: "+hemisphere, buf);
|
||||
}
|
||||
},
|
||||
|
||||
// Speed
|
||||
// (we convert to metres per second)
|
||||
(buf, ctx) => {
|
||||
ctx.speed = Number(buf.subarray(s+80, s+80+6).toString('ascii'));
|
||||
const units = buf.subarray(s+86, s+86+2).toString('ascii');
|
||||
switch (units.toUpperCase()) {
|
||||
case "KT":
|
||||
ctx.speed *= (1.852/3.6);
|
||||
break;
|
||||
default:
|
||||
throw new NavHeaderError("Unexpected speed units: "+units, buf);
|
||||
}
|
||||
},
|
||||
|
||||
// Bearing
|
||||
// (in degrees)
|
||||
(buf, ctx) => {
|
||||
ctx.bearing = Number(buf.subarray(s+89, s+89+6).toString('ascii'));
|
||||
const units = buf.subarray(s+95, s+95+2).toString('ascii');
|
||||
switch (units.toUpperCase()) {
|
||||
case "DG":
|
||||
break;
|
||||
default:
|
||||
throw new NavHeaderError("Unexpected angular units: "+units, buf);
|
||||
}
|
||||
},
|
||||
|
||||
// Water depth
|
||||
// (in metres)
|
||||
(buf, ctx) => {
|
||||
ctx.waterDepth = Number(buf.subarray(s+98, s+98+5).toString('ascii'));
|
||||
const units = String.fromCharCode(buf[s+103]);
|
||||
switch (units.toUpperCase()) {
|
||||
case "M":
|
||||
break;
|
||||
default:
|
||||
throw new NavHeaderError("Unexpected depth units: "+units, buf);
|
||||
}
|
||||
},
|
||||
|
||||
// Date
|
||||
// Already taken care of along with the time part
|
||||
|
||||
// Distance offline
|
||||
// (meaning crossline?)
|
||||
(buf, ctx) => {
|
||||
ctx.crossline = Number(buf.subarray(s+116, s+116+5).toString('ascii'));
|
||||
const units = String.fromCharCode(buf[s+121]);
|
||||
switch (units.toUpperCase()) {
|
||||
case "M":
|
||||
break;
|
||||
default:
|
||||
throw new NavHeaderError("Unexpected crossline units: "+units, buf);
|
||||
}
|
||||
},
|
||||
|
||||
// Distance inline
|
||||
(buf, ctx) => {
|
||||
ctx.inline = Number(buf.subarray(s+123, s+123+8).toString('ascii'));
|
||||
const units = String.fromCharCode(buf[s+131]);
|
||||
switch (units.toUpperCase()) {
|
||||
case "M":
|
||||
break;
|
||||
default:
|
||||
throw new NavHeaderError("Unexpected inline units: "+units, buf);
|
||||
}
|
||||
},
|
||||
|
||||
// Line status
|
||||
(buf, ctx) => {
|
||||
const status = String.fromCharCode(buf[s+133]);
|
||||
switch (status) {
|
||||
case "0":
|
||||
ctx.lineStatus = "offline";
|
||||
break;
|
||||
case "1":
|
||||
ctx.lineStatus = "approach";
|
||||
break;
|
||||
case "2":
|
||||
ctx.lineStatus = "online";
|
||||
break;
|
||||
case "3":
|
||||
ctx.lineStatus = "runout";
|
||||
break;
|
||||
default:
|
||||
throw new NavHeaderError("Unrecognised line status: "+status, buf);
|
||||
}
|
||||
},
|
||||
|
||||
// GPS height
|
||||
(buf, ctx) => {
|
||||
ctx.gpsHeight = Number(buf.subarray(s+135, s+135+5).toString('ascii'));
|
||||
const units = String.fromCharCode(buf[s+140]);
|
||||
switch (units.toUpperCase()) {
|
||||
case "M":
|
||||
break;
|
||||
default:
|
||||
throw new NavHeaderError("Unexpected GPS height units: "+units, buf);
|
||||
}
|
||||
},
|
||||
|
||||
// Line name
|
||||
(buf, ctx) => {
|
||||
ctx.lineName = buf.subarray(s+0x8e, s+0x8e+16).toString('ascii');
|
||||
},
|
||||
|
||||
// End of Hydronav data
|
||||
(buf, ctx) => {
|
||||
if (String.fromCharCode(buf[s+0x9e]) != "*") {
|
||||
throw new NavHeaderError("Missing end of HYDRONAV data marker '*'", buf);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const hydronav = {};
|
||||
hydronav._type = "hydronav";
|
||||
hydronav._received = new Date();
|
||||
for (const fn of parser) {
|
||||
fn(buffer, hydronav);
|
||||
}
|
||||
|
||||
return hydronav;
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
name: "Hydronav",
|
||||
detect,
|
||||
parse
|
||||
};
|
||||
5
lib/www/server/lib/headers/index.js
Normal file
5
lib/www/server/lib/headers/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
hydronav: require('./hydronav'),
|
||||
labo: require('./labo'),
|
||||
smartsource: require('./smartsource')
|
||||
};
|
||||
210
lib/www/server/lib/headers/labo.js
Normal file
210
lib/www/server/lib/headers/labo.js
Normal file
@@ -0,0 +1,210 @@
|
||||
|
||||
const NavHeaderError = require('./error');
|
||||
|
||||
/**
|
||||
* Detect if a LABO header is present in `buffer`.
|
||||
*/
|
||||
function detect (buffer) {
|
||||
const s = buffer.indexOf("$1");
|
||||
return (s != -1 &&
|
||||
buffer.subarray(s+6, s+10).toString('ascii') == "0003") ? s : false;
|
||||
};
|
||||
|
||||
function parse (buffer) {
|
||||
|
||||
const s = buffer.indexOf("$1");
|
||||
|
||||
if (s == -1) {
|
||||
return; // undefined
|
||||
}
|
||||
|
||||
let offset = 0;
|
||||
|
||||
function ascii (length) {
|
||||
const start = offset;
|
||||
const end = offset+length;
|
||||
offset += length;
|
||||
// const v = buffer.subarray(start, end).toString('ascii');
|
||||
// console.log("v = ", v);
|
||||
// return v;
|
||||
return buffer.subarray(start, end).toString('ascii');
|
||||
}
|
||||
|
||||
const parser = [
|
||||
// LABO marker
|
||||
(buf, ctx) => {
|
||||
if (! /\$[12]/.test(ascii(2))) {
|
||||
throw new NavHeaderError("Expected LABO marker not found at position "+s, buf);
|
||||
}
|
||||
},
|
||||
|
||||
// Message length
|
||||
// NOTE isn't this fixed?
|
||||
(buf, ctx) => {
|
||||
ctx.messageLength = Number(ascii(4));
|
||||
},
|
||||
|
||||
// Revision
|
||||
(buf, ctx) => {
|
||||
const rev = Number(ascii(4));
|
||||
if (rev != 3) {
|
||||
throw new NavHeaderError("Unsupported LABO header version: "+rev, buf);
|
||||
}
|
||||
},
|
||||
|
||||
// Line status
|
||||
(buf, ctx) => {
|
||||
const status = Number(ascii(2));
|
||||
switch (status) {
|
||||
case 1:
|
||||
ctx.lineStatus = "offline";
|
||||
break;
|
||||
case 2:
|
||||
ctx.lineStatus = "approach";
|
||||
break;
|
||||
case 3:
|
||||
ctx.lineStatus = "online";
|
||||
break;
|
||||
case 4:
|
||||
ctx.lineStatus = "runout";
|
||||
break;
|
||||
default:
|
||||
throw new NavHeaderError("Unrecognised line status: "+status, buf);
|
||||
}
|
||||
},
|
||||
|
||||
// Timestamp
|
||||
// We process a number of fields in one go
|
||||
(buf, ctx) => {
|
||||
const t = ascii(6);
|
||||
const ms = ascii(7);
|
||||
const d = ascii(8);
|
||||
const tref = ascii(3);
|
||||
|
||||
if (tref != "UTC" && tref != "GPS") {
|
||||
throw new NavHeaderError("Unrecognised time reference: "+tref, buf);
|
||||
}
|
||||
|
||||
const tstr =
|
||||
d.replace(/^(.{4})(.{2})(.{2})$/, "$1-$2-$3") + "T" +
|
||||
t.replace(/^(.{2})(.{2})(.{2})$/, "$1:$2:$3") +
|
||||
ms + "Z";
|
||||
|
||||
const tstamp = new Date(tstr);
|
||||
|
||||
if (isNaN(tstamp)) {
|
||||
throw new NavHeaderError("Invalid timestamp: "+tstr, buf);
|
||||
}
|
||||
ctx.tstamp = tstamp;
|
||||
},
|
||||
|
||||
// Shotpoint
|
||||
(buf, ctx) => {
|
||||
ctx.shotPoint = Number(ascii(6));
|
||||
},
|
||||
|
||||
// Line name
|
||||
(buf, ctx) => {
|
||||
ctx.lineName = ascii(16);
|
||||
},
|
||||
|
||||
// Master latitude
|
||||
(buf, ctx) => {
|
||||
ctx.latitudeMaster = Number(ascii(11));
|
||||
},
|
||||
|
||||
// Master longitude
|
||||
(buf, ctx) => {
|
||||
ctx.longitudeMaster = Number(ascii(11));
|
||||
},
|
||||
|
||||
// Master depth
|
||||
// in metres
|
||||
(buf, ctx) => {
|
||||
ctx.waterDepth = Number(ascii(6));
|
||||
},
|
||||
|
||||
// Source latitude
|
||||
(buf, ctx) => {
|
||||
ctx.latitude = Number(ascii(11));
|
||||
},
|
||||
|
||||
// Source longitude
|
||||
(buf, ctx) => {
|
||||
ctx.longitude = Number(ascii(11));
|
||||
},
|
||||
|
||||
// Source gyro
|
||||
// NOTE is this the same as Hydronav bearing?
|
||||
(buf, ctx) => {
|
||||
ctx.heading = Number(ascii(5));
|
||||
},
|
||||
|
||||
// Master CMG
|
||||
(buf, ctx) => {
|
||||
ctx.cmg = Number(ascii(5));
|
||||
},
|
||||
|
||||
// Master speed
|
||||
// FIXME Probably knots?
|
||||
// We convert to m/s
|
||||
(buf, ctx) => {
|
||||
ctx.speed = Number(ascii(4)) * 1.852 / 3.6;
|
||||
},
|
||||
|
||||
// Vessel ID
|
||||
(buf, ctx) => {
|
||||
ctx.vesselId = ascii(3);
|
||||
},
|
||||
|
||||
// Master easting
|
||||
(buf, ctx) => {
|
||||
ctx.eastingMaster = Number(ascii(11));
|
||||
},
|
||||
|
||||
// Master northing
|
||||
(buf, ctx) => {
|
||||
ctx.northingMaster = Number(ascii(11));
|
||||
},
|
||||
|
||||
// Source delta easting
|
||||
// NOTE This is the vessel's easting in Triggerfish
|
||||
(buf, ctx) => {
|
||||
ctx.deltaEasting = Number(ascii(7));
|
||||
},
|
||||
|
||||
// Source delta northing
|
||||
// NOTE This is the vessel's northing in Triggerfish
|
||||
(buf, ctx) => {
|
||||
ctx.deltaNorthing = Number(ascii(7));
|
||||
},
|
||||
|
||||
// Line bearing
|
||||
(buf, ctx) => {
|
||||
ctx.lineBearing = Number(5);
|
||||
}
|
||||
|
||||
// Julian day
|
||||
// NOTE We don't care about this, ignored
|
||||
|
||||
// -rstdiff option
|
||||
// NOTE We don't even know what this is, ignored
|
||||
];
|
||||
|
||||
const labo = {};
|
||||
labo._type = "labov3";
|
||||
labo._received = new Date();
|
||||
for (const fn of parser) {
|
||||
fn(buffer, labo);
|
||||
}
|
||||
|
||||
return labo;
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
name: "Labo v3",
|
||||
detect,
|
||||
parse
|
||||
};
|
||||
|
||||
14
lib/www/server/lib/headers/smartsource.js
Normal file
14
lib/www/server/lib/headers/smartsource.js
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
function detect (buffer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function parse (buffer) {
|
||||
return; // undefined
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
name: "SmartSource",
|
||||
detect,
|
||||
parse
|
||||
};
|
||||
Reference in New Issue
Block a user