Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
87 changes: 87 additions & 0 deletions hooks/PASHR.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* Copyright 2019 Signal K and Fabian Tollenaar <fabian@decipher.industries>.
*
* 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/PASHR')
const utils = require('@signalk/nmea0183-utilities')

/**
* $PASHR,225444.123,125.12,T,12.45,11.91,xxx.xx,1.123,1.456,2.653,1,4*38
*
* Roll and pitch information - https://help.fieldsystems.trimble.com/r780/nmea0183-messages-pashr.htm
*
* 0) 225444.123 UTC time of fix 22:54:44.123
* 1,2) 125.12,T True Heading, flag to indicate that the Heading is True Heading
* 3) 12.45 Roll Angle in degrees
* 4) 11.91 Pitch Angle in degrees
* 5) xxx.xx Reserved Field (Not Supported)
* 6) 1.123 Roll Angle accuracy estimate in degrees
* 7) 1.456 Pitch Angle accuracy estimate in degrees
* 8) 2.653 Heading Angle accuracy estimate in degrees
* 9) 1 GNSS Quality [0-6]
* 10) 4 IMU Alignment Status [0-4]
**/


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

debug(`[PASHRHook] decoding sentence ${id} => ${sentence}`)

let state = {
timestamp: utils.timestamp(parts[0]),
trueHeading: parts[2] == 'T' ? parts[1] : null,
rollAngle: parts[3],
pitchAngle: parts[4],
rollAngleAccuracy: parts[6],
pitchAngleAccuracy: parts[7],
trueHeadingAccuracy: parts[8],
}

return {
updates: [
{
timestamp: state.timestamp,
source: tags.source,
values: [
{
path: 'navigation.headingTrue',
value: state.trueHeading ? utils.transform(utils.float(state.trueHeading), 'deg', 'rad') : null,
},
{
path: 'balance.rollAngle',
value: state.rollAngle ? utils.transform(utils.float(state.rollAngle), 'deg', 'rad') : null,
},
{
path: 'balance.pitchAngle',
value: state.pitchAngle ? utils.transform(utils.float(state.pitchAngle), 'deg', 'rad') : null,
},
{
path: 'balance.rollAngleAccuracy',
value: state.rollAngle && state.rollAngleAccuracy ? utils.transform(utils.float(state.rollAngleAccuracy), 'deg', 'rad') : null,
},
{
path: 'balance.pitchAngleAccuracy',
value: state.pitchAngle && state.pitchAngleAccuracy ? utils.transform(utils.float(state.pitchAngleAccuracy), 'deg', 'rad') : null,
},
{
path: 'navigation.headingTrueAccuracy',
value: state.trueHeading && state.trueHeadingAccuracy ? utils.transform(utils.float(state.trueHeadingAccuracy), 'deg', 'rad') : null,
}
],
},
],
}
}
1 change: 1 addition & 0 deletions hooks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ module.exports = {
BWC: require('./BWC.js'),
BWR: require('./BWR.js'),
HSC: require('./HSC.js'),
PASHR: require('./PASHR.js')
}
187 changes: 187 additions & 0 deletions test/PASHR.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/**
* Copyright 2025 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.
*/

'use strict'

const Parser = require('../lib')
const chai = require('chai')
const { expect } = require('chai')
const utils = require('@signalk/nmea0183-utilities')

chai.Should()

chai.use(require('chai-things'))

// {
// path: 'navigation.headingTrue',
// value: utils.transform(utils.float(state.trueHeading), 'deg', 'rad'),
// },
// {
// path: 'balance.rollAngle',
// value: utils.transform(utils.float(state.rollAngle), 'deg', 'rad'),
// },
// {
// path: 'balance.pitchAngle',
// value: utils.transform(utils.float(state.pitchAngle), 'deg', 'rad'),
// },
// {
// path: 'balance.rollAngleAccuracy',
// value: utils.float(state.rollAngleAccuracy),
// },
// {
// path: 'balance.pitchAngleAccuracy',
// value: utils.float(state.pitchAngleAccuracy),
// }


describe('PASHR', () => {
it('Should understand full trame', () => {
const delta = new Parser().parse('$PASHR,225444.123,125.12,T,12.45,11.91,xxx.xx,1.123,1.456,2.653,1,4*56')
delta.should.be.an('object')

testAllKeys(delta)

// headingTrue
delta.updates[0].values[0].value.should.be.closeTo(
2, 18166156,
0.01
)

// rollAngle
delta.updates[0].values[1].value.should.be.closeTo(
0, 2712,
0.01
)

// pitchAngle
delta.updates[0].values[2].value.should.be.closeTo(
0, 2078,
0.01
)

// rollAngleAccuracy
delta.updates[0].values[3].value.should.be.closeTo(
0.0196,
0.01
)

// pitchAngleAccuracy
delta.updates[0].values[4].value.should.be.closeTo(
0.0254,
0.01
)

// headingTrueAccuracy
delta.updates[0].values[5].value.should.be.closeTo(
0.0463,
0.01
)
})


it('Should dont set heading if the flag isnt present', () => {
const delta = new Parser().parse('$PASHR,225444.123,125.12,,12.45,11.91,xxx.xx,1.123,1.456,2.653,1,4*02')
delta.should.be.an('object')

testAllKeys(delta)

// headingTrue
expect(delta.updates[0].values[0].value).to.be.null

// rollAngle
delta.updates[0].values[1].value.should.be.closeTo(
0, 2712,
0.01
)

// pitchAngle
delta.updates[0].values[2].value.should.be.closeTo(
0, 2078,
0.01
)

// rollAngleAccuracy
delta.updates[0].values[3].value.should.be.closeTo(
0.0196,
0.01
)

// pitchAngleAccuracy
delta.updates[0].values[4].value.should.be.closeTo(
0.0254,
0.01
)

// headingTrueAccuracy
expect(delta.updates[0].values[5].value).to.be.null
})


it('Should handle all null values', () => {
const delta = new Parser().parse('$PASHR,225444.123,,,,,,,,,,*6b')
delta.should.be.an('object')

testAllKeys(delta)

// headingTrue
expect(delta.updates[0].values[0].value).to.be.null

// rollAngle
expect(delta.updates[0].values[1].value).to.be.null


// pitchAngle
expect(delta.updates[0].values[2].value).to.be.null


// rollAngleAccuracy
expect(delta.updates[0].values[3].value).to.be.null


// pitchAngleAccuracy
expect(delta.updates[0].values[4].value).to.be.null


// headingTrueAccuracy
expect(delta.updates[0].values[5].value).to.be.null
})

})


function testAllKeys(delta) {
delta.updates[0].values.should.contain.an.item.with.property(
"path",
'navigation.headingTrue',
)

delta.updates[0].values.should.contain.an.item.with.property(
"path",
'balance.rollAngle',
)
delta.updates[0].values.should.contain.an.item.with.property(
"path",
'balance.pitchAngle',
)
delta.updates[0].values.should.contain.an.item.with.property(
"path",
'balance.rollAngleAccuracy',
)
delta.updates[0].values.should.contain.an.item.with.property(
"path",
'balance.pitchAngleAccuracy',
)
}