From 7eb035c57ca0beb42be14fdb6a4a155683d98da5 Mon Sep 17 00:00:00 2001 From: Fabian Tollenaar Date: Wed, 21 Sep 2022 11:57:41 +0200 Subject: [PATCH 1/8] XDR parser using the excellent xdr-parser-plugin --- hooks/XDR.js | 13 +++++++++++++ package.json | 3 ++- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 hooks/XDR.js diff --git a/hooks/XDR.js b/hooks/XDR.js new file mode 100644 index 00000000..d7873b8c --- /dev/null +++ b/hooks/XDR.js @@ -0,0 +1,13 @@ +// $IIXDR,C,C,10.7,C,AIRTEMP,A,0.5,D,HEEL,A,-1.-3,D,TRIM,P,1.026,B,BARO,A,A,-4.-3,D,RUDDER*18 + +const xdrDictionary = require('xdr-parser-plugin/xdrDict'); +const xdrParser = require('xdr-parser-plugin/xdrParser'); + + +module.exports = function (input) { + const { sentence } = input + + const delta = xdrParser(sentence, xdrDictionary); + console.log(`Parsing ${sentence} >>> ` + JSON.stringify({ delta }, null, 2)); + return delta +} \ No newline at end of file diff --git a/package.json b/package.json index 122bbeaf..6423c8e0 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,8 @@ "@signalk/signalk-schema": "1.6.0", "ggencoder": "^1.0.4", "moment-timezone": "^0.5.21", - "split": "^1.0.1" + "split": "^1.0.1", + "xdr-parser-plugin": "^1.0.5" }, "devDependencies": { "@signalk/github-create-release": "^1.2.0", From 4e8b3543d4ce80eebcd0a0e7be509c506929d5da Mon Sep 17 00:00:00 2001 From: Fabian Tollenaar Date: Wed, 21 Sep 2022 12:02:53 +0200 Subject: [PATCH 2/8] Add hook to index --- hooks/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/hooks/index.js b/hooks/index.js index b2044687..253531f4 100644 --- a/hooks/index.js +++ b/hooks/index.js @@ -40,4 +40,5 @@ module.exports = { BWC: require('./BWC.js'), BWR: require('./BWR.js'), HSC: require('./HSC.js'), + XDR: require('./XDR.js'), } From b32d715d3be12d0b1c06241f8df5501863ded025 Mon Sep 17 00:00:00 2001 From: Fabian Tollenaar Date: Wed, 21 Sep 2022 12:09:20 +0200 Subject: [PATCH 3/8] Add dictionary resolving --- hooks/XDR.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/hooks/XDR.js b/hooks/XDR.js index d7873b8c..03339ae8 100644 --- a/hooks/XDR.js +++ b/hooks/XDR.js @@ -1,8 +1,22 @@ // $IIXDR,C,C,10.7,C,AIRTEMP,A,0.5,D,HEEL,A,-1.-3,D,TRIM,P,1.026,B,BARO,A,A,-4.-3,D,RUDDER*18 -const xdrDictionary = require('xdr-parser-plugin/xdrDict'); +const fs = require('fs'); const xdrParser = require('xdr-parser-plugin/xdrParser'); +const xdrDictionary = { definitions: [] }; +// Populate a dictionary +const xdrDictPath = require.resolve('xdr-parser-plugin/xdrDict'); +if (fs.existsSync(xdrDictPath)) { + try { + const json = JSON.parse(fs.readFileSync(xdrDictPath, 'utf-8')); + + if (json && Array.isArray(json.definitions)) { + xdrDictionary.definitions = [ ...json.definitions ]; + } + } catch (err) { + console.warn('No dictionary found for xdr-parser-plugin'); + } +} module.exports = function (input) { const { sentence } = input From a0a8623a9924e303afd68bf7249e363df243b797 Mon Sep 17 00:00:00 2001 From: Fabian Tollenaar Date: Wed, 21 Sep 2022 12:12:21 +0200 Subject: [PATCH 4/8] Add dictionary resolving --- hooks/XDR.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/hooks/XDR.js b/hooks/XDR.js index 03339ae8..7751fd0a 100644 --- a/hooks/XDR.js +++ b/hooks/XDR.js @@ -18,6 +18,18 @@ if (fs.existsSync(xdrDictPath)) { } } +xdrDictionary.definitions = [ + ...xdrDictionary.definitions, + { + type: "Air temperature", + data: "temperature", + units: "C", + name: "AIRTEMP", + expression: "(x+273.15)", + sk_path: "environment.outside.temperature", + } +] + module.exports = function (input) { const { sentence } = input From 6fdc06ca2ce2e32b89152b1204b2c1277edfcf76 Mon Sep 17 00:00:00 2001 From: Fabian Tollenaar Date: Wed, 21 Sep 2022 12:20:00 +0200 Subject: [PATCH 5/8] Add dictionary resolving --- hooks/XDR.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/hooks/XDR.js b/hooks/XDR.js index 7751fd0a..5fb93f40 100644 --- a/hooks/XDR.js +++ b/hooks/XDR.js @@ -27,6 +27,22 @@ xdrDictionary.definitions = [ name: "AIRTEMP", expression: "(x+273.15)", sk_path: "environment.outside.temperature", + }, + { + type: "Roll", + data: "Angles", + units: "Deg", + name: "HEEL", + expression: "(x*pi/180)", + sk_path: "navigation.attitude" + }, + { + type: "Outside temperature", + data: "Angles", + units: "Deg", + name: "RUDDER", + expression: "(x*pi/180)", + sk_path: "steering.rudderAngle" } ] From ba67ad18f63e3695f5b15711966c7f7773dcc24f Mon Sep 17 00:00:00 2001 From: Fabian Tollenaar Date: Wed, 21 Sep 2022 12:20:28 +0200 Subject: [PATCH 6/8] Add dictionary resolving --- hooks/XDR.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/XDR.js b/hooks/XDR.js index 5fb93f40..7b3b2e8a 100644 --- a/hooks/XDR.js +++ b/hooks/XDR.js @@ -50,6 +50,6 @@ module.exports = function (input) { const { sentence } = input const delta = xdrParser(sentence, xdrDictionary); - console.log(`Parsing ${sentence} >>> ` + JSON.stringify({ delta }, null, 2)); + console.log(`Parsing 4 ${sentence} >>> ` + JSON.stringify({ delta }, null, 2)); return delta } \ No newline at end of file From fcc93a133a7328c72eaeba0eaddfee5c92f767f0 Mon Sep 17 00:00:00 2001 From: Fabian Tollenaar Date: Wed, 21 Sep 2022 13:31:06 +0200 Subject: [PATCH 7/8] Rewrite functionality from xdr-parser-plugin, add test --- hooks/XDR.js | 125 ++++++++++++++++++++++++++++++++++++++++++++++----- package.json | 1 + test/XDR.js | 27 +++++++++++ 3 files changed, 141 insertions(+), 12 deletions(-) create mode 100644 test/XDR.js diff --git a/hooks/XDR.js b/hooks/XDR.js index 7b3b2e8a..15a97c36 100644 --- a/hooks/XDR.js +++ b/hooks/XDR.js @@ -1,7 +1,7 @@ // $IIXDR,C,C,10.7,C,AIRTEMP,A,0.5,D,HEEL,A,-1.-3,D,TRIM,P,1.026,B,BARO,A,A,-4.-3,D,RUDDER*18 +const math = require('math-expression-evaluator'); const fs = require('fs'); -const xdrParser = require('xdr-parser-plugin/xdrParser'); const xdrDictionary = { definitions: [] }; // Populate a dictionary @@ -21,7 +21,7 @@ if (fs.existsSync(xdrDictPath)) { xdrDictionary.definitions = [ ...xdrDictionary.definitions, { - type: "Air temperature", + type: "value", data: "temperature", units: "C", name: "AIRTEMP", @@ -29,17 +29,17 @@ xdrDictionary.definitions = [ sk_path: "environment.outside.temperature", }, { - type: "Roll", - data: "Angles", - units: "Deg", + type: "roll", + data: "angle", + units: "D", name: "HEEL", expression: "(x*pi/180)", sk_path: "navigation.attitude" }, { - type: "Outside temperature", - data: "Angles", - units: "Deg", + type: "value", + data: "angle", + units: "D", name: "RUDDER", expression: "(x*pi/180)", sk_path: "steering.rudderAngle" @@ -47,9 +47,110 @@ xdrDictionary.definitions = [ ] module.exports = function (input) { - const { sentence } = input + if (!Array.isArray(xdrDictionary.definitions)) { + return null; + } + + const isUpperCaseChar = (p, minLen = 0) => { + const num = parseFloat(p) + return (isNaN(num) && typeof p === 'string' && p.length > minLen && p.toUpperCase() === p) + } + + const { definitions } = xdrDictionary + const { parts } = input + const subs = {} + const boundaries = parts.slice(1).filter(p => isUpperCaseChar(p, 1)) + const values = [] + + for (const boundary of boundaries) { + const index = boundaries.indexOf(boundary); + const prevBoundary = index === 0 ? null : boundaries[index - 1]; + const elements = []; + let fill = false; + + for (p of parts.slice(1)) { + if (!fill && (!prevBoundary || p === prevBoundary) && elements.length === 0) { + fill = true; + } + + if (fill === false || p === boundary) { + fill = false; + continue + } + + if (p !== prevBoundary) { + elements.push(p); + } + } + + if (elements.length) { + subs[boundary] = elements + } + } + + for (const boundary of boundaries) { + const data = subs[boundary] + + let typeFlag = null + let value = null + let unit = null + + if (data.length === 3) { + ([ typeFlag, value, unit ] = data); + } + + if (value === null || typeFlag === null || unit === null) { + continue + } + + if (isNaN(parseFloat(value))) { + continue + } + + const def = definitions.find(({ name }) => (name === boundary)); + + if (!def) { + continue; + } + + if (def.units !== unit) { + // Not parsing as unit doesn't match + continue; + } + + const expression = def.expression.replace(/x/g, value); + const attitudeTypes = ['yaw', 'pitch', 'roll']; + + let path = def.sk_path; + let result = math.eval(expression); + + if (!result || isNaN(result)) { + continue + } + + result = parseFloat(result.toFixed(4)) - const delta = xdrParser(sentence, xdrDictionary); - console.log(`Parsing 4 ${sentence} >>> ` + JSON.stringify({ delta }, null, 2)); - return delta + if (attitudeTypes.includes(def.type.toLowerCase())) { + path = `${path}.${def.type}` + } + + values.push({ + path, + value: result + }) + } + + if (values.length === 0) { + return null; + } + + return { + updates: [ + { + source: 'XDR', + timestamp: new Date().toISOString(), + values, + }, + ], + } } \ No newline at end of file diff --git a/package.json b/package.json index 6423c8e0..838c0f57 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@signalk/nmea0183-utilities": "^0.8.0", "@signalk/signalk-schema": "1.6.0", "ggencoder": "^1.0.4", + "math-expression-evaluator": "^1.4.0", "moment-timezone": "^0.5.21", "split": "^1.0.1", "xdr-parser-plugin": "^1.0.5" diff --git a/test/XDR.js b/test/XDR.js new file mode 100644 index 00000000..ea3c77f1 --- /dev/null +++ b/test/XDR.js @@ -0,0 +1,27 @@ +// $IIXDR,C,C,10.7,C,AIRTEMP,A,0.5,D,HEEL,A,-1.-3,D,TRIM,P,1.026,B,BARO,A,A,-4.-3,D,RUDDER*18 + +const Parser = require('../lib') + +const chai = require('chai') +const expect = chai.expect + +chai.Should() +chai.use(require('chai-things')) + +describe('XDR', () => { + it('Converts OK using individual parser', () => { + const delta = new Parser().parse('$IIXDR,C,C,10.7,C,AIRTEMP,A,0.5,D,HEEL,A,-1.-3,D,TRIM,P,1.026,B,BARO,A,A,-4.-3,D,RUDDER*18') + + delta.updates[0].values.should.contain.an.item.with.property( + 'path', + 'environment.outside.temperature' + ) + delta.updates[0].values[0].value.should.be.closeTo(283.85, 0.005) + + delta.updates[0].values.should.contain.an.item.with.property( + 'path', + 'navigation.attitude.roll' + ) + delta.updates[0].values[1].value.should.be.closeTo(0.0087, 0.00005) + }) +}) From b5d2c4c4df240a277ad65578d72fd44341bf9bf0 Mon Sep 17 00:00:00 2001 From: Fabian Tollenaar Date: Wed, 21 Sep 2022 14:21:44 +0200 Subject: [PATCH 8/8] Add try/catch around xdr-parser-plugin --- hooks/XDR.js | 24 ++++++++++++++---------- package.json | 3 +-- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/hooks/XDR.js b/hooks/XDR.js index 15a97c36..b29eb824 100644 --- a/hooks/XDR.js +++ b/hooks/XDR.js @@ -4,18 +4,22 @@ const math = require('math-expression-evaluator'); const fs = require('fs'); const xdrDictionary = { definitions: [] }; -// Populate a dictionary -const xdrDictPath = require.resolve('xdr-parser-plugin/xdrDict'); -if (fs.existsSync(xdrDictPath)) { - try { - const json = JSON.parse(fs.readFileSync(xdrDictPath, 'utf-8')); - - if (json && Array.isArray(json.definitions)) { - xdrDictionary.definitions = [ ...json.definitions ]; +try { + // Populate a dictionary + const xdrDictPath = require.resolve('xdr-parser-plugin/xdrDict'); + if (fs.existsSync(xdrDictPath)) { + try { + const json = JSON.parse(fs.readFileSync(xdrDictPath, 'utf-8')); + + if (json && Array.isArray(json.definitions)) { + xdrDictionary.definitions = [ ...json.definitions ]; + } + } catch (err) { + console.warn('No dictionary found for xdr-parser-plugin'); } - } catch (err) { - console.warn('No dictionary found for xdr-parser-plugin'); } +} catch (e) { + console.warn('Using default dictionary'); } xdrDictionary.definitions = [ diff --git a/package.json b/package.json index 838c0f57..6b17b56f 100644 --- a/package.json +++ b/package.json @@ -31,8 +31,7 @@ "ggencoder": "^1.0.4", "math-expression-evaluator": "^1.4.0", "moment-timezone": "^0.5.21", - "split": "^1.0.1", - "xdr-parser-plugin": "^1.0.5" + "split": "^1.0.1" }, "devDependencies": { "@signalk/github-create-release": "^1.2.0",