From f3b0278b561de9fa1fd9edc774d8842ffbc1f5bb Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Mon, 15 Jan 2018 16:07:01 -0500 Subject: [PATCH 01/24] refactor: break conversion out into separate files and use Concentrate --- conversions/ais.js | 247 +++++++++++++++ conversions/gps.js | 21 ++ conversions/heading.js | 23 ++ conversions/systemTime.js | 29 ++ conversions/wind.js | 23 ++ index.js | 616 ++++++-------------------------------- package.json | 3 +- 7 files changed, 441 insertions(+), 521 deletions(-) create mode 100644 conversions/ais.js create mode 100644 conversions/gps.js create mode 100644 conversions/heading.js create mode 100644 conversions/systemTime.js create mode 100644 conversions/wind.js diff --git a/conversions/ais.js b/conversions/ais.js new file mode 100644 index 0000000..33bd0d4 --- /dev/null +++ b/conversions/ais.js @@ -0,0 +1,247 @@ +const _ = require('lodash') +const Concentrate = require("concentrate"); +const debug = require("debug")("signalk:signalk-to-nmea2000"); + +const static_keys = [ + "name", + "design.aisShipType", + "design.draft", + "design.length", + "design.beam", + "sensors.ais.fromCenter", + "sensors.ais.fromBow" , + "design.draft", + "registrations.imo" +] + +const position_keys = [ 'navigation.position' ] + +const static_pgn = 129794 +const position_pgn = 129038 + +module.exports = (app, plugin) => { + return { + title: `AIS (${static_pgn}, ${position_pgn})`, + type: 'onDelta', + optionKey: 'AIS', + callback: (delta) => { + var selfContext = 'vessels.' + app.selfId + + if ( delta.context == selfContext || isN2K(delta) ) { + return null + } + + var hasStatic = hasAnyKeys(delta, static_keys); + var hasPosition = hasAnyKeys(delta, position_keys) + + if ( !hasStatic && !hasPosition ) { + return null + } + + var vessel = _.get(app.signalk.root, delta.context) + var mmsi = _.get(vessel, 'mmsi') || findDeltaValue(delta, 'mmsi'); + + if ( !mmsi ) { + return null; + } + + var res = [] + if ( hasPosition ) { + res.push(generatePosition(vessel, mmsi, delta)) + } + + if ( hasStatic ) { + res.push(generateStatic(vessel, mmsi, delta)) + } + return res + } + } +} + +function generateStatic(vessel, mmsi, delta) { + var name = _.get(vessel, "name") || findDeltaValue(delta, 'name'); + + var type = _.get(findDeltaValue(delta, "design.aisShipType"), "id") + var callsign = findDeltaValue(delta, "communication.callsignVhf") + var length = _.get(findDeltaValue(delta, 'design.length'), 'overall') + var beam = findDeltaValue(delta, 'design.beam') + var fromCenter = findDeltaValue(delta, 'sensors.ais.fromCenter') + var fromBow = findDeltaValue(delta, 'sensors.ais.fromBow') + var draft = _.get(findDeltaValue(delta, 'design.draft'), 'maximum') + var imo = findDeltaValue(delta, 'registrations.imo') + + type = _.isUndefined(type) ? 0 : type + callsign = fillASCII(callsign ? callsign : '0', 7) + name = fillASCII(name ? name : '0', 20) + length = length ? length * 10 : 0xffff; + beam = beam ? beam * 10 : 0xffff; + draft = _.isUndefined(draft) ? 0xffff : draft * 100 + + if ( _.isUndefined(imo) ) { + imo = 0 + } else { + var parts = imo.split(imo) + imo = Number(parts[parts.length-1]) + } + + var fromStarboard = 0xffff + if ( beam && fromCenter ) { + fromStarboard = (beam / 2 + fromCenter) * 10 + } + fromBow = fromBow ? fromBow * 10 : 0xffff + + //2017-04-15T14:58:37.625Z,6,129794,43,255,76,05,28,e0,42,0f,0f,ee,8c,00,39,48,41,33,37,39,35,41,54,4c,41,4e,54,49,43,20,50,52,4f,4a,45,43,54,20,49,49,40,4f,8a,07,18,01,8c,00,fe,06,de,44,00,cc,bf,19,e8,03,52,55,20,4c,45,44,20,3e,20,55,53,20,42,41,4c,40,40,40,40,40,04,00,ff + + var dest = fillASCII('0', 20) + + mmsi = parseInt(mmsi, 10) + var data = Concentrate() + .uint8(0x05) + .uint32(mmsi) + .uint32(imo) + .buffer(callsign) + .buffer(name) + .uint8(type) + .uint16(length) + .uint16(beam) + .uint16(fromStarboard) + .uint16(fromBow) + .buffer(int8buff([0xff, 0xff, 0xff, 0xff, 0xff, 0xff])) + .uint16(draft) + .buffer(dest) + .buffer(int8buff([0x05,0x00,0xff])) + .result() + + return { pgn: static_pgn, buffer:data } +} + +function generatePosition(vessel, mmsi, delta) { + var position = findDeltaValue(delta, 'navigation.position') + + if ( position && position.latitude && position.longitude ) { + var cog = findDeltaValue(delta, 'navigation.courseOverGroundTrue') + var sog = findDeltaValue(delta, 'navigation.speedOverGround') + var heading = findDeltaValue(delta, 'navigation.headingTrue'); + var rot = findDeltaValue(delta, 'navigation.rateOfTurn') + + cog = _.isUndefined(cog) ? 0xffff : (Math.trunc(cog * 10000)) + sog = _.isUndefined(sog) ? 0xffff : (sog*100); + heading = _.isUndefined(heading) ? 0xffff : (Math.trunc(heading * 10000)) + rot = _.isUndefined(rot) ? 0x7fff : rot + + var latitude = position.latitude * 10000000; + var longitude = position.longitude * 10000000; + + /* + 2017-04-15T15:06:37.589Z,4,129038,43,255,28, + + 01, + ae,e7,e0,15, mmsi + 36,5c,76,d2, lon + 93,0b,52,17, lat + 94, RAIM/TS + 4d,e9, COG + 39,01, SOG + 7e,05,01, + ff,ff, heading + ff,7f, rat + 01, + 00, Nav Status, reserved + ff reserved + */ + + mmsi = parseInt(mmsi, 10) + var data = Concentrate() + .uint8(0x01) + .uint32(mmsi) + .int32(longitude) + .int32(latitude) + .uint8(0x94) + .uint16(cog) + .uint16(sog) + .uint8(0x7e) + .uint8(0x05) + .uint8(0x01) + .uint16(heading) + .int16(rot) + .uint8(0xff) + .uint8(0xff) + .result() + + return { pgn: position_pgn, buffer: data } + } else { + return null + } +} + +function int8buff(array) { + return new Buffer(new Uint8Array(array).buffer) +} + +function hasAnyKeys(delta, keys) { + if ( delta.updates ) { + for ( var i = 0; i < delta.updates.length; i++ ) { + for ( var j = 0; j < delta.updates[i].values.length; j++ ) { + var valuePath = delta.updates[i].values[j].path + var value = delta.updates[i].values[j].value + + if ( valuePath == '' ) { + if ( _.intersection(_.keys(value), keys).length > 0 ) { + return true + } + } else if ( keys.includes(valuePath) ) { + return true + } + } + } + } + return false +} + +function findDeltaValue(delta, path) { + if ( delta.updates ) { + for ( var i = 0; i < delta.updates.length; i++ ) { + for ( var j = 0; j < delta.updates[i].values.length; j++ ) { + var valuePath = delta.updates[i].values[j].path + var value = delta.updates[i].values[j].value + if ( valuePath == '' && path.indexOf('.') == -1 ) { + value = _.get(value, path) + if ( value ) { + return value + } + } else if ( path == valuePath ) { + return value + } + } + } + } + return undefined +} + +function fillASCII(theString, len) +{ + var res = [] + var i + for ( i = 0; i < len && i < theString.length; i++ ) + { + res.push(theString.charCodeAt(i)) + } + for ( ; i < len; i++ ) + { + res.push(0x40) + } + return new Buffer(new Uint8Array(res).buffer); +} + +function isN2K(delta) { + var res = false + if ( delta.updates ) { + delta.updates.forEach(update => { + var type = _.get(update, 'source.type') + if ( type && type == 'NMEA2000' ) { + res = true + } + }); + } + return res +} diff --git a/conversions/gps.js b/conversions/gps.js new file mode 100644 index 0000000..59229e9 --- /dev/null +++ b/conversions/gps.js @@ -0,0 +1,21 @@ +const Concentrate = require("concentrate"); + +module.exports = (app, plugin) => { + return { + title: 'Location (129025)', + type: 'toN2K', + optionKey: 'GPS_LOCATION', + keys: ["navigation.position"], + callback: (position) => { + return [ + { + pgn: 129025, + buffer: Concentrate() + .int32(position.latitude * 10000000) + .int32(position.longitude * 10000000) + .result() + } + ] + } + } +} diff --git a/conversions/heading.js b/conversions/heading.js new file mode 100644 index 0000000..18e20da --- /dev/null +++ b/conversions/heading.js @@ -0,0 +1,23 @@ + +module.exports = (app, plugin) => { + return { + pgn: 127250, + title: 'Heading (127250)', + type: 'toPgn', + optionKey: 'HEADING', + keys: [ + "navigation.headingMagnetic" + // ,'navigation.magneticVariation' + ], + callback: (heading, variation) => { + console.log(`heading ${heading}`) + return { + pgn: 127250, + SID: 87, + Heading: heading / 180 * Math.PI, + // "Variation": variation, + Reference: "Magnetic" + } + } + } +} diff --git a/conversions/systemTime.js b/conversions/systemTime.js new file mode 100644 index 0000000..8fba631 --- /dev/null +++ b/conversions/systemTime.js @@ -0,0 +1,29 @@ +const Concentrate = require("concentrate"); + +module.exports = (app, plugin) => { + return { + title: 'System Time (126992)', + type: 'timer', + interval: 1000, + optionKey: 'SYSTEM_TIME', + callback: (app) => { + var dateObj = new Date(); + var date = Math.trunc(dateObj.getTime() / 86400 / 1000); + var time = + dateObj.getUTCHours() * (60 * 60) + + dateObj.getUTCMinutes() * 60 + + dateObj.getUTCSeconds(); + time = time * 10000; + + return [ + { + pgn: 126992, + buffer: Concentrate() + .uint16(date) + .uint32(time) + .result() + } + ] + } + } +} diff --git a/conversions/wind.js b/conversions/wind.js new file mode 100644 index 0000000..bc6a1fb --- /dev/null +++ b/conversions/wind.js @@ -0,0 +1,23 @@ +const Concentrate = require("concentrate"); + +module.exports = (app, plugin) => { + return { + title: 'Wind (130306)', + type: 'toN2K', + optionKey: 'WIND', + keys: ["environment.wind.angleApparent", "environment.wind.speedApparent"], + callback: (angle, speed) => { + return [ + { + pgn: 130306, + buffer: Concentrate() + .uint8(0xff) + .uint16((speed*100).toFixed(0)) + .uint16((angle * 10000).toFixed(0)) + .result() + } + ] + } + } +} + diff --git a/index.js b/index.js index 271bad3..9546a66 100644 --- a/index.js +++ b/index.js @@ -3,11 +3,21 @@ const debug = require("debug")("signalk:signalk-to-nmea2000"); const util = require("util"); const toPgn = require("to-n2k").toPgn; const _ = require('lodash') +const path = require('path') +const fs = require('fs') module.exports = function(app) { var plugin = {}; var unsubscribes = []; - var timer; + var timers = [] + var conversions = load_conversions(app, plugin) + + var types = { + 'onDelta': mapOnDelta, + 'toPgn': mapToPgn, + 'toN2K': mapToNmea, + 'timer': mapTimer + } plugin.id = "sk-to-nmea2000"; plugin.name = "Convert Signal K to NMEA2000"; @@ -17,561 +27,127 @@ module.exports = function(app) { type: "object", title: "Conversions to NMEA2000", description: - "If there is SK data for the conversion generate the following NMEA2000 pgns from Signal K data:", - properties: { - WIND: { - title: "130306 Wind", - type: "boolean", - default: false - }, - GPS_LOCATION: { - title: "129025 Location", - type: "boolean", - default: false - }, - SYSTEM_TIME: { - title: "126992 System Time", - type: "boolean", - default: false - }, - HEADING: { - title: "127250 Heading", - type: "boolean", - default: false - }, - AIS: { - title: "AIS", - type: "boolean", - default: false - } - } + "If there is SignalK data for the conversion generate the following NMEA2000 pgns from Signal K data:", + properties: {} }; - plugin.start = function(options) { - debug("start"); - function mapToNmea(encoder) { - const selfStreams = encoder.keys.map( - app.streambundle.getSelfStream, - app.streambundle - ); - unsubscribes.push( - Bacon.combineWith(encoder.f, selfStreams) - .changes() - .debounceImmediate(20) - .onValue(nmeaString => { - if (nmeaString) { - debug("emit " + nmeaString); - app.emit("nmea2000out", nmeaString); - } - }) - ); + conversions.forEach(conversion => { + plugin.schema.properties[conversion.optionKey] = { + title: conversion.title, + type: 'boolean', + default: false } + }) + + plugin.start = function(options) { + debug("start"); - if (options.WIND) { - mapToNmea(WIND); - } - if (options.GPS_LOCATION) { - mapToNmea(GPS_LOCATION); - } - if (options.SYSTEM_TIME) { - timer = setInterval(send_date, 1000, app); - } - if (options.HEADING) { - mapToPgn(HEADING_127250); - } - if (options.AIS) { - mapOnDelta(AIS_CLASSA_STATIC) - mapOnDelta(AIS_CLASSA_POSITION) - } - app.on('unknownN2K', unknownN2K) + conversions.forEach(conversion => { + if ( options[conversion.optionKey] ) { + debug(`${conversion.title} is enabled`) + var mapper = types[conversion.type] + if ( _.isUndefined(mapper) ) { + console.error(`Unknown conversion type: ${conversion.type}`) + } else { + mapper(conversion) + } + } + }) }; plugin.stop = function() { unsubscribes.forEach(f => f()); unsubscribes = []; - if (timer) { - clearTimeout(timer); - timer = null; - } + timers.forEach(timer => clearInterval(timer)) + timers = [] }; return plugin; - function unknownN2K(chunk) { - if ( chunk.pgn === 59904 && chunk.dst == 0 ) { - if ( chunk.fields.PGN == 126464 ) { - var pgn = 128267 //130306 - var data = [ - 1, - pgn & 0xff, - (pgn >> 8) & 0xff, - (pgn >> 16) & 0xff - ] - app.emit("nmea2000out", toActisenseSerialFormat(126464, data, chunk.src)) - } + function toActisenseSerialFormat(pgn, data, dst) { + dst = _.isUndefined(dst) ? '255' : dst + return ( + new Date().toISOString() + + ",2," + + pgn + + `,0,${dst},` + + data.length + + "," + + new Uint32Array(data) + .reduce(function(acc, i) { + acc.push(i.toString(16)); + return acc; + }, []) + .map(x => (x.length === 1 ? "0" + x : x)) + .join(",") + ); + } + + function load_conversions (app, plugin) { + fpath = path.join(__dirname, 'conversions') + files = fs.readdirSync(fpath) + return files.map(fname => { + pgn = path.basename(fname, '.js') + return require(path.join(fpath, pgn))(app, plugin); + }).filter(converter => { return typeof converter !== 'undefined'; }); + } + + function processPGNs(pgns) { + if ( pgns ) { + pgns.filter(pgn => pgn != null).forEach(pgn => { + const msg = toActisenseSerialFormat(pgn.pgn, pgn.buffer); + debug("emit " + msg); + app.emit("nmea2000out", msg); + }) } } - function mapToPgn(mapping) { + function mapToPgn(conversion) { unsubscribes.push( Bacon.combineWith( - mapping.f, - mapping.keys.map(app.streambundle.getSelfStream, app.streambundle) + conversion.callback, + conversion.keys.map(app.streambundle.getSelfStream, app.streambundle) ) .changes() .debounceImmediate(20) .map(toPgn) .onValue(pgnBuffer => { if (pgnBuffer) { - const msg = toActisenseSerialFormat(mapping.pgn, pgnBuffer); + const msg = toActisenseSerialFormat(conversion.pgn, pgnBuffer); debug("emit " + msg); app.emit("nmea2000out", msg); } }) ); } - - function subscription_error(err) - { - console.log("error: " + err) - } - function mapSubscription(mapping) { - var subscription = { - "context": mapping.context, - subscribe: [] - } - - mapping.keys.forEach(key => { - subscription.subscribe.push({ path: key}) - }); - - debug("subscription: " + JSON.stringify(subscription)) - - app.subscriptionmanager.subscribe( - subscription, - unsubscribes, - subscription_error, - delta => { - var data = mapping.f(app, delta) - if ( data ) { - const msg = toActisenseSerialFormat(mapping.pgn, data); - debug("emit " + msg); - app.emit("nmea2000out", msg); - } - }); + function mapToNmea(conversion) { + const selfStreams = conversion.keys.map( + app.streambundle.getSelfStream, + app.streambundle + ); + unsubscribes.push( + Bacon.combineWith(conversion.callback, selfStreams) + .changes() + .debounceImmediate(20) + .onValue(pgns => processPGNs(pgns)) + ) } - - function mapOnDelta(mapping) { + + function mapOnDelta(conversion) { app.signalk.on('delta', (delta) => { - var data = mapping.f(app, delta) - if ( data ) { - const msg = toActisenseSerialFormat(mapping.pgn, data); - debug("emit " + msg); - app.emit("nmea2000out", msg); + try { + processPGNs(conversion.callback(delta)) + } catch ( err ) { + console.log(err) } - }); + }) } -}; - -function padd(n, p, c) { - var pad_char = typeof c !== "undefined" ? c : "0"; - var pad = new Array(1 + p).join(pad_char); - return (pad + n).slice(-pad.length); -} - -const wind_format = "%s,2,130306,1,255,8,ff,%s,%s,%s,%s,fa,ff,ff"; - -const WIND = { - keys: ["environment.wind.angleApparent", "environment.wind.speedApparent"], - f: function wind(angle, speed) { - speed = speed * 100; - angle = Math.trunc(angle * 10000); - return util.format( - wind_format, - new Date().toISOString(), - padd((speed & 0xff).toString(16), 2), - padd(((speed >> 8) & 0xff).toString(16), 2), - padd((angle & 0xff).toString(16), 2), - padd(((angle >> 8) & 0xff).toString(16), 2) - ); - } -}; - -const location_format = "%s,7,129025,1,255,8,%s,%s,%s,%s,%s,%s,%s,%s"; - -const GPS_LOCATION = { - keys: ["navigation.position"], - f: function location(pos) { - var lat = pos.latitude * 10000000; - var lon = pos.longitude * 10000000; - return util.format( - location_format, - new Date().toISOString(), - padd((lat & 0xff).toString(16), 2), - padd(((lat >> 8) & 0xff).toString(16), 2), - padd(((lat >> 16) & 0xff).toString(16), 2), - padd(((lat >> 24) & 0xff).toString(16), 2), - padd((lon & 0xff).toString(16), 2), - padd(((lon >> 8) & 0xff).toString(16), 2), - padd(((lon >> 16) & 0xff).toString(16), 2), - padd(((lon >> 24) & 0xff).toString(16), 2) - ); - } -}; - -const system_time_format = "%s,3,126992,1,255,8,ff,ff,%s,%s,%s,%s,%s,%s"; - -function send_date(app) { - var dateObj = new Date(); - var date = Math.trunc(dateObj.getTime() / 86400 / 1000); - var time = - dateObj.getUTCHours() * (60 * 60) + - dateObj.getUTCMinutes() * 60 + - dateObj.getUTCSeconds(); - time = time * 10000; - msg = util.format( - system_time_format, - new Date().toISOString(), - padd((date & 0xff).toString(16), 2), - padd(((date >> 8) & 0xff).toString(16), 2), - padd((time & 0xff).toString(16), 2), - padd(((time >> 8) & 0xff).toString(16), 2), - padd(((time >> 16) & 0xff).toString(16), 2), - padd(((time >> 24) & 0xff).toString(16), 2) - ); - debug("system time: " + msg); - app.emit("nmea2000out", msg); -} - -const HEADING_127250 = { - pgn: 127250, - keys: [ - "navigation.headingMagnetic" - // ,'navigation.magneticVariation' - ], - f: (heading, variation) => { - return { - pgn: 127250, - SID: 87, - Heading: heading / 180 * Math.PI, - // "Variation": variation, - Reference: "Magnetic" - }; + function mapTimer(conversion) { + timers.push(setInterval(() => { + processPGNs(conversion.callback(app)) + }, conversion.interval)); } }; -function fillASCII(theString, len) -{ - var res = [] - var i - for ( i = 0; i < len && i < theString.length; i++ ) - { - res.push(theString.charCodeAt(i)) - } - for ( ; i < len; i++ ) - { - res.push(0x40) - } - return res; -} - -function isN2K(delta) { - var res = false - if ( delta.updates ) { - delta.updates.forEach(update => { - var type = _.get(update, 'source.type') - if ( type && type == 'NMEA2000' ) { - res = true - } - }); - } - return res -} - -function hasAnyKeys(delta, keys) { - if ( delta.updates ) { - for ( var i = 0; i < delta.updates.length; i++ ) { - for ( var j = 0; j < delta.updates[i].values.length; j++ ) { - var valuePath = delta.updates[i].values[j].path - var value = delta.updates[i].values[j].value - - if ( valuePath == '' ) { - if ( _.intersection(_.keys(value), keys).length > 0 ) { - return true - } - } else if ( keys.includes(valuePath) ) { - return true - } - } - } - } - return false -} - -function findDeltaValue(delta, path) { - if ( delta.updates ) { - for ( var i = 0; i < delta.updates.length; i++ ) { - for ( var j = 0; j < delta.updates[i].values.length; j++ ) { - var valuePath = delta.updates[i].values[j].path - var value = delta.updates[i].values[j].value - if ( valuePath == '' && path.indexOf('.') == -1 ) { - value = _.get(value, path) - if ( value ) { - return value - } - } else if ( path == valuePath ) { - return value - } - } - } - } - return undefined -} - -const AIS_CLASSA_STATIC = { - pgn: 129794, - context: "vessels.*", - keys: [ - "name", - "design.aisShipType", - "design.draft", - "design.length", - "design.beam", - "sensors.ais.fromCenter", - "sensors.ais.fromBow" , - "design.draft", - "registrations.imo" - ], - f: function(app, delta) { - var selfContext = 'vessels.' + app.selfId - - if ( delta.context == selfContext || isN2K(delta) ) { - return null - } - - if ( !hasAnyKeys(delta, AIS_CLASSA_STATIC.keys) ) { - return null - } - - var vessel = _.get(app.signalk.root, delta.context) - var mmsi = _.get(vessel, 'mmsi') || findDeltaValue(delta, 'mmsi'); - - if ( !mmsi ) { - return null; - } - - var name = _.get(vessel, "name") || findDeltaValue(delta, 'name'); - - var type = _.get(findDeltaValue(delta, "design.aisShipType"), "id") - var callsign = findDeltaValue(delta, "communication.callsignVhf") - var length = _.get(findDeltaValue(delta, 'design.length'), 'overall') - var beam = findDeltaValue(delta, 'design.beam') - var fromCenter = findDeltaValue(delta, 'sensors.ais.fromCenter') - var fromBow = findDeltaValue(delta, 'sensors.ais.fromBow') - var draft = _.get(findDeltaValue(delta, 'design.draft'), 'maximum') - var imo = findDeltaValue(delta, 'registrations.imo') - - type = _.isUndefined(type) ? 0 : type - callsign = fillASCII(callsign ? callsign : '0', 7) - name = fillASCII(name ? name : '0', 20) - length = length ? length * 10 : 0xffff; - beam = beam ? beam * 10 : 0xffff; - draft = _.isUndefined(draft) ? 0xffff : draft * 100 - - if ( _.isUndefined(imo) ) { - imo = 0 - } else { - var parts = imo.split(imo) - imo = Number(parts[parts.length-1]) - } - - var fromStarboard = 0xffff - if ( beam && fromCenter ) { - fromStarboard = (beam / 2 + fromCenter) * 10 - } - fromBow = fromBow ? fromBow * 10 : 0xffff - - //2017-04-15T14:58:37.625Z,6,129794,43,255,76,05,28,e0,42,0f,0f,ee,8c,00,39,48,41,33,37,39,35,41,54,4c,41,4e,54,49,43,20,50,52,4f,4a,45,43,54,20,49,49,40,4f,8a,07,18,01,8c,00,fe,06,de,44,00,cc,bf,19,e8,03,52,55,20,4c,45,44,20,3e,20,55,53,20,42,41,4c,40,40,40,40,40,04,00,ff - - mmsi = parseInt(mmsi, 10) - var data = [ - 0x05, - mmsi & 0xff, - (mmsi >> 8) & 0xff, - (mmsi >> 16) & 0xff, - (mmsi >> 24) & 0xff, - imo & 0xff, - (imo >> 8) & 0xff, - (imo >> 16) & 0xff, - (imo >> 24) & 0xff - ] - - data = data.concat(callsign) - data = data.concat(name) - - data = data.concat([ - type & 0xff, - length & 0xff, - (length >> 8) & 0xff, - beam & 0xff, - (beam >> 8) & 0xff, - fromStarboard & 0xff, - (fromStarboard >> 8) & 0xff, - fromBow & 0xff, - (fromBow >> 8) & 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - draft & 0xff, - (draft >> 8 ) & 0xff - ]) - - var dest = fillASCII('0', 20) - data = data.concat(dest) - - data = data.concat([ - 0x05, - 0x00, - 0xff - ]) - - return data - } -} - -const AIS_CLASSA_POSITION = { - pgn: 129038, - context: "vessels.*", - keys: ["navigation.position" ], - f: function(app, delta) { - var selfContext = 'vessels.' + app.selfId - - if ( delta.context == selfContext || isN2K(delta) ) { - return null - } - - if ( !hasAnyKeys(delta, AIS_CLASSA_POSITION.keys) ) { - return null - } - - var vessel = _.get(app.signalk.root, delta.context) - var mmsi = _.get(vessel, 'mmsi') || findDeltaValue(delta, 'mmsi'); - - if ( !mmsi ) { - debug(`no mmsi:${JSON.stringify(delta)}`) - return null - } - - var position = findDeltaValue(delta, 'navigation.position') - - if ( position && position.latitude && position.longitude ) { - var cog = findDeltaValue(delta, 'navigation.courseOverGroundTrue') - var sog = findDeltaValue(delta, 'navigation.speedOverGround') - var heading = findDeltaValue(delta, 'navigation.headingTrue'); - var rot = findDeltaValue(delta, 'navigation.rateOfTurn') - - cog = _.isUndefined(cog) ? 0xffff : (Math.trunc(cog * 10000)) - sog = _.isUndefined(sog) ? 0xffff : (sog*100); - heading = _.isUndefined(heading) ? 0xffff : (Math.trunc(heading * 10000)) - rot = _.isUndefined(rot) ? 0xffff : rot - - var latitude = position.latitude * 10000000; - var longitude = position.longitude * 10000000; - - /* - 2017-04-15T15:06:37.589Z,4,129038,43,255,28, - - 01, - ae,e7,e0,15, mmsi - 36,5c,76,d2, lon - 93,0b,52,17, lat - 94, RAIM/TS - 4d,e9, COG - 39,01, SOG - 7e,05,01, - ff,ff, heading - ff,7f, rat - 01, - 00, Nav Status, reserved - ff reserved - */ - - mmsi = parseInt(mmsi, 10) - var data = [ - 0x01, - mmsi & 0xff, - (mmsi >> 8) & 0xff, - (mmsi >> 16) & 0xff, - (mmsi >> 24) & 0xff, - longitude & 0xff, - (longitude >> 8) & 0xff, - (longitude >> 16) & 0xff, - (longitude >> 24) & 0xff, - latitude & 0xff, - (latitude >> 8) & 0xff, - (latitude >> 16) & 0xff, - (latitude >> 24) & 0xff, - 0x94, - cog & 0xff, - (cog >> 8) & 0xff, - sog & 0xff, - (sog >> 8) & 0xff, - 0x7e, - 0x05, - 0x01, - heading & 0xff, - (heading >> 8) & 0xff, - rot & 0xff, - (rot >> 8 ) & 0xff, - 0xff, - 0xff - ]; - - return data - } else { - debug('no position') - return null - } - } -} - - -const AIS_ATON = { - pgn: 129041, - context: "atons.*", - keys: ["navigation.position", - "mmsi", - "name", - "atonType", - "design.length", - "design.beam", - "sensors.ais.fromCenter", - "sensors.ais.fromBow"], - f: function(app, delta) { - } -} - - -function toActisenseSerialFormat(pgn, data, dst) { - dst = _.isUndefined(dst) ? '255' : dst - return ( - new Date().toISOString() + - ",2," + - pgn + - `,0,${dst},` + - data.length + - "," + - new Uint32Array(data) - .reduce(function(acc, i) { - acc.push(i.toString(16)); - return acc; - }, []) - .map(x => (x.length === 1 ? "0" + x : x)) - .join(",") - ); -} diff --git a/package.json b/package.json index 522aca9..4174a1d 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "baconjs": "^0.7.88", "debug": "^3.1.0", "lodash": "^4.17.4", - "to-n2k": "^1.0.0" + "to-n2k": "^1.0.0", + "concentrate": "^0.2.3" }, "repository": { "type": "git", From fe012c50d118a109832caefd1ba38b2c5756fc0d Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Mon, 15 Jan 2018 21:38:01 -0500 Subject: [PATCH 02/24] fix: catch exceptions from concentrator --- conversions/wind.js | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/conversions/wind.js b/conversions/wind.js index bc6a1fb..7c0d116 100644 --- a/conversions/wind.js +++ b/conversions/wind.js @@ -7,16 +7,23 @@ module.exports = (app, plugin) => { optionKey: 'WIND', keys: ["environment.wind.angleApparent", "environment.wind.speedApparent"], callback: (angle, speed) => { - return [ - { - pgn: 130306, - buffer: Concentrate() - .uint8(0xff) - .uint16((speed*100).toFixed(0)) - .uint16((angle * 10000).toFixed(0)) - .result() - } - ] + try { + return [ + { + pgn: 130306, + buffer: Concentrate() + .uint8(0xff) + .uint16((speed*100).toFixed(0)) + .uint16((angle * 10000).toFixed(0)) + .uint8(0xfa) + .uint8(0xff) + .uint8(0xff) + .result() + } + ] + } catch ( err ) { + console.error(err) + } } } } From 84dc687fd1f5a14b5ace819405d36b86e02de92f Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Tue, 16 Jan 2018 14:18:44 -0500 Subject: [PATCH 03/24] fix: heading not encoded properly --- conversions/heading.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conversions/heading.js b/conversions/heading.js index 18e20da..61be08f 100644 --- a/conversions/heading.js +++ b/conversions/heading.js @@ -14,7 +14,7 @@ module.exports = (app, plugin) => { return { pgn: 127250, SID: 87, - Heading: heading / 180 * Math.PI, + Heading: heading, // "Variation": variation, Reference: "Magnetic" } From c8e7229a4add8cad45c77497ff8aa870c7eb5566 Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Tue, 16 Jan 2018 14:19:07 -0500 Subject: [PATCH 04/24] fix: system time not encoded properly --- conversions/systemTime.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conversions/systemTime.js b/conversions/systemTime.js index 8fba631..66a181c 100644 --- a/conversions/systemTime.js +++ b/conversions/systemTime.js @@ -19,6 +19,8 @@ module.exports = (app, plugin) => { { pgn: 126992, buffer: Concentrate() + .uint8(0xff) + .uint8(0xff) .uint16(date) .uint32(time) .result() From 5be8eee29775341fe51a51f221adfc03fd174f69 Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Tue, 16 Jan 2018 14:20:07 -0500 Subject: [PATCH 05/24] feature: add 129029 to gps.js --- conversions/gps.js | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/conversions/gps.js b/conversions/gps.js index 59229e9..9aa0e1a 100644 --- a/conversions/gps.js +++ b/conversions/gps.js @@ -1,13 +1,27 @@ +const _ = require('lodash') const Concentrate = require("concentrate"); +const debug = require("debug")("signalk:signalk-to-nmea2000"); +//const bignum = require('bignum') +const Int64LE = require('int64-buffer').Int64LE module.exports = (app, plugin) => { + var lastUpdate = null + return { title: 'Location (129025)', type: 'toN2K', optionKey: 'GPS_LOCATION', keys: ["navigation.position"], callback: (position) => { - return [ + //debug(`position: ${JSON.stringify(position)}`) + var dateObj = new Date(); + var date = Math.trunc(dateObj.getTime() / 86400 / 1000); + var time = + dateObj.getUTCHours() * (60 * 60) + + dateObj.getUTCMinutes() * 60 + + dateObj.getUTCSeconds(); + time = time * 10000; + var res = [ { pgn: 129025, buffer: Concentrate() @@ -16,6 +30,33 @@ module.exports = (app, plugin) => { .result() } ] + + + if ( + lastUpdate == null || + (new Date().getTime() - lastUpdate.getTime()) > 1000 + ) { + lastUpdate = new Date() + var lat = (position.latitude * 10000000 * 10000000 * 100) + var lon = (position.longitude * 10000000 * 10000000 * 100) + + var rest = new Buffer(new Uint8Array([0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x23,0xfc,0x10,0x40,0x00,0xff,0x7f,0xff,0xff,0xff,0x7f,0x00])) + + res = res.concat([ + { + pgn: 129029, + buffer: Concentrate() + .uint8(0xf3) + .uint16(date) + .uint32(time) + .buffer(new Int64LE(lat).toBuffer()) + .buffer(new Int64LE(lon).toBuffer()) + .buffer(rest) + .result() + } + ]) + } + return res } } } From e6bc1839182cec592dd857b73626e3b0fc1d0ce3 Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Tue, 16 Jan 2018 17:19:35 -0500 Subject: [PATCH 06/24] add: subscription mapping and dynamic properties --- index.js | 80 +++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 68 insertions(+), 12 deletions(-) diff --git a/index.js b/index.js index 9546a66..684d470 100644 --- a/index.js +++ b/index.js @@ -11,11 +11,13 @@ module.exports = function(app) { var unsubscribes = []; var timers = [] var conversions = load_conversions(app, plugin) + conversions = [].concat.apply([], conversions) var types = { 'onDelta': mapOnDelta, 'toPgn': mapToPgn, 'toN2K': mapToNmea, + 'toSubscription': mapSubscription, 'timer': mapTimer } @@ -23,33 +25,55 @@ module.exports = function(app) { plugin.name = "Convert Signal K to NMEA2000"; plugin.description = "Plugin to convert Signal K to NMEA2000"; - plugin.schema = { + var schema = { type: "object", title: "Conversions to NMEA2000", description: "If there is SignalK data for the conversion generate the following NMEA2000 pgns from Signal K data:", properties: {} }; + + updateSchema() + + function updateSchema() { + conversions.forEach(conversion => { + var obj = { + type: 'object', + title: conversion.title, + properties: { + enabled: { + title: 'Enabled', + type: 'boolean', + default: false + } + } + } + + schema.properties[conversion.optionKey] = obj + + if ( conversion.properties ) { + var props = typeof conversion.properties === 'function' ? conversion.properties() : conversion.properties + _.extend(obj.properties, props) + } + }) + } - conversions.forEach(conversion => { - plugin.schema.properties[conversion.optionKey] = { - title: conversion.title, - type: 'boolean', - default: false - } - }) + plugin.schema = function() { + updateSchema() + return schema + } plugin.start = function(options) { debug("start"); conversions.forEach(conversion => { - if ( options[conversion.optionKey] ) { + if ( options[conversion.optionKey] && options[conversion.optionKey].enabled ) { debug(`${conversion.title} is enabled`) var mapper = types[conversion.type] if ( _.isUndefined(mapper) ) { console.error(`Unknown conversion type: ${conversion.type}`) } else { - mapper(conversion) + mapper(conversion, options) } } }) @@ -121,8 +145,9 @@ module.exports = function(app) { ); } - function mapToNmea(conversion) { - const selfStreams = conversion.keys.map( + function mapToNmea(conversion, options) { + var keys = _.isFunction(conversion.keys) ? conversion.keys(options) : conversion.keys + const selfStreams = keys.map( app.streambundle.getSelfStream, app.streambundle ); @@ -149,5 +174,36 @@ module.exports = function(app) { processPGNs(conversion.callback(app)) }, conversion.interval)); } + + function subscription_error(err) + { + console.log("error: " + err) + } + + function mapSubscription(mapping, options) { + var subscription = { + "context": mapping.context, + subscribe: [] + } + + var keys = _.isFunction(mapping.keys) ? mapping.keys(options) : keys + keys.forEach(key => { + subscription.subscribe.push({ path: key}) + }); + + debug("subscription: " + JSON.stringify(subscription)) + + app.subscriptionmanager.subscribe( + subscription, + unsubscribes, + subscription_error, + delta => { + try { + processPGNs(mapping.callback(delta, options)) + } catch ( err ) { + console.log(err) + } + }); + } }; From c714f697f31b7c63c983142db2ebe7be6c20d4cc Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Tue, 16 Jan 2018 17:20:37 -0500 Subject: [PATCH 07/24] remove: debug logging --- conversions/heading.js | 1 - 1 file changed, 1 deletion(-) diff --git a/conversions/heading.js b/conversions/heading.js index 61be08f..fc7f2d8 100644 --- a/conversions/heading.js +++ b/conversions/heading.js @@ -10,7 +10,6 @@ module.exports = (app, plugin) => { // ,'navigation.magneticVariation' ], callback: (heading, variation) => { - console.log(`heading ${heading}`) return { pgn: 127250, SID: 87, From 6341799bc030c0fe4ec6b8b89e87f53a383974ff Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Tue, 16 Jan 2018 17:21:04 -0500 Subject: [PATCH 08/24] add: battery, COG and SOG, and depth --- conversions/battery.js | 130 +++++++++++++++++++++++++++++++++++++++++ conversions/cogSOG.js | 32 ++++++++++ conversions/depth.js | 33 +++++++++++ 3 files changed, 195 insertions(+) create mode 100644 conversions/battery.js create mode 100644 conversions/cogSOG.js create mode 100644 conversions/depth.js diff --git a/conversions/battery.js b/conversions/battery.js new file mode 100644 index 0000000..497a39c --- /dev/null +++ b/conversions/battery.js @@ -0,0 +1,130 @@ +const Concentrate = require("concentrate"); +const _ = require('lodash') + +module.exports = (app, plugin, options) => { + + function getBatteries() { + if ( _.get(app.signalk.self, "electrical.batteries") ) + { + return _.keys(app.signalk.self.electrical.batteries) + } else { + return undefined + } + } + + const batteryKeys = [ + 'voltage', + 'current', + 'temperature', + 'capacity.stateOfCharge', + 'capacity.timeRemaining', + 'capacity.stateOfHealth', + 'ripple' + ] + + return { + title: 'Battery (127506 & 127508)', + type: 'toSubscription', + optionKey: 'BATTERY', + context: 'vessels.self', + keys: (options) => { + var res = [] + _.keys(options.BATTERY).forEach(prop => { + if ( prop.startsWith('instance') && options.BATTERY[prop] ) { + var instance = prop.substring(8) + res = res.concat(batteryKeys.map(key => { + return `electrical.batteries.${instance}.${key}` + })) + } + }) + return res + }, + properties: () => { + var props = {} + var batteries = getBatteries() + if ( batteries ) { + batteries.forEach(instance => { + props[`instance${instance}`] = { + title: `Instance ${instance}`, + type: 'boolean', + default:false + } + }) + } + return props + }, + callback: (delta, options) => { + var flat = flattenDelta(delta) + var res = [] + _.keys(options.BATTERY).forEach(prop => { + if ( prop.startsWith('instance') && options.BATTERY[prop] ) { + var instance = prop.substring(8) + + function get(key) { + return flat[`electrical.batteries.${instance}.${key}`] + } + + var voltage = get('voltage'), + current = get('current'), + temperature = get('temperature'), + stateOfCharge = get('capacity.stateOfCharge'), + timeRemaining = get('capacity.timeRemaining'), + stateOfHealth = get('capacity.stateOfHealth'), + ripple = get('ripple') + + if ( !_.isUndefined(voltage) + || !_.isUndefined(current) + || !_.isUndefined(temperature) ) { + voltage = _.isUndefined(voltage) ? 0x7fff : voltage * 100 + temperature = _.isUndefined(temperature) ? 0xffff : temperature * 100 + current = _.isUndefined(current) ? 0x7fff : current * 10 + res.push({ + pgn: 127508, + buffer: Concentrate() + .uint8(instance) + .uint16(voltage) + .uint16(current) + .uint16(temperature) + .uint8(0xff) + .result() + }) + } + + if ( !_.isUndefined(stateOfCharge) + || !_.isUndefined(timeRemaining) + || !_.isUndefined(stateOfHealth) + || !_.isUndefined(ripple) ) { + stateOfCharge = _.isUndefined(stateOfCharge) ? 0xff : stateOfCharge*100 + timeRemaining = _.isUndefined(timeRemaining) ? 0xffff : timeRemaining + stateOfHealth = _.isUndefined(stateOfHealth) ? 0xff : stateOfHealth*100 + ripple = _.isUndefined(ripple) ? 0xffff : ripple * 100 + res.push({ + pgn: 127506, + buffer: Concentrate() + .uint8(0xff) + .uint8(instance) + .uint8(0x00) + .uint8(stateOfCharge) + .uint8(stateOfHealth) + .uint16(timeRemaining) + .uint16(ripple) + .result() + }) + } + } + }) + return res + } + } +} + +function flattenDelta(delta) { + var res = {} + delta.updates.forEach(update => { + update.values.forEach(pathValue => { + res[pathValue.path] = pathValue.value + }) + }) + return res +} + diff --git a/conversions/cogSOG.js b/conversions/cogSOG.js new file mode 100644 index 0000000..5ae02db --- /dev/null +++ b/conversions/cogSOG.js @@ -0,0 +1,32 @@ +const _ = require('lodash') +const Concentrate = require("concentrate"); +const debug = require("debug")("signalk:signalk-to-nmea2000"); + +module.exports = (app, plugin) => { + var lastUpdate = null + + return { + title: 'COG & SOG (129026)', + type: 'toN2K', + optionKey: 'COG_SOG', + keys: ["navigation.courseOverGroundTrue", "navigation.speedOverGround"], + callback: (course, speed) => { + try { + return [ + { + pgn: 129026, + buffer: Concentrate() + .uint8(0xff) + .uint8(0xfc) + .uint16((course * 10000).toFixed(0)) + .uint16((speed*100).toFixed(0)) + .uint16(0xffff) + .result() + } + ] + } catch ( err ) { + console.error(err) + } + } + } +} diff --git a/conversions/depth.js b/conversions/depth.js new file mode 100644 index 0000000..ae2bc09 --- /dev/null +++ b/conversions/depth.js @@ -0,0 +1,33 @@ +const Concentrate = require("concentrate"); +const _ = require('lodash') + +module.exports = (app, plugin) => { + return { + title: 'Depth (128267)', + type: 'toN2K', + optionKey: 'DEPTH', + keys: ["environment.depth.belowTransducer"], + callback: (belowTransducer) => { + var surfaceToTransducer = _.get(app.signalk.self, + 'environment.depth.surfaceToTransducer.value') + var transducerToKeel = _.get(app.signalk.self, + 'environment.depth.transducerToKeel.value') + var offset = _.isUndefined(surfaceToTransducer) ? (_.isUndefined(transducerToKeel) ? 0 : transducerToKeel) : surfaceToTransducer + try { + return [ + { + pgn: 128267, + buffer: Concentrate() + .uint8(0xff) + .uint32(belowTransducer * 100) + .uint16(offset * 1000) + .result() + } + ] + } catch ( err ) { + console.error(err) + } + } + } +} + From 471dd45118d88fd07924f3a166e7900309bf7aa9 Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Thu, 18 Jan 2018 16:23:21 -0500 Subject: [PATCH 09/24] update: use concentrate2 --- conversions/ais.js | 137 +++++++++++++++++++++++++++++++------- conversions/battery.js | 6 +- conversions/cogSOG.js | 4 +- conversions/depth.js | 4 +- conversions/gps.js | 14 ++-- conversions/systemTime.js | 4 +- conversions/wind.js | 4 +- package.json | 2 +- 8 files changed, 131 insertions(+), 44 deletions(-) diff --git a/conversions/ais.js b/conversions/ais.js index 33bd0d4..726c0b3 100644 --- a/conversions/ais.js +++ b/conversions/ais.js @@ -1,5 +1,5 @@ const _ = require('lodash') -const Concentrate = require("concentrate"); +const Concentrate2 = require("concentrate2"); const debug = require("debug")("signalk:signalk-to-nmea2000"); const static_keys = [ @@ -18,42 +18,54 @@ const position_keys = [ 'navigation.position' ] const static_pgn = 129794 const position_pgn = 129038 +const aton_pgn = 129041 module.exports = (app, plugin) => { return { - title: `AIS (${static_pgn}, ${position_pgn})`, + title: `AIS (${static_pgn}, ${position_pgn}, ${aton_pgn})`, type: 'onDelta', optionKey: 'AIS', callback: (delta) => { var selfContext = 'vessels.' + app.selfId - + if ( delta.context == selfContext || isN2K(delta) ) { return null } - var hasStatic = hasAnyKeys(delta, static_keys); - var hasPosition = hasAnyKeys(delta, position_keys) - - if ( !hasStatic && !hasPosition ) { - return null - } + if ( delta.context.startsWith('vessels.') ) { + var hasStatic = hasAnyKeys(delta, static_keys); + var hasPosition = hasAnyKeys(delta, position_keys) + + if ( !hasStatic && !hasPosition ) { + return null + } - var vessel = _.get(app.signalk.root, delta.context) - var mmsi = _.get(vessel, 'mmsi') || findDeltaValue(delta, 'mmsi'); - - if ( !mmsi ) { - return null; - } - + var vessel = _.get(app.signalk.root, delta.context) + var mmsi = _.get(vessel, 'mmsi') || findDeltaValue(delta, 'mmsi'); + + if ( !mmsi ) { + return null; + } + var res = [] - if ( hasPosition ) { - res.push(generatePosition(vessel, mmsi, delta)) - } - - if ( hasStatic ) { - res.push(generateStatic(vessel, mmsi, delta)) + if ( hasPosition ) { + res.push(generatePosition(vessel, mmsi, delta)) + } + + if ( hasStatic ) { + res.push(generateStatic(vessel, mmsi, delta)) + } + return res + } else if ( delta.context.startsWith('atons.') ) { + var vessel = _.get(app.signalk.root, delta.context) + var mmsi = _.get(vessel, 'mmsi') || findDeltaValue(delta, 'mmsi'); + + if ( !mmsi ) { + return + } + + return [ generateAtoN(vessel, mmsi, delta) ] } - return res } } } @@ -95,7 +107,7 @@ function generateStatic(vessel, mmsi, delta) { var dest = fillASCII('0', 20) mmsi = parseInt(mmsi, 10) - var data = Concentrate() + var data = Concentrate2() .uint8(0x05) .uint32(mmsi) .uint32(imo) @@ -151,7 +163,7 @@ function generatePosition(vessel, mmsi, delta) { */ mmsi = parseInt(mmsi, 10) - var data = Concentrate() + var data = Concentrate2() .uint8(0x01) .uint32(mmsi) .int32(longitude) @@ -174,6 +186,80 @@ function generatePosition(vessel, mmsi, delta) { } } +function generateAtoN(vessel, mmsi, delta) { + var position = findDeltaValue(delta, 'navigation.position') + + if ( position && position.latitude && position.longitude ) { + var name = _.get(vessel, "name") || findDeltaValue(delta, 'name'); + var type = _.get(findDeltaValue(delta, "atonType"), "id") + var length = _.get(findDeltaValue(delta, 'design.length'), 'overall') + var beam = findDeltaValue(delta, 'design.beam') + var fromCenter = findDeltaValue(delta, 'sensors.ais.fromCenter') + var fromBow = findDeltaValue(delta, 'sensors.ais.fromBow') + var latitude = position.latitude * 10000000; + var longitude = position.longitude * 10000000; + + type = _.isUndefined(type) ? 0 : type + name = fillASCII(name ? name : '0', 20) + length = length ? length * 10 : 0xffff; + beam = beam ? beam * 10 : 0xffff; + + var fromStarboard = 0xffff + if ( beam && fromCenter ) { + fromStarboard = (beam / 2 + fromCenter) * 10 + } + fromBow = fromBow ? fromBow * 10 : 0xffff + + /* + 2017-04-15T15:15:08.461Z,4,129041,43,255,49,15, + + 77,3c,3a,3b, + 0d,bf,62,d2, + b3,5e,60,17, + f5, + ff,ff, + ff,ff, + ff,ff, + ff,ff, /from True north egde + 4e, + 0e, + 00, + 01, + 17,01, + 4e,57, + 20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,40 + */ + + mmsi = parseInt(mmsi, 10) + var data = Concentrate2() + .uint8(0x15) + .uint32(mmsi) + .int32(longitude) + .int32(latitude) + .uint8(0xf5) + .uint16(length) + .uint16(beam) + .uint16(fromStarboard) + .uint16(fromBow) + .tinyInt(type,5) + .tinyInt(0, 1) + .tinyInt(0, 1) + .tinyInt(0, 1) + .uint8(0x0e) + .uint8(0x00) + .uint8(0x01) + .uint8(0x17) + .uint8(0x01) + .buffer(name) + .uint8(0x40) + .result() + return { pgn: aton_pgn, buffer: data } + } else { + return null + } + +} + function int8buff(array) { return new Buffer(new Uint8Array(array).buffer) } @@ -234,6 +320,7 @@ function fillASCII(theString, len) } function isN2K(delta) { + return false var res = false if ( delta.updates ) { delta.updates.forEach(update => { diff --git a/conversions/battery.js b/conversions/battery.js index 497a39c..b2a31a4 100644 --- a/conversions/battery.js +++ b/conversions/battery.js @@ -1,4 +1,4 @@ -const Concentrate = require("concentrate"); +const Concentrate2 = require("concentrate2"); const _ = require('lodash') module.exports = (app, plugin, options) => { @@ -80,7 +80,7 @@ module.exports = (app, plugin, options) => { current = _.isUndefined(current) ? 0x7fff : current * 10 res.push({ pgn: 127508, - buffer: Concentrate() + buffer: Concentrate2() .uint8(instance) .uint16(voltage) .uint16(current) @@ -100,7 +100,7 @@ module.exports = (app, plugin, options) => { ripple = _.isUndefined(ripple) ? 0xffff : ripple * 100 res.push({ pgn: 127506, - buffer: Concentrate() + buffer: Concentrate2() .uint8(0xff) .uint8(instance) .uint8(0x00) diff --git a/conversions/cogSOG.js b/conversions/cogSOG.js index 5ae02db..49be3ba 100644 --- a/conversions/cogSOG.js +++ b/conversions/cogSOG.js @@ -1,5 +1,5 @@ const _ = require('lodash') -const Concentrate = require("concentrate"); +const Concentrate2 = require("concentrate2"); const debug = require("debug")("signalk:signalk-to-nmea2000"); module.exports = (app, plugin) => { @@ -15,7 +15,7 @@ module.exports = (app, plugin) => { return [ { pgn: 129026, - buffer: Concentrate() + buffer: Concentrate2() .uint8(0xff) .uint8(0xfc) .uint16((course * 10000).toFixed(0)) diff --git a/conversions/depth.js b/conversions/depth.js index ae2bc09..74356e3 100644 --- a/conversions/depth.js +++ b/conversions/depth.js @@ -1,4 +1,4 @@ -const Concentrate = require("concentrate"); +const Concentrate2 = require("concentrate2"); const _ = require('lodash') module.exports = (app, plugin) => { @@ -17,7 +17,7 @@ module.exports = (app, plugin) => { return [ { pgn: 128267, - buffer: Concentrate() + buffer: Concentrate2() .uint8(0xff) .uint32(belowTransducer * 100) .uint16(offset * 1000) diff --git a/conversions/gps.js b/conversions/gps.js index 9aa0e1a..c2ba655 100644 --- a/conversions/gps.js +++ b/conversions/gps.js @@ -1,8 +1,6 @@ const _ = require('lodash') -const Concentrate = require("concentrate"); +const Concentrate2 = require("concentrate2"); const debug = require("debug")("signalk:signalk-to-nmea2000"); -//const bignum = require('bignum') -const Int64LE = require('int64-buffer').Int64LE module.exports = (app, plugin) => { var lastUpdate = null @@ -24,7 +22,7 @@ module.exports = (app, plugin) => { var res = [ { pgn: 129025, - buffer: Concentrate() + buffer: Concentrate2() .int32(position.latitude * 10000000) .int32(position.longitude * 10000000) .result() @@ -40,17 +38,19 @@ module.exports = (app, plugin) => { var lat = (position.latitude * 10000000 * 10000000 * 100) var lon = (position.longitude * 10000000 * 10000000 * 100) + //console.log(`${lat} ${lon}`) var rest = new Buffer(new Uint8Array([0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x23,0xfc,0x10,0x40,0x00,0xff,0x7f,0xff,0xff,0xff,0x7f,0x00])) res = res.concat([ { pgn: 129029, - buffer: Concentrate() + buffer: Concentrate2() .uint8(0xf3) .uint16(date) .uint32(time) - .buffer(new Int64LE(lat).toBuffer()) - .buffer(new Int64LE(lon).toBuffer()) + + .uint64(lat) + .uint64(lon) .buffer(rest) .result() } diff --git a/conversions/systemTime.js b/conversions/systemTime.js index 66a181c..ab8abc5 100644 --- a/conversions/systemTime.js +++ b/conversions/systemTime.js @@ -1,4 +1,4 @@ -const Concentrate = require("concentrate"); +const Concentrate2 = require("concentrate2"); module.exports = (app, plugin) => { return { @@ -18,7 +18,7 @@ module.exports = (app, plugin) => { return [ { pgn: 126992, - buffer: Concentrate() + buffer: Concentrate2() .uint8(0xff) .uint8(0xff) .uint16(date) diff --git a/conversions/wind.js b/conversions/wind.js index 7c0d116..5541200 100644 --- a/conversions/wind.js +++ b/conversions/wind.js @@ -1,4 +1,4 @@ -const Concentrate = require("concentrate"); +const Concentrate2 = require("concentrate2"); module.exports = (app, plugin) => { return { @@ -11,7 +11,7 @@ module.exports = (app, plugin) => { return [ { pgn: 130306, - buffer: Concentrate() + buffer: Concentrate2() .uint8(0xff) .uint16((speed*100).toFixed(0)) .uint16((angle * 10000).toFixed(0)) diff --git a/package.json b/package.json index 4174a1d..220ef5e 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "debug": "^3.1.0", "lodash": "^4.17.4", "to-n2k": "^1.0.0", - "concentrate": "^0.2.3" + "concentrate2": "^0.0.1" }, "repository": { "type": "git", From 410c40e8c4a9f3239243f38d7949d219fed8c111 Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Sun, 21 Jan 2018 17:20:45 -0500 Subject: [PATCH 10/24] update: most PGNs now use toPgn --- conversions/battery.js | 32 ++++++++++----------------- conversions/cogSOG.js | 11 +++------- conversions/depth.js | 9 +++----- conversions/gps.js | 46 ++++++++++++++------------------------- conversions/heading.js | 5 ++--- conversions/systemTime.js | 9 ++------ conversions/wind.js | 7 +++++- index.js | 45 ++++++++++++++++++++++++++------------ 8 files changed, 74 insertions(+), 90 deletions(-) diff --git a/conversions/battery.js b/conversions/battery.js index b2a31a4..5b8ff92 100644 --- a/conversions/battery.js +++ b/conversions/battery.js @@ -75,18 +75,13 @@ module.exports = (app, plugin, options) => { if ( !_.isUndefined(voltage) || !_.isUndefined(current) || !_.isUndefined(temperature) ) { - voltage = _.isUndefined(voltage) ? 0x7fff : voltage * 100 - temperature = _.isUndefined(temperature) ? 0xffff : temperature * 100 - current = _.isUndefined(current) ? 0x7fff : current * 10 + res.push({ pgn: 127508, - buffer: Concentrate2() - .uint8(instance) - .uint16(voltage) - .uint16(current) - .uint16(temperature) - .uint8(0xff) - .result() + "Battery Instance": instance, + Voltage: voltage, + Current: current, + Temperature: temperature }) } @@ -95,20 +90,15 @@ module.exports = (app, plugin, options) => { || !_.isUndefined(stateOfHealth) || !_.isUndefined(ripple) ) { stateOfCharge = _.isUndefined(stateOfCharge) ? 0xff : stateOfCharge*100 - timeRemaining = _.isUndefined(timeRemaining) ? 0xffff : timeRemaining stateOfHealth = _.isUndefined(stateOfHealth) ? 0xff : stateOfHealth*100 - ripple = _.isUndefined(ripple) ? 0xffff : ripple * 100 + res.push({ pgn: 127506, - buffer: Concentrate2() - .uint8(0xff) - .uint8(instance) - .uint8(0x00) - .uint8(stateOfCharge) - .uint8(stateOfHealth) - .uint16(timeRemaining) - .uint16(ripple) - .result() + "DC Instance": instance, + 'State of Charge': stateOfCharge, + 'State of Health': stateOfHealth, + 'Time Remaining': timeRemaining, + 'Ripple Voltage': ripple }) } } diff --git a/conversions/cogSOG.js b/conversions/cogSOG.js index 49be3ba..395c05a 100644 --- a/conversions/cogSOG.js +++ b/conversions/cogSOG.js @@ -7,7 +7,6 @@ module.exports = (app, plugin) => { return { title: 'COG & SOG (129026)', - type: 'toN2K', optionKey: 'COG_SOG', keys: ["navigation.courseOverGroundTrue", "navigation.speedOverGround"], callback: (course, speed) => { @@ -15,13 +14,9 @@ module.exports = (app, plugin) => { return [ { pgn: 129026, - buffer: Concentrate2() - .uint8(0xff) - .uint8(0xfc) - .uint16((course * 10000).toFixed(0)) - .uint16((speed*100).toFixed(0)) - .uint16(0xffff) - .result() + 'COG Reference': 0, + COG: course, + SOG: speed } ] } catch ( err ) { diff --git a/conversions/depth.js b/conversions/depth.js index 74356e3..e054541 100644 --- a/conversions/depth.js +++ b/conversions/depth.js @@ -4,7 +4,6 @@ const _ = require('lodash') module.exports = (app, plugin) => { return { title: 'Depth (128267)', - type: 'toN2K', optionKey: 'DEPTH', keys: ["environment.depth.belowTransducer"], callback: (belowTransducer) => { @@ -17,11 +16,9 @@ module.exports = (app, plugin) => { return [ { pgn: 128267, - buffer: Concentrate2() - .uint8(0xff) - .uint32(belowTransducer * 100) - .uint16(offset * 1000) - .result() + SID: 0xff, + Depth: belowTransducer, + Offset: offset } ] } catch ( err ) { diff --git a/conversions/gps.js b/conversions/gps.js index c2ba655..09503bd 100644 --- a/conversions/gps.js +++ b/conversions/gps.js @@ -7,25 +7,16 @@ module.exports = (app, plugin) => { return { title: 'Location (129025)', - type: 'toN2K', + type: 'toPgn', optionKey: 'GPS_LOCATION', keys: ["navigation.position"], callback: (position) => { //debug(`position: ${JSON.stringify(position)}`) - var dateObj = new Date(); - var date = Math.trunc(dateObj.getTime() / 86400 / 1000); - var time = - dateObj.getUTCHours() * (60 * 60) + - dateObj.getUTCMinutes() * 60 + - dateObj.getUTCSeconds(); - time = time * 10000; var res = [ { pgn: 129025, - buffer: Concentrate2() - .int32(position.latitude * 10000000) - .int32(position.longitude * 10000000) - .result() + Latitude: position.latitude, + Longitude: position.longitude } ] @@ -35,26 +26,21 @@ module.exports = (app, plugin) => { (new Date().getTime() - lastUpdate.getTime()) > 1000 ) { lastUpdate = new Date() - var lat = (position.latitude * 10000000 * 10000000 * 100) - var lon = (position.longitude * 10000000 * 10000000 * 100) - //console.log(`${lat} ${lon}`) - var rest = new Buffer(new Uint8Array([0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x23,0xfc,0x10,0x40,0x00,0xff,0x7f,0xff,0xff,0xff,0x7f,0x00])) + var dateObj = new Date(); + var date = Math.trunc(dateObj.getTime() / 86400 / 1000); + var time = + dateObj.getUTCHours() * (60 * 60) + + dateObj.getUTCMinutes() * 60 + + dateObj.getUTCSeconds(); - res = res.concat([ - { - pgn: 129029, - buffer: Concentrate2() - .uint8(0xf3) - .uint16(date) - .uint32(time) - - .uint64(lat) - .uint64(lon) - .buffer(rest) - .result() - } - ]) + res.push({ + pgn: 129029, + Date: date, + Time: time, + Latitude: position.latitude, + Longitude: position.longitude + }) } return res } diff --git a/conversions/heading.js b/conversions/heading.js index fc7f2d8..c627283 100644 --- a/conversions/heading.js +++ b/conversions/heading.js @@ -3,20 +3,19 @@ module.exports = (app, plugin) => { return { pgn: 127250, title: 'Heading (127250)', - type: 'toPgn', optionKey: 'HEADING', keys: [ "navigation.headingMagnetic" // ,'navigation.magneticVariation' ], callback: (heading, variation) => { - return { + return [{ pgn: 127250, SID: 87, Heading: heading, // "Variation": variation, Reference: "Magnetic" - } + }] } } } diff --git a/conversions/systemTime.js b/conversions/systemTime.js index ab8abc5..4d01290 100644 --- a/conversions/systemTime.js +++ b/conversions/systemTime.js @@ -13,17 +13,12 @@ module.exports = (app, plugin) => { dateObj.getUTCHours() * (60 * 60) + dateObj.getUTCMinutes() * 60 + dateObj.getUTCSeconds(); - time = time * 10000; return [ { pgn: 126992, - buffer: Concentrate2() - .uint8(0xff) - .uint8(0xff) - .uint16(date) - .uint32(time) - .result() + Date: date, + Time: time } ] } diff --git a/conversions/wind.js b/conversions/wind.js index 5541200..6e877d5 100644 --- a/conversions/wind.js +++ b/conversions/wind.js @@ -3,14 +3,18 @@ const Concentrate2 = require("concentrate2"); module.exports = (app, plugin) => { return { title: 'Wind (130306)', - type: 'toN2K', optionKey: 'WIND', keys: ["environment.wind.angleApparent", "environment.wind.speedApparent"], callback: (angle, speed) => { + console.log(`${angle} ${speed}`) try { return [ { pgn: 130306, + 'Wind Speed': speed, + 'Wind Angle': angle < 0 ? angle + Math.PI*2 : angle, + 'Reference': 2 + /* buffer: Concentrate2() .uint8(0xff) .uint16((speed*100).toFixed(0)) @@ -19,6 +23,7 @@ module.exports = (app, plugin) => { .uint8(0xff) .uint8(0xff) .result() + */ } ] } catch ( err ) { diff --git a/index.js b/index.js index 684d470..265fee2 100644 --- a/index.js +++ b/index.js @@ -69,9 +69,10 @@ module.exports = function(app) { conversions.forEach(conversion => { if ( options[conversion.optionKey] && options[conversion.optionKey].enabled ) { debug(`${conversion.title} is enabled`) - var mapper = types[conversion.type] + var type = _.isUndefined(conversion.type) ? 'toPgn' : conversion.type + var mapper = types[type] if ( _.isUndefined(mapper) ) { - console.error(`Unknown conversion type: ${conversion.type}`) + console.error(`Unknown conversion type: ${type}`) } else { mapper(conversion, options) } @@ -119,9 +120,30 @@ module.exports = function(app) { function processPGNs(pgns) { if ( pgns ) { pgns.filter(pgn => pgn != null).forEach(pgn => { - const msg = toActisenseSerialFormat(pgn.pgn, pgn.buffer); - debug("emit " + msg); - app.emit("nmea2000out", msg); + try { + const msg = toActisenseSerialFormat(pgn.pgn, pgn.buffer); + debug("emit " + msg); + app.emit("nmea2000out", msg); + } catch ( err ) { + console.error(`error writing pgn ${JSON.stringify(pgn)}`) + console.error(err.stack) + } + }) + } + } + + function processToPGNs(pgns) { + if ( pgns ) { + pgns.filter(pgn => pgn != null).forEach(pgn => { + try { + const msg = toActisenseSerialFormat(pgn.pgn, toPgn(pgn)); + debug("emit " + msg); + app.emit("nmea2000out", msg); + } + catch ( err ) { + console.error(`error writing pgn ${JSON.stringify(pgn)}`) + console.error(err.stack) + } }) } } @@ -134,13 +156,8 @@ module.exports = function(app) { ) .changes() .debounceImmediate(20) - .map(toPgn) - .onValue(pgnBuffer => { - if (pgnBuffer) { - const msg = toActisenseSerialFormat(conversion.pgn, pgnBuffer); - debug("emit " + msg); - app.emit("nmea2000out", msg); - } + .onValue(pgns => { + processToPGNs(pgns) }) ); } @@ -171,7 +188,7 @@ module.exports = function(app) { function mapTimer(conversion) { timers.push(setInterval(() => { - processPGNs(conversion.callback(app)) + processToPGNs(conversion.callback(app)) }, conversion.interval)); } @@ -199,7 +216,7 @@ module.exports = function(app) { subscription_error, delta => { try { - processPGNs(mapping.callback(delta, options)) + processToPGNs(mapping.callback(delta, options)) } catch ( err ) { console.log(err) } From 77aa65cf0296887b436de2ea6f97a71563e8f0db Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Sun, 21 Jan 2018 17:51:57 -0500 Subject: [PATCH 11/24] refactor: the convertion type stuff and add comment to document --- conversions/ais.js | 3 +- conversions/battery.js | 2 +- conversions/gps.js | 1 - conversions/systemTime.js | 2 +- index.js | 69 ++++++++++++++++++++++++--------------- 5 files changed, 47 insertions(+), 30 deletions(-) diff --git a/conversions/ais.js b/conversions/ais.js index 726c0b3..1eaf9e7 100644 --- a/conversions/ais.js +++ b/conversions/ais.js @@ -23,7 +23,8 @@ const aton_pgn = 129041 module.exports = (app, plugin) => { return { title: `AIS (${static_pgn}, ${position_pgn}, ${aton_pgn})`, - type: 'onDelta', + sourceType: 'onDelta', + outputType: 'buffer', optionKey: 'AIS', callback: (delta) => { var selfContext = 'vessels.' + app.selfId diff --git a/conversions/battery.js b/conversions/battery.js index 5b8ff92..f0d0966 100644 --- a/conversions/battery.js +++ b/conversions/battery.js @@ -24,7 +24,7 @@ module.exports = (app, plugin, options) => { return { title: 'Battery (127506 & 127508)', - type: 'toSubscription', + sourceType: 'subscription', optionKey: 'BATTERY', context: 'vessels.self', keys: (options) => { diff --git a/conversions/gps.js b/conversions/gps.js index 09503bd..d450c38 100644 --- a/conversions/gps.js +++ b/conversions/gps.js @@ -7,7 +7,6 @@ module.exports = (app, plugin) => { return { title: 'Location (129025)', - type: 'toPgn', optionKey: 'GPS_LOCATION', keys: ["navigation.position"], callback: (position) => { diff --git a/conversions/systemTime.js b/conversions/systemTime.js index 4d01290..ec50bff 100644 --- a/conversions/systemTime.js +++ b/conversions/systemTime.js @@ -3,7 +3,7 @@ const Concentrate2 = require("concentrate2"); module.exports = (app, plugin) => { return { title: 'System Time (126992)', - type: 'timer', + sourceType: 'timer', interval: 1000, optionKey: 'SYSTEM_TIME', callback: (app) => { diff --git a/index.js b/index.js index 265fee2..1499f0e 100644 --- a/index.js +++ b/index.js @@ -13,14 +13,38 @@ module.exports = function(app) { var conversions = load_conversions(app, plugin) conversions = [].concat.apply([], conversions) - var types = { + /* + Each conversion can specify the sourceType and outputType. + + Source type can be: + + onDelta - You will get all deltas via app.signalk.on('delta', ...). Please do no use this unless absolutely necessary. + + bacanjs - The conversion should specify a variable called 'keys' which is an array of the Signal K paths that the convesion needs + + timer - The conversions callback will get called per the givien 'interval' variable + + Output type can be: + + 'to-n2k' - The outut will be sent through the to-n2k package (https://github.com/tkurki/to-n2k) + + 'buffer' - The output should be a buffer that is sent directly to nmea2000out + + sourceType defaults to 'bacanjs' + outputType defaults to 'to-n2k' + */ + + var sourceTypes = { 'onDelta': mapOnDelta, - 'toPgn': mapToPgn, - 'toN2K': mapToNmea, - 'toSubscription': mapSubscription, + 'baconjs': mapBaconjs, 'timer': mapTimer } + var outputTypes = { + 'to-n2k': processToN2K, + 'buffer': processBufferOutput + } + plugin.id = "sk-to-nmea2000"; plugin.name = "Convert Signal K to NMEA2000"; plugin.description = "Plugin to convert Signal K to NMEA2000"; @@ -69,11 +93,14 @@ module.exports = function(app) { conversions.forEach(conversion => { if ( options[conversion.optionKey] && options[conversion.optionKey].enabled ) { debug(`${conversion.title} is enabled`) - var type = _.isUndefined(conversion.type) ? 'toPgn' : conversion.type - var mapper = types[type] + var type = _.isUndefined(conversion.sourceType) ? 'baconjs' : conversion.sourceType + var mapper = sourceTypes[type] if ( _.isUndefined(mapper) ) { console.error(`Unknown conversion type: ${type}`) } else { + if ( _.isUndefined(conversion.outputType) ) { + conversion.outputType = 'to-n2k' + } mapper(conversion, options) } } @@ -117,7 +144,7 @@ module.exports = function(app) { }).filter(converter => { return typeof converter !== 'undefined'; }); } - function processPGNs(pgns) { + function processBufferOutput(pgns) { if ( pgns ) { pgns.filter(pgn => pgn != null).forEach(pgn => { try { @@ -132,7 +159,7 @@ module.exports = function(app) { } } - function processToPGNs(pgns) { + function processToN2K(pgns) { if ( pgns ) { pgns.filter(pgn => pgn != null).forEach(pgn => { try { @@ -148,7 +175,11 @@ module.exports = function(app) { } } - function mapToPgn(conversion) { + function processOutput(conversion, output) { + outputTypes[conversion.outputType](output) + } + + function mapBaconjs(conversion) { unsubscribes.push( Bacon.combineWith( conversion.callback, @@ -157,29 +188,15 @@ module.exports = function(app) { .changes() .debounceImmediate(20) .onValue(pgns => { - processToPGNs(pgns) + processOutput(conversion, pgns) }) ); } - function mapToNmea(conversion, options) { - var keys = _.isFunction(conversion.keys) ? conversion.keys(options) : conversion.keys - const selfStreams = keys.map( - app.streambundle.getSelfStream, - app.streambundle - ); - unsubscribes.push( - Bacon.combineWith(conversion.callback, selfStreams) - .changes() - .debounceImmediate(20) - .onValue(pgns => processPGNs(pgns)) - ) - } - function mapOnDelta(conversion) { app.signalk.on('delta', (delta) => { try { - processPGNs(conversion.callback(delta)) + processOutput(conversion, conversion.callback(delta)) } catch ( err ) { console.log(err) } @@ -188,7 +205,7 @@ module.exports = function(app) { function mapTimer(conversion) { timers.push(setInterval(() => { - processToPGNs(conversion.callback(app)) + processOutput(conversion, conversion.callback(app)) }, conversion.interval)); } From d1a1e9ede017ee247724074711f5eacf08b632ed Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Sun, 21 Jan 2018 17:56:04 -0500 Subject: [PATCH 12/24] feature: allow a convertion to return an array of oncversions --- index.js | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/index.js b/index.js index 1499f0e..078b462 100644 --- a/index.js +++ b/index.js @@ -91,19 +91,24 @@ module.exports = function(app) { debug("start"); conversions.forEach(conversion => { - if ( options[conversion.optionKey] && options[conversion.optionKey].enabled ) { - debug(`${conversion.title} is enabled`) - var type = _.isUndefined(conversion.sourceType) ? 'baconjs' : conversion.sourceType - var mapper = sourceTypes[type] - if ( _.isUndefined(mapper) ) { - console.error(`Unknown conversion type: ${type}`) - } else { - if ( _.isUndefined(conversion.outputType) ) { - conversion.outputType = 'to-n2k' + if ( !_.isArray(conversion) ) { + conversion = [ conversion ] + } + conversion.forEach(conversion => { + if ( options[conversion.optionKey] && options[conversion.optionKey].enabled ) { + debug(`${conversion.title} is enabled`) + var type = _.isUndefined(conversion.sourceType) ? 'baconjs' : conversion.sourceType + var mapper = sourceTypes[type] + if ( _.isUndefined(mapper) ) { + console.error(`Unknown conversion type: ${type}`) + } else { + if ( _.isUndefined(conversion.outputType) ) { + conversion.outputType = 'to-n2k' + } + mapper(conversion, options) } - mapper(conversion, options) } - } + }) }) }; From 0819361d2b7a0ab396dd6037b42c19154a96bbfd Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Sun, 21 Jan 2018 20:05:21 -0500 Subject: [PATCH 13/24] feature: allow a conversion to return a 'sub' list of conversions --- conversions/battery.js | 132 ++++++++++++++++------------------------- index.js | 81 ++++++++++++++++++++----- package.json | 12 +++- 3 files changed, 128 insertions(+), 97 deletions(-) diff --git a/conversions/battery.js b/conversions/battery.js index f0d0966..33e3054 100644 --- a/conversions/battery.js +++ b/conversions/battery.js @@ -1,16 +1,7 @@ const Concentrate2 = require("concentrate2"); const _ = require('lodash') -module.exports = (app, plugin, options) => { - - function getBatteries() { - if ( _.get(app.signalk.self, "electrical.batteries") ) - { - return _.keys(app.signalk.self.electrical.batteries) - } else { - return undefined - } - } +module.exports = (app, plugin) => { const batteryKeys = [ 'voltage', @@ -24,86 +15,67 @@ module.exports = (app, plugin, options) => { return { title: 'Battery (127506 & 127508)', - sourceType: 'subscription', optionKey: 'BATTERY', context: 'vessels.self', - keys: (options) => { - var res = [] - _.keys(options.BATTERY).forEach(prop => { - if ( prop.startsWith('instance') && options.BATTERY[prop] ) { - var instance = prop.substring(8) - res = res.concat(batteryKeys.map(key => { - return `electrical.batteries.${instance}.${key}` - })) - } - }) - return res - }, - properties: () => { - var props = {} - var batteries = getBatteries() - if ( batteries ) { - batteries.forEach(instance => { - props[`instance${instance}`] = { - title: `Instance ${instance}`, - type: 'boolean', - default:false + properties: { + batteries: { + title: 'Battery Mapping', + type: 'array', + items: { + type: 'object', + properties: { + signalkId: { + title: 'Signal K battery id', + type: 'string' + }, + instanceId: { + title: 'NMEA2000 Battery Instance Id', + type: 'number' + } } - }) + } } - return props }, - callback: (delta, options) => { - var flat = flattenDelta(delta) - var res = [] - _.keys(options.BATTERY).forEach(prop => { - if ( prop.startsWith('instance') && options.BATTERY[prop] ) { - var instance = prop.substring(8) - - function get(key) { - return flat[`electrical.batteries.${instance}.${key}`] - } - - var voltage = get('voltage'), - current = get('current'), - temperature = get('temperature'), - stateOfCharge = get('capacity.stateOfCharge'), - timeRemaining = get('capacity.timeRemaining'), - stateOfHealth = get('capacity.stateOfHealth'), - ripple = get('ripple') - - if ( !_.isUndefined(voltage) - || !_.isUndefined(current) - || !_.isUndefined(temperature) ) { - - res.push({ - pgn: 127508, - "Battery Instance": instance, - Voltage: voltage, - Current: current, - Temperature: temperature - }) - } - - if ( !_.isUndefined(stateOfCharge) - || !_.isUndefined(timeRemaining) - || !_.isUndefined(stateOfHealth) - || !_.isUndefined(ripple) ) { - stateOfCharge = _.isUndefined(stateOfCharge) ? 0xff : stateOfCharge*100 - stateOfHealth = _.isUndefined(stateOfHealth) ? 0xff : stateOfHealth*100 - res.push({ - pgn: 127506, - "DC Instance": instance, - 'State of Charge': stateOfCharge, - 'State of Health': stateOfHealth, + conversions: (options) => { + return options.BATTERY.batteries.map(battery => { + return { + keys: batteryKeys.map(key => `electrical.batteries.${battery.signalkId}.${key}`), + timeouts: batteryKeys.map(key => 60000), + callback: (voltage, current, temperature, stateOfCharge, timeRemaining, stateOfHealth, ripple) => { + var res = [] + if ( voltage != null + || current != null + || temperature != null ) { + res.push({ + pgn: 127508, + "Battery Instance": battery.instanceId, + Voltage: voltage, + Current: current, + Temperature: temperature + }) + } + + if ( stateOfCharge != null + || timeRemaining != null + || stateOfHealth != null + || ripple != null ) { + stateOfCharge = _.isUndefined(stateOfCharge) || stateOfCharge == null ? undefined : stateOfCharge*100 + stateOfHealth = _.isUndefined(stateOfHealth) || stateOfHealth == null ? undefined : stateOfHealth*100 + + res.push({ + pgn: 127506, + "DC Instance": battery.instanceId, + 'State of Charge': stateOfCharge, + 'State of Health': stateOfHealth, 'Time Remaining': timeRemaining, - 'Ripple Voltage': ripple - }) + 'Ripple Voltage': ripple + }) + } + return res } } }) - return res } } } diff --git a/index.js b/index.js index 078b462..a20083f 100644 --- a/index.js +++ b/index.js @@ -97,16 +97,25 @@ module.exports = function(app) { conversion.forEach(conversion => { if ( options[conversion.optionKey] && options[conversion.optionKey].enabled ) { debug(`${conversion.title} is enabled`) - var type = _.isUndefined(conversion.sourceType) ? 'baconjs' : conversion.sourceType - var mapper = sourceTypes[type] - if ( _.isUndefined(mapper) ) { - console.error(`Unknown conversion type: ${type}`) - } else { - if ( _.isUndefined(conversion.outputType) ) { - conversion.outputType = 'to-n2k' - } - mapper(conversion, options) + + var subConversions = conversion.conversions + if ( _.isUndefined(subConversions) ) { + subConversions = [ conversion ] + } else if ( _.isFunction(subConversions) ) { + subConversions = subConversions(options) } + subConversions.forEach(subConversion => { + var type = _.isUndefined(subConversion.sourceType) ? 'baconjs' : subConversion.sourceType + var mapper = sourceTypes[type] + if ( _.isUndefined(mapper) ) { + console.error(`Unknown conversion type: ${type}`) + } else { + if ( _.isUndefined(subConversion.outputType) ) { + subConversion.outputType = 'to-n2k' + } + mapper(subConversion, options) + } + }) } }) }) @@ -186,12 +195,13 @@ module.exports = function(app) { function mapBaconjs(conversion) { unsubscribes.push( - Bacon.combineWith( - conversion.callback, - conversion.keys.map(app.streambundle.getSelfStream, app.streambundle) + timeoutingArrayStream( + conversion.keys, + conversion.timeouts, + app.streambundle, + unsubscribes ) - .changes() - .debounceImmediate(20) + .map(values => conversion.callback.call(this, ...values)) .onValue(pgns => { processOutput(conversion, pgns) }) @@ -246,3 +256,46 @@ module.exports = function(app) { } }; +function timeoutingArrayStream ( + keys, + timeouts = [], + streambundle, + unsubscribes +) { + debug(`keys:${keys}`) + debug(`timeouts:${timeouts}`) + const lastValues = keys.reduce((acc, key) => { + acc[key] = { + timestamp: new Date().getTime(), + value: null + } + return acc + }, {}) + const combinedBus = new Bacon.Bus() + keys.map(skKey => { + streambundle.getSelfStream(skKey).onValue(value => { + lastValues[skKey] = { + timestamp: new Date().getTime(), + value + } + const now = new Date().getTime() + + combinedBus.push( + keys.map((key, i) => { + return notDefined(timeouts[i]) || + lastValues[key].timestamp + timeouts[i] > now + ? lastValues[key].value + : null + }) + ) + }) + }) + const result = combinedBus.debounce(10) + if (debug.enabled) { + unsubscribes.push(result.onValue(x => debug(`${keys}:${x}`))) + } + return result +} + +const notDefined = x => typeof x === 'undefined' +const isDefined = x => typeof x !== 'undefined' diff --git a/package.json b/package.json index 220ef5e..2688ab7 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Signal K server plugin to convert Signal K to NMEA2000", "main": "index.js", "scripts": { - "test": "$NODE $npm_package_main" + "test": "mocha" }, "keywords": [ "signalk-node-server-plugin" @@ -24,10 +24,16 @@ "debug": "^3.1.0", "lodash": "^4.17.4", "to-n2k": "^1.0.0", - "concentrate2": "^0.0.1" + "concentrate2": "^1.0.0" }, "repository": { "type": "git", - "url": "https://github.com/sbender9/signalk-to-nmea2000" + "url": "https://github.com/SignalK/signalk-to-nmea2000" + }, + "devDependencies": { + "chai": "^4.1.2", + "mocha": "^5.0.0", + "signalk-server": "^1.0.0-0", + "sinon": "^4.1.6" } } From 61c83ed6a32759499ab403e6aa4db27b6cf4e161 Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Mon, 22 Jan 2018 20:36:34 -0500 Subject: [PATCH 14/24] delete: unused metho flattenDelta --- conversions/battery.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/conversions/battery.js b/conversions/battery.js index 33e3054..b028085 100644 --- a/conversions/battery.js +++ b/conversions/battery.js @@ -80,13 +80,4 @@ module.exports = (app, plugin) => { } } -function flattenDelta(delta) { - var res = {} - delta.updates.forEach(update => { - update.values.forEach(pathValue => { - res[pathValue.path] = pathValue.value - }) - }) - return res -} From 69a91eb363a6a5ce3f404a569a6a770666bdc8f9 Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Mon, 22 Jan 2018 20:41:53 -0500 Subject: [PATCH 15/24] add: navigation.magneticVariation to heading --- conversions/heading.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/conversions/heading.js b/conversions/heading.js index c627283..c26449e 100644 --- a/conversions/heading.js +++ b/conversions/heading.js @@ -5,15 +5,15 @@ module.exports = (app, plugin) => { title: 'Heading (127250)', optionKey: 'HEADING', keys: [ - "navigation.headingMagnetic" - // ,'navigation.magneticVariation' + "navigation.headingMagnetic", + 'navigation.magneticVariation' ], callback: (heading, variation) => { return [{ pgn: 127250, SID: 87, Heading: heading, - // "Variation": variation, + "Variation": variation, Reference: "Magnetic" }] } From 75832fbb7c33e6d42573e87488f5bcd9e0a22019 Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Thu, 25 Jan 2018 21:35:07 -0500 Subject: [PATCH 16/24] fix: not enough data in 129029 --- conversions/gps.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/conversions/gps.js b/conversions/gps.js index d450c38..db68801 100644 --- a/conversions/gps.js +++ b/conversions/gps.js @@ -6,7 +6,7 @@ module.exports = (app, plugin) => { var lastUpdate = null return { - title: 'Location (129025)', + title: 'Location (129025,129029)', optionKey: 'GPS_LOCATION', keys: ["navigation.position"], callback: (position) => { @@ -38,7 +38,17 @@ module.exports = (app, plugin) => { Date: date, Time: time, Latitude: position.latitude, - Longitude: position.longitude + Longitude: position.longitude, + 'GNSS type': 'GPS+SBAS/WAAS', + Method: 'DGNSS fix', + Integrity: 'No integrity checking', + 'Number of SVs': 16, + HDOP:0.64, + 'Geoidal Separation': -0.01, + 'Reference Stations': 1, + 'Reference Station Type': 'GPS+SBAS/WAAS', + 'Reference Station ID': 7 + //'Age of DGNSS Corrections': }) } return res From b07d177ac53a5dffcb6fd15ad0ce830540b06978 Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Thu, 25 Jan 2018 21:35:38 -0500 Subject: [PATCH 17/24] chore: remove debuging log message --- conversions/wind.js | 1 - 1 file changed, 1 deletion(-) diff --git a/conversions/wind.js b/conversions/wind.js index 6e877d5..175dd61 100644 --- a/conversions/wind.js +++ b/conversions/wind.js @@ -6,7 +6,6 @@ module.exports = (app, plugin) => { optionKey: 'WIND', keys: ["environment.wind.angleApparent", "environment.wind.speedApparent"], callback: (angle, speed) => { - console.log(`${angle} ${speed}`) try { return [ { From 7610ea20de1b705fde0aef333ca9f1e30beae625 Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Thu, 25 Jan 2018 21:49:40 -0500 Subject: [PATCH 18/24] change: use canboatjs for the toPgn conversion --- index.js | 30 +- package-lock.json | 3348 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 6 +- 3 files changed, 3358 insertions(+), 26 deletions(-) create mode 100644 package-lock.json diff --git a/index.js b/index.js index a20083f..76665e1 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,7 @@ const Bacon = require("baconjs"); const debug = require("debug")("signalk:signalk-to-nmea2000"); const util = require("util"); -const toPgn = require("to-n2k").toPgn; +const { toPgn, toActisenseSerialFormat } = require("canboatjs"); const _ = require('lodash') const path = require('path') const fs = require('fs') @@ -130,25 +130,6 @@ module.exports = function(app) { return plugin; - function toActisenseSerialFormat(pgn, data, dst) { - dst = _.isUndefined(dst) ? '255' : dst - return ( - new Date().toISOString() + - ",2," + - pgn + - `,0,${dst},` + - data.length + - "," + - new Uint32Array(data) - .reduce(function(acc, i) { - acc.push(i.toString(16)); - return acc; - }, []) - .map(x => (x.length === 1 ? "0" + x : x)) - .join(",") - ); - } - function load_conversions (app, plugin) { fpath = path.join(__dirname, 'conversions') files = fs.readdirSync(fpath) @@ -177,9 +158,12 @@ module.exports = function(app) { if ( pgns ) { pgns.filter(pgn => pgn != null).forEach(pgn => { try { - const msg = toActisenseSerialFormat(pgn.pgn, toPgn(pgn)); - debug("emit " + msg); - app.emit("nmea2000out", msg); + const data = toPgn(pgn) + if ( !_.isUndefined(data) ) { + const msg = toActisenseSerialFormat(pgn.pgn, data); + debug("emit " + msg); + app.emit("nmea2000out", msg); + } } catch ( err ) { console.error(`error writing pgn ${JSON.stringify(pgn)}`) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..ca2ad60 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3348 @@ +{ + "name": "signalk-to-nmea2000", + "version": "1.6.8", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@signalk/aisreporter": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@signalk/aisreporter/-/aisreporter-0.0.3.tgz", + "integrity": "sha1-SLhaXEvhZwuOIRJ2BRuxCiqF8mo=", + "dev": true, + "requires": { + "baconjs": "0.7.95", + "debug": "2.6.9", + "ggencoder": "0.1.20", + "lodash": "4.17.4" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "@signalk/client": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@signalk/client/-/client-0.2.1.tgz", + "integrity": "sha512-NOwD9PCIikVRedwMupx1ZZgrmCxuC1G676V12j17XXNKDQUuHDhQGrHXPkJ1shHP7OsdMNhaorDJHYY58S3IPQ==", + "dev": true, + "requires": { + "bluebird": "3.5.1", + "debug": "3.1.0", + "eventemitter3": "3.0.0", + "lodash": "4.17.4", + "superagent": "3.8.2", + "superagent-promise": "1.1.0", + "url": "0.11.0", + "ws": "3.3.3" + } + }, + "@signalk/freeboard-sk": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@signalk/freeboard-sk/-/freeboard-sk-0.0.1.tgz", + "integrity": "sha1-sZUbIzVkaZ7nFLYLgcnW5ysk4BE=", + "dev": true + }, + "@signalk/instrumentpanel": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@signalk/instrumentpanel/-/instrumentpanel-0.2.5.tgz", + "integrity": "sha512-UpLW/+MlM4TNpc95Y1Ut6Qw6qOfLyoYNc0CbGV/20tplpk/1lOuAWMqxQ8Wo+r8hy07ga0dbgrezm6W8hz4XDA==", + "dev": true + }, + "@signalk/maptracker": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@signalk/maptracker/-/maptracker-1.0.0.tgz", + "integrity": "sha512-F6V/UpN/tQMYiICuE7x78tqLeKU44OYGVV/iW28nDRqTvsAxUTzWlmqShQrqzB00YNIocMC+KhfS8x96U6JRxw==", + "dev": true, + "requires": { + "@signalk/client": "0.1.4", + "d3": "3.5.17", + "jquery": "2.2.4", + "js-quantities": "1.7.0", + "leaflet": "0.7.7" + }, + "dependencies": { + "@signalk/client": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@signalk/client/-/client-0.1.4.tgz", + "integrity": "sha1-kzK2P7TLxUyGpXWGcvh9hxTvd4c=", + "dev": true, + "requires": { + "bluebird": "3.5.1", + "debug": "3.1.0", + "eventemitter3": "2.0.3", + "lodash": "4.17.4", + "superagent": "3.8.2", + "superagent-promise": "1.1.0", + "url": "0.11.0", + "ws": "3.3.3" + } + }, + "eventemitter3": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz", + "integrity": "sha1-teEHm1n7XhuidxwKmTvgYKWMmbo=", + "dev": true + } + } + }, + "@signalk/n2k-signalk": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@signalk/n2k-signalk/-/n2k-signalk-1.0.1.tgz", + "integrity": "sha512-moqcv/pQCJnnt/q28Vpk3SnCMlsCP10sUW3zbufnBpG/drjCCAegxWlj8UiAUppULI5eT5bBX3EN+SHeArl1pg==", + "dev": true, + "requires": { + "JSONStream": "1.3.2", + "debug": "3.1.0", + "through": "2.3.8" + } + }, + "@signalk/nmea0183-signalk": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@signalk/nmea0183-signalk/-/nmea0183-signalk-2.1.2.tgz", + "integrity": "sha512-7njiRM6vDsg6Nz/DjfJ5385cmOJPD/dBnG5dzkRyp4HFvsWfZicmb2x0d5J+s5/ZEEuF2ybIt8og+XUupyM6aQ==", + "dev": true, + "requires": { + "@signalk/nmea0183-utilities": "0.4.0", + "@signalk/signalk-schema": "1.0.2", + "ggencoder": "0.1.20", + "moment": "2.20.1", + "moment-timezone": "0.5.14", + "split": "1.0.1" + }, + "dependencies": { + "@signalk/signalk-schema": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@signalk/signalk-schema/-/signalk-schema-1.0.2.tgz", + "integrity": "sha512-qU9T/qZfO/lW2+4kZbm0UCcKmrWv6dZMYp1XtFl1WSxrqHwPO5ctKJFhKFe4f2S2MpaUiXYF9MSTQtMHf6mNKQ==", + "dev": true, + "requires": { + "JSONStream": "0.7.4", + "debug": "2.6.9", + "json-schema-ref-parser": "3.3.1", + "lodash": "3.10.1", + "tv4": "1.3.0", + "tv4-formats": "2.2.2" + } + }, + "JSONStream": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.7.4.tgz", + "integrity": "sha1-c0KQ5BUR7qfCz+FR+/mlY6l7l4Y=", + "dev": true, + "requires": { + "jsonparse": "0.0.5", + "through": "2.3.8" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "jsonparse": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz", + "integrity": "sha1-MwVCrT8KZUZlt3jz6y2an6UHrGQ=", + "dev": true + }, + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", + "dev": true + } + } + }, + "@signalk/nmea0183-utilities": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@signalk/nmea0183-utilities/-/nmea0183-utilities-0.4.0.tgz", + "integrity": "sha512-QVw0M3R2KLgBpxA0jMNDepkbZWvX0A35QWzulKMgkPx2Dn4LBd9DRTljwUHKIp+u3U1Dvtwf9pFHLdk48zQROA==", + "dev": true + }, + "@signalk/playground": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@signalk/playground/-/playground-1.0.0.tgz", + "integrity": "sha512-SrB3QFrIDRa4hPCBPvKpalQzjOfHbw1XUi7U05FCCqphktv37R1dU6rSYB/jXYIp836W36z3W18M5/pMer4bRg==", + "dev": true + }, + "@signalk/sailgauge": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@signalk/sailgauge/-/sailgauge-1.1.0.tgz", + "integrity": "sha1-atdr+tkj3kH/nHDYZuFOtTvzjxc=", + "dev": true + }, + "@signalk/set-system-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@signalk/set-system-time/-/set-system-time-1.0.0.tgz", + "integrity": "sha512-nwz9L9PvLfwythcRf1qiFBBTiNvwlziXa9OkfC577cdbmc7uTPVaQWqc7cgamCTAzcWxdwcnBsMulhk3ZDJL0g==", + "dev": true + }, + "@signalk/signalk-schema": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@signalk/signalk-schema/-/signalk-schema-1.0.3.tgz", + "integrity": "sha512-tlTQk4bFcHENOnGbyDpaZ/LlttgK3BF5IGAH8jIjnomkcguw63vpbmaLyiqCaFqMWfq8a67Hed5p0Apmwr99yg==", + "dev": true, + "requires": { + "JSONStream": "0.7.4", + "debug": "2.6.9", + "json-schema-ref-parser": "3.3.1", + "lodash": "3.10.1", + "tv4": "1.3.0", + "tv4-formats": "2.2.2" + }, + "dependencies": { + "JSONStream": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.7.4.tgz", + "integrity": "sha1-c0KQ5BUR7qfCz+FR+/mlY6l7l4Y=", + "dev": true, + "requires": { + "jsonparse": "0.0.5", + "through": "2.3.8" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "jsonparse": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz", + "integrity": "sha1-MwVCrT8KZUZlt3jz6y2an6UHrGQ=", + "dev": true + }, + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", + "dev": true + } + } + }, + "@signalk/signalk-to-nmea0183": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@signalk/signalk-to-nmea0183/-/signalk-to-nmea0183-0.1.1.tgz", + "integrity": "sha1-4NM/F2eQIAZAYeoZ3PcHB2O2CTc=", + "dev": true, + "requires": { + "baconjs": "0.7.95", + "mocha": "3.5.3" + }, + "dependencies": { + "commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true, + "requires": { + "graceful-readlink": "1.0.1" + } + }, + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "diff": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", + "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", + "dev": true + }, + "glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "dev": true + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "mocha": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz", + "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==", + "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.9.0", + "debug": "2.6.8", + "diff": "3.2.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.1", + "growl": "1.9.2", + "he": "1.1.1", + "json3": "3.3.2", + "lodash.create": "3.1.1", + "mkdirp": "0.5.1", + "supports-color": "3.1.2" + } + }, + "supports-color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", + "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "dev": true, + "requires": { + "has-flag": "1.0.0" + } + } + } + }, + "@signalk/simplegauges": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@signalk/simplegauges/-/simplegauges-1.0.1.tgz", + "integrity": "sha1-U3QBxoUf5iJBJzFbJXL7MwkV73s=", + "dev": true + }, + "@signalk/sk-simple-token-security": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@signalk/sk-simple-token-security/-/sk-simple-token-security-2.2.2.tgz", + "integrity": "sha1-36KHKMazzbtVFT4s4eB20ZpqMdg=", + "dev": true, + "requires": { + "bcrypt": "1.0.3", + "body-parser": "1.18.2", + "cookie-parser": "1.4.3", + "debug": "3.1.0", + "lodash": "4.17.4", + "passport-jwt": "3.0.1" + } + }, + "@signalk/zones": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@signalk/zones/-/zones-1.0.0.tgz", + "integrity": "sha512-QIgy8w9veDnyG/AghCFUS3sQkdGKGkxje0RDW4Zvdh6hTOSgTmgEXFG4x+hFCh4bmOkXUrqnBnKO6P/hB/22AA==", + "dev": true, + "requires": { + "@signalk/signalk-schema": "1.0.2", + "baconjs": "0.7.95" + }, + "dependencies": { + "@signalk/signalk-schema": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@signalk/signalk-schema/-/signalk-schema-1.0.2.tgz", + "integrity": "sha512-qU9T/qZfO/lW2+4kZbm0UCcKmrWv6dZMYp1XtFl1WSxrqHwPO5ctKJFhKFe4f2S2MpaUiXYF9MSTQtMHf6mNKQ==", + "dev": true, + "requires": { + "JSONStream": "0.7.4", + "debug": "2.6.9", + "json-schema-ref-parser": "3.3.1", + "lodash": "3.10.1", + "tv4": "1.3.0", + "tv4-formats": "2.2.2" + } + }, + "JSONStream": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.7.4.tgz", + "integrity": "sha1-c0KQ5BUR7qfCz+FR+/mlY6l7l4Y=", + "dev": true, + "requires": { + "jsonparse": "0.0.5", + "through": "2.3.8" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "jsonparse": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz", + "integrity": "sha1-MwVCrT8KZUZlt3jz6y2an6UHrGQ=", + "dev": true + }, + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", + "dev": true + } + } + }, + "JSONSelect": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/JSONSelect/-/JSONSelect-0.4.0.tgz", + "integrity": "sha1-oI7cxn6z/L6Z7WMIVTRKDPKCu40=", + "dev": true + }, + "JSONStream": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.2.tgz", + "integrity": "sha1-wQI3G27Dp887hHygDCC7D85Mbeo=", + "dev": true, + "requires": { + "jsonparse": "1.3.1", + "through": "2.3.8" + } + }, + "JSV": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/JSV/-/JSV-4.0.2.tgz", + "integrity": "sha1-0Hf2glVx+CEy+d/67Vh7QCn+/1c=", + "dev": true + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "accepts": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", + "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", + "dev": true, + "requires": { + "mime-types": "2.1.17", + "negotiator": "0.6.1" + } + }, + "access-control": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/access-control/-/access-control-1.0.0.tgz", + "integrity": "sha1-rrooLO53MT6FJAFj1p41sp421iY=", + "dev": true, + "requires": { + "millisecond": "0.1.2", + "setheader": "0.0.4", + "vary": "1.1.2" + } + }, + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.0.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", + "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.3.3" + } + }, + "argparse": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", + "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", + "dev": true, + "requires": { + "sprintf-js": "1.0.3" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "dev": true + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", + "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", + "dev": true, + "requires": { + "lodash": "4.17.4" + } + }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "dev": true + }, + "asyncemit": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/asyncemit/-/asyncemit-3.0.1.tgz", + "integrity": "sha1-zD4P4No5tTzBXls6qGFupqcr1Zk=", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", + "dev": true + }, + "baconjs": { + "version": "0.7.95", + "resolved": "https://registry.npmjs.org/baconjs/-/baconjs-0.7.95.tgz", + "integrity": "sha512-3qp0GuAfEUlJybSVPQ2oai8VYO0aSTJf4wP/jYZpgaffEXi31VBcqSlVmD8ahmXXGzgdO+yFk9onDnt2ZJJXxA==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base64url": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", + "integrity": "sha1-6sFuA+oUOO/5Qj1puqNiYu0fcLs=", + "dev": true + }, + "basic-auth": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.0.tgz", + "integrity": "sha1-AV2z81PgLlY3d1X5YnQuiYHnu7o=", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "bcrypt": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-1.0.3.tgz", + "integrity": "sha512-pRyDdo73C8Nim3jwFJ7DWe3TZCgwDfWZ6nHS5LSdU77kWbj1frruvdndP02AOavtD4y8v6Fp2dolbHgp4SDrfg==", + "dev": true, + "requires": { + "nan": "2.6.2", + "node-pre-gyp": "0.6.36" + } + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "bindings": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz", + "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE=", + "dev": true, + "optional": true + }, + "bit-buffer": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/bit-buffer/-/bit-buffer-0.2.3.tgz", + "integrity": "sha512-Ugsj2ZD23YKb4cCasdBD4l1Oy4eI/752hnCMAmZO62P+oS1emCqNMlkex02ppGWgKHTS6sfzFeUbPD6VWd+s1w==" + }, + "bl": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.1.tgz", + "integrity": "sha1-ysMo977kVzDUBLaSID/LWQ4XLV4=", + "requires": { + "readable-stream": "2.3.3" + } + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", + "dev": true + }, + "body-parser": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "dev": true, + "requires": { + "bytes": "3.0.0", + "content-type": "1.0.4", + "debug": "2.6.9", + "depd": "1.1.2", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "on-finished": "2.3.0", + "qs": "6.5.1", + "raw-body": "2.3.2", + "type-is": "1.6.15" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "boom": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", + "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", + "dev": true, + "requires": { + "hoek": "4.2.0" + } + }, + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=", + "dev": true + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true + }, + "call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", + "dev": true + }, + "canboatjs": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/canboatjs/-/canboatjs-0.0.1.tgz", + "integrity": "sha512-GBSte1Zvw7Sjy/wTjepIpJSNxNnd6KO2cJHpe1HnwGarM/BbLCoUvWwDfSvSqZHyb+ckWMYTCDSrIoOy/cvt1w==", + "requires": { + "bit-buffer": "0.2.3", + "debug": "3.1.0", + "int64-buffer": "0.1.10", + "lodash": "4.17.4", + "serialport": "6.0.4", + "split": "1.0.1" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chai": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", + "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", + "dev": true, + "requires": { + "assertion-error": "1.1.0", + "check-error": "1.0.2", + "deep-eql": "3.0.1", + "get-func-name": "2.0.0", + "pathval": "1.1.0", + "type-detect": "4.0.7" + }, + "dependencies": { + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "4.0.7" + } + }, + "type-detect": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.7.tgz", + "integrity": "sha512-4Rh17pAMVdMWzktddFhISRnUnFIStObtUMNGzDwlA6w/77bmGv3aBbRdCmQR6IjzfkTo9otnW+2K/cDRhKSxDA==", + "dev": true + } + } + }, + "charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=", + "dev": true + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "chownr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", + "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=" + }, + "cjson": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/cjson/-/cjson-0.3.0.tgz", + "integrity": "sha1-5kObkHA9MS/24iJAl76pLOPQKhQ=", + "dev": true, + "requires": { + "jsonlint": "1.6.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "color": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/color/-/color-0.8.0.tgz", + "integrity": "sha1-iQwHw/1OZJU3Y4kRz2keVFi2/KU=", + "dev": true, + "requires": { + "color-convert": "0.5.3", + "color-string": "0.3.0" + } + }, + "color-convert": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz", + "integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0=", + "dev": true + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "color-string": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", + "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "colornames": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/colornames/-/colornames-0.0.2.tgz", + "integrity": "sha1-2BH9bIT1kClJmorEQ2ICk1uSvjE=", + "dev": true + }, + "colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "dev": true + }, + "colorspace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.0.1.tgz", + "integrity": "sha1-yZx5btMRKLmHalLh7l7gOkpxl0k=", + "dev": true, + "requires": { + "color": "0.8.0", + "text-hex": "0.0.0" + } + }, + "combined-stream": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "dev": true, + "requires": { + "delayed-stream": "1.0.0" + } + }, + "commander": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==" + }, + "compare-versions": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.1.0.tgz", + "integrity": "sha512-4hAxDSBypT/yp2ySFD346So6Ragw5xmBn/e/agIGl3bZr6DLUqnoRZPusxKrXdYRZpgexO9daejmIenlq/wrIQ==", + "dev": true + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concentrate2": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/concentrate2/-/concentrate2-1.0.0.tgz", + "integrity": "sha512-v2S31jtmx4/dEqPwS/H9EDCa2ZeCP6NQ8XzfVNC30kDsQNc9rIy3zpFP42TXlDlRXL19NHum/2tv2SAkgugg4g==", + "requires": { + "int64-buffer": "0.1.10" + } + }, + "connected": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/connected/-/connected-0.0.2.tgz", + "integrity": "sha1-e1dVshbOMf+rzMOOn04d/Bw7fG0=", + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", + "dev": true + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "dev": true + }, + "cookie-parser": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.3.tgz", + "integrity": "sha1-D+MfoZ0AC5X0qt8fU/3CuKIDuqU=", + "dev": true, + "requires": { + "cookie": "0.3.1", + "cookie-signature": "1.0.6" + } + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "cookiejar": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.1.tgz", + "integrity": "sha1-Qa1XsbVVlR7BcUEqgZQrHoIA00o=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cors": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz", + "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=", + "dev": true, + "requires": { + "object-assign": "4.1.1", + "vary": "1.1.2" + } + }, + "create-server": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/create-server/-/create-server-1.0.1.tgz", + "integrity": "sha1-FkNCg08Yi77Hx7xGZ0Y8wrEwTEQ=", + "dev": true, + "requires": { + "connected": "0.0.2" + } + }, + "crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=", + "dev": true + }, + "cryptiles": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", + "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", + "dev": true, + "requires": { + "boom": "5.2.0" + }, + "dependencies": { + "boom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", + "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "dev": true, + "requires": { + "hoek": "4.2.0" + } + } + } + }, + "d3": { + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/d3/-/d3-3.5.17.tgz", + "integrity": "sha1-vEZ0gAQ3iyGjYMn8fPUjF5B2L7g=", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "1.0.0" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", + "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=" + }, + "deep-get-set": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/deep-get-set/-/deep-get-set-1.1.0.tgz", + "integrity": "sha1-R7uiAIo/3px1PcvmGjYkFgbZg28=", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, + "dev-null-stream": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/dev-null-stream/-/dev-null-stream-0.0.1.tgz", + "integrity": "sha1-oqLie025mSjW2NRNXF9++9TLQ3I=", + "dev": true + }, + "diagnostics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.0.tgz", + "integrity": "sha1-4QkJALSVI+hSe+IPCBJ1IF8q42o=", + "dev": true, + "requires": { + "colorspace": "1.0.1", + "enabled": "1.0.2", + "kuler": "0.0.0" + } + }, + "diff": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", + "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", + "dev": true + }, + "ebnf-parser": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/ebnf-parser/-/ebnf-parser-0.1.10.tgz", + "integrity": "sha1-zR9rpHfFY4xAyX7ZtXLbW6tdgzE=", + "dev": true + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz", + "integrity": "sha1-S8kmJ07Dtau1AW5+HWCSGsJisqE=", + "dev": true, + "requires": { + "base64url": "2.0.0", + "safe-buffer": "5.1.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "ejson": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ejson/-/ejson-2.1.2.tgz", + "integrity": "sha1-Du1AVbx+DnVh/lnowyDtw/+M598=", + "dev": true, + "requires": { + "underscore": "1.8.3" + }, + "dependencies": { + "underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", + "dev": true + } + } + }, + "emits": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emits/-/emits-3.0.0.tgz", + "integrity": "sha1-MnUrupXhcHshlWI4Srm7ix/WL3A=", + "dev": true + }, + "enabled": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", + "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", + "dev": true, + "requires": { + "env-variable": "0.0.3" + } + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "requires": { + "once": "1.4.0" + } + }, + "env-variable": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.3.tgz", + "integrity": "sha1-uGwWQb5WECZ9UG8YBx6nbXBwl8s=", + "dev": true + }, + "errorhandler": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.0.tgz", + "integrity": "sha1-6rpkyl1UKjEayUX1gt78M2Fl2fQ=", + "dev": true, + "requires": { + "accepts": "1.3.4", + "escape-html": "1.0.3" + } + }, + "es6-promise": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", + "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escodegen": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.3.3.tgz", + "integrity": "sha1-8CQBb1qI4Eb9EgBQVek5gC5sXyM=", + "dev": true, + "requires": { + "esprima": "1.1.1", + "estraverse": "1.5.1", + "esutils": "1.0.0", + "source-map": "0.1.43" + } + }, + "esprima": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.1.1.tgz", + "integrity": "sha1-W28VR/TRAuZw4UDFCb5ncdautUk=", + "dev": true + }, + "estraverse": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.5.1.tgz", + "integrity": "sha1-hno+jlip+EYYr7bC3bzZFrfLr3E=", + "dev": true + }, + "esutils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.0.0.tgz", + "integrity": "sha1-gVHTWOIMisx/t0XnRywAJf5JZXA=", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, + "eventemitter3": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.0.0.tgz", + "integrity": "sha512-62TxCtz4m2LRaOERVEvLJJ4A6rsg8lC9Xm+FLg2y/1fB/v4ZZ9JCOn+/Ppl5KkH6sRih6bhix724PVanmXYZJQ==", + "dev": true + }, + "expand-template": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-1.1.0.tgz", + "integrity": "sha512-kkjwkMqj0h4w/sb32ERCDxCQkREMCAgS39DscDnSwDsbxnwwM1BTZySdC3Bn1lhY7vL08n9GoO/fVTynjDgRyQ==" + }, + "express": { + "version": "4.16.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", + "integrity": "sha1-41xt/i1kt9ygpc1PIXgb4ymeB2w=", + "dev": true, + "requires": { + "accepts": "1.3.4", + "array-flatten": "1.1.1", + "body-parser": "1.18.2", + "content-disposition": "0.5.2", + "content-type": "1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "1.1.2", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "etag": "1.8.1", + "finalhandler": "1.1.0", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "1.1.2", + "on-finished": "2.3.0", + "parseurl": "1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "2.0.2", + "qs": "6.5.1", + "range-parser": "1.2.0", + "safe-buffer": "5.1.1", + "send": "0.16.1", + "serve-static": "1.13.1", + "setprototypeof": "1.1.0", + "statuses": "1.3.1", + "type-is": "1.6.15", + "utils-merge": "1.0.1", + "vary": "1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", + "dev": true + } + } + }, + "express-namespace": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/express-namespace/-/express-namespace-0.1.1.tgz", + "integrity": "sha1-xJhQp0zqFu2UuoVjdAHYZsYbGfE=", + "dev": true, + "requires": { + "methods": "0.0.1" + }, + "dependencies": { + "methods": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/methods/-/methods-0.0.1.tgz", + "integrity": "sha1-J3yQ+L7zlwlkWoNxxRw7bGSOBow=", + "dev": true + } + } + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", + "dev": true + }, + "extendible": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/extendible/-/extendible-0.1.1.tgz", + "integrity": "sha1-4qN+2HEp+0+VM+io11BiMKU5yQU=", + "dev": true + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", + "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "file-timestamp-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/file-timestamp-stream/-/file-timestamp-stream-0.1.0.tgz", + "integrity": "sha512-pa2Jo/hLu8ya449oYlW64YzseEO7QsnJnlx83nm8V62Ntr7PpiB/1EP56exvT6bf8O0hnXuuT42hQj1GJK9cpg==", + "dev": true, + "requires": { + "ultra-strftime": "1.0.2" + } + }, + "finalhandler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", + "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "on-finished": "2.3.0", + "parseurl": "1.3.2", + "statuses": "1.3.1", + "unpipe": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", + "dev": true + } + } + }, + "flatmap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/flatmap/-/flatmap-0.0.3.tgz", + "integrity": "sha1-Hxik2TgVLUlZZfnJWNkjqy3WabQ=", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", + "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", + "dev": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.17" + } + }, + "format-util": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/format-util/-/format-util-1.0.3.tgz", + "integrity": "sha1-Ay3KShFiYqEsQ/TD7IVmQWxbLZU=", + "dev": true + }, + "formatio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", + "integrity": "sha1-87IWfZBoxGmKjVH092CjmlTYGOs=", + "dev": true, + "requires": { + "samsam": "1.3.0" + } + }, + "formidable": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.1.1.tgz", + "integrity": "sha1-lriIb3w8NQi5Mta9cMTTqI818ak=", + "dev": true + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "dev": true + }, + "forwarded-for": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/forwarded-for/-/forwarded-for-1.0.1.tgz", + "integrity": "sha1-59pIFAJRaP/AoQ0/954UFfRq9Gk=", + "dev": true + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fstream": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.2" + } + }, + "fstream-ignore": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", + "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=", + "dev": true, + "requires": { + "fstream": "1.0.11", + "inherits": "2.0.3", + "minimatch": "3.0.4" + } + }, + "fusing": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fusing/-/fusing-1.0.0.tgz", + "integrity": "sha1-VQwV12r5Jld4qgUezkTUAAoJjUU=", + "dev": true, + "requires": { + "emits": "3.0.0", + "predefine": "0.1.2" + } + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" + } + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "1.0.0" + } + }, + "ggencoder": { + "version": "0.1.20", + "resolved": "https://registry.npmjs.org/ggencoder/-/ggencoder-0.1.20.tgz", + "integrity": "sha1-P22Y3BgSlUx3gX0/7GB2YwBaRsQ=", + "dev": true, + "requires": { + "async": "2.6.0", + "jison": "0.4.18" + } + }, + "github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=" + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true + }, + "growl": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", + "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "dev": true, + "requires": { + "ajv": "5.5.2", + "har-schema": "2.0.0" + } + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "hawk": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", + "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", + "dev": true, + "requires": { + "boom": "4.3.1", + "cryptiles": "3.1.2", + "hoek": "4.2.0", + "sntp": "2.1.0" + } + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "hoek": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", + "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==", + "dev": true + }, + "http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "dev": true, + "requires": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": "1.4.0" + }, + "dependencies": { + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", + "dev": true + } + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.13.1" + } + }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "int64-buffer": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/int64-buffer/-/int64-buffer-0.1.10.tgz", + "integrity": "sha1-J3siiofZWtd30HwTgyAiQGpHNCM=" + }, + "ipaddr.js": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.5.2.tgz", + "integrity": "sha1-1LUFvemUaYfM8PxY2QEP+WB+P6A=", + "dev": true + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isemail": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-1.2.0.tgz", + "integrity": "sha1-vgPfjMPineTSxd9lASY/H6RZXpo=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "jison": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/jison/-/jison-0.4.18.tgz", + "integrity": "sha512-FKkCiJvozgC7VTHhMJ00a0/IApSxhlGsFIshLW6trWJ8ONX2TQJBBz6DlcO1Gffy4w9LT+uL+PA+CVnUSJMF7w==", + "dev": true, + "requires": { + "JSONSelect": "0.4.0", + "cjson": "0.3.0", + "ebnf-parser": "0.1.10", + "escodegen": "1.3.3", + "esprima": "1.1.1", + "jison-lex": "0.3.4", + "lex-parser": "0.1.4", + "nomnom": "1.5.2" + } + }, + "jison-lex": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/jison-lex/-/jison-lex-0.3.4.tgz", + "integrity": "sha1-gcoo2E+ESZ36jFlNzePYo/Jux6U=", + "dev": true, + "requires": { + "lex-parser": "0.1.4", + "nomnom": "1.5.2" + } + }, + "joi": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-6.10.1.tgz", + "integrity": "sha1-TVDDGAeRIgAP5fFq8f+OGRe3fgY=", + "dev": true, + "requires": { + "hoek": "2.16.3", + "isemail": "1.2.0", + "moment": "2.20.1", + "topo": "1.1.0" + }, + "dependencies": { + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", + "dev": true + } + } + }, + "jquery": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-2.2.4.tgz", + "integrity": "sha1-LInWiJterFIqfuoywUUhVZxsvwI=", + "dev": true + }, + "js-quantities": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/js-quantities/-/js-quantities-1.7.0.tgz", + "integrity": "sha1-dXxb4BLbPXJ504q/xRhyImd17h4=", + "dev": true + }, + "js-yaml": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", + "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", + "dev": true, + "requires": { + "argparse": "1.0.9", + "esprima": "4.0.0" + }, + "dependencies": { + "esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "dev": true + } + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true, + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-ref-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-3.3.1.tgz", + "integrity": "sha512-stQTMhec2R/p2L9dH4XXRlpNCP0mY8QrLd/9Kl+8SHJQmwHtE1nDfXH4wbsSM+GkJMl8t92yZbI0OIol432CIQ==", + "dev": true, + "requires": { + "call-me-maybe": "1.0.1", + "debug": "3.1.0", + "es6-promise": "4.2.4", + "js-yaml": "3.10.0", + "ono": "4.0.3", + "z-schema": "3.19.0" + } + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json3": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "dev": true + }, + "jsonlint": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/jsonlint/-/jsonlint-1.6.0.tgz", + "integrity": "sha1-iKpGvCiaesk7tGyuLVihh6m7SUo=", + "dev": true, + "requires": { + "JSV": "4.0.2", + "nomnom": "1.5.2" + } + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "jsonwebtoken": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-7.4.3.tgz", + "integrity": "sha1-d/UCHeBYtgWheD+hKD6ZgS5kVjg=", + "dev": true, + "requires": { + "joi": "6.10.1", + "jws": "3.1.4", + "lodash.once": "4.1.1", + "ms": "2.0.0", + "xtend": "4.0.1" + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "just-extend": { + "version": "1.1.27", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-1.1.27.tgz", + "integrity": "sha512-mJVp13Ix6gFo3SBAy9U/kL+oeZqzlYYYLQBwXVBlVzIsZwBqGREnOro24oC/8s8aox+rJhtZ2DiQof++IrkA+g==", + "dev": true + }, + "jwa": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.5.tgz", + "integrity": "sha1-oFUs4CIHQs1S4VN3SjKQXDDnVuU=", + "dev": true, + "requires": { + "base64url": "2.0.0", + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.9", + "safe-buffer": "5.1.1" + } + }, + "jws": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.4.tgz", + "integrity": "sha1-+ei5M46KhHJ31kRLFGT2GIDgUKI=", + "dev": true, + "requires": { + "base64url": "2.0.0", + "jwa": "1.1.5", + "safe-buffer": "5.1.1" + } + }, + "kuler": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-0.0.0.tgz", + "integrity": "sha1-tmu0a5NOVQ9Z2BiEjgq7pPf1VTw=", + "dev": true, + "requires": { + "colornames": "0.0.2" + } + }, + "leaflet": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-0.7.7.tgz", + "integrity": "sha1-HjUrpU5j0HZFH6NjyQCJDLLPde4=", + "dev": true + }, + "lex-parser": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/lex-parser/-/lex-parser-0.1.4.tgz", + "integrity": "sha1-ZMTwJfF/1Tv7RXY/rrFvAVp0dVA=", + "dev": true + }, + "limiter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.2.tgz", + "integrity": "sha512-JIKZ0xb6fZZYa3deZ0BgXCgX6HgV8Nx3mFGeFHmFWW8Fb2c08e0CyE+G3nalpD0xGvGssjGb1UdFr+PprxZEbw==", + "dev": true + }, + "lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + }, + "lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "dev": true, + "requires": { + "lodash._basecopy": "3.0.1", + "lodash.keys": "3.1.2" + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._basecreate": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", + "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash.create": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", + "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", + "dev": true, + "requires": { + "lodash._baseassign": "3.2.0", + "lodash._basecreate": "3.0.3", + "lodash._isiterateecall": "3.0.9" + } + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", + "dev": true + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "3.9.1", + "lodash.isarguments": "3.1.0", + "lodash.isarray": "3.0.4" + } + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=", + "dev": true + }, + "lolex": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.3.1.tgz", + "integrity": "sha512-mQuW55GhduF3ppo+ZRUTz1PRjEh1hS5BbqU7d8D0ez2OKxHDod7StPPeAVKisZR5aLkHZjdGWSL42LSONUJsZw==", + "dev": true + }, + "md5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", + "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=", + "dev": true, + "requires": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "1.1.6" + } + }, + "mdns": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/mdns/-/mdns-2.3.4.tgz", + "integrity": "sha512-Z4WTKeTukCtJG53SS3BGNnsGkHdIXNZa9nwGMYeoohU1AjEBPS3c/1vIx95SEfeQKYduuOMTo1E4RfXDUt2ZYg==", + "dev": true, + "optional": true, + "requires": { + "bindings": "1.2.1", + "nan": "2.3.5" + }, + "dependencies": { + "nan": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.3.5.tgz", + "integrity": "sha1-gioNwmYpDOTNOhIoLKPn42Rmigg=", + "dev": true, + "optional": true + } + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "millisecond": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/millisecond/-/millisecond-0.1.2.tgz", + "integrity": "sha1-bMWtOGJByrjniv+WT4cCjuyS2sU=", + "dev": true + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "mime-db": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=", + "dev": true + }, + "mime-types": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "dev": true, + "requires": { + "mime-db": "1.30.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.8" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.0.0.tgz", + "integrity": "sha512-ukB2dF+u4aeJjc6IGtPNnJXfeby5d4ZqySlIBT0OEyva/DrMjVm5HkQxKnHDLKEfEQBsEnwTg9HHhtPHJdTd8w==", + "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.11.0", + "debug": "3.1.0", + "diff": "3.3.1", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.3", + "he": "1.1.1", + "mkdirp": "0.5.1", + "supports-color": "4.4.0" + } + }, + "moment": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.20.1.tgz", + "integrity": "sha512-Yh9y73JRljxW5QxN08Fner68eFLxM5ynNOAw2LbIB1YAGeQzZT8QFSUvkAz609Zf+IHhhaUxqZK8dG3W/+HEvg==", + "dev": true + }, + "moment-timezone": { + "version": "0.5.14", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.14.tgz", + "integrity": "sha1-TrOP+VOLgBCLpGekWPPtQmjM/LE=", + "dev": true, + "requires": { + "moment": "2.20.1" + } + }, + "morgan": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.0.tgz", + "integrity": "sha1-0B+mxlhZt2/PMbPLU6OCGjEdgFE=", + "dev": true, + "requires": { + "basic-auth": "2.0.0", + "debug": "2.6.9", + "depd": "1.1.2", + "on-finished": "2.3.0", + "on-headers": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "nan": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.6.2.tgz", + "integrity": "sha1-5P805slf37WuzAjeZZb0NgWn20U=" + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "dev": true + }, + "nise": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.2.0.tgz", + "integrity": "sha512-q9jXh3UNsMV28KeqI43ILz5+c3l+RiNW8mhurEwCKckuHQbL+hTJIKKTiUlCPKlgQ/OukFvSnKB/Jk3+sFbkGA==", + "dev": true, + "requires": { + "formatio": "1.2.0", + "just-extend": "1.1.27", + "lolex": "1.6.0", + "path-to-regexp": "1.7.0", + "text-encoding": "0.6.4" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "lolex": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", + "integrity": "sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=", + "dev": true + }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + } + } + }, + "node-abi": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.1.2.tgz", + "integrity": "sha512-hmUtb8m75RSi7N+zZLYqe75XDvZB+6LyTBPkj2DConvNgQet2e3BIqEwe1LLvqMrfyjabuT5ZOrTioLCH1HTdA==", + "requires": { + "semver": "5.5.0" + } + }, + "node-gpsd": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/node-gpsd/-/node-gpsd-0.3.0.tgz", + "integrity": "sha1-RjerBRfXAYyCDtHTM/zirPaBw9U=", + "dev": true + }, + "node-pre-gyp": { + "version": "0.6.36", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz", + "integrity": "sha1-22BBEst04NR3VU6bUFsXq936t4Y=", + "dev": true, + "requires": { + "mkdirp": "0.5.1", + "nopt": "4.0.1", + "npmlog": "4.1.2", + "rc": "1.2.4", + "request": "2.83.0", + "rimraf": "2.6.2", + "semver": "5.5.0", + "tar": "2.2.1", + "tar-pack": "3.4.1" + } + }, + "nomnom": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.5.2.tgz", + "integrity": "sha1-9DRUSKhTz71cDSYyDyR3qwUm/i8=", + "dev": true, + "requires": { + "colors": "0.5.1", + "underscore": "1.1.7" + }, + "dependencies": { + "colors": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz", + "integrity": "sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q=", + "dev": true + } + } + }, + "noop-logger": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", + "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=" + }, + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "dev": true, + "requires": { + "abbrev": "1.1.1", + "osenv": "0.1.4" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", + "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1.0.2" + } + }, + "ono": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/ono/-/ono-4.0.3.tgz", + "integrity": "sha512-7QIxG4UB00H7CR7fhXC/U7VhB5DK9wsYLwaYBui1JmQoXtLkhIBn3fbuk6FgAP+ctWeBsWVTM+R/bThvUZN+ww==", + "dev": true, + "requires": { + "format-util": "1.0.3" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "osenv": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz", + "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=", + "dev": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", + "dev": true + }, + "passport-jwt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-3.0.1.tgz", + "integrity": "sha1-5Pcnba2L0lHUPG/DiIMTC5YycvY=", + "dev": true, + "requires": { + "jsonwebtoken": "7.4.3", + "passport-strategy": "1.0.0" + } + }, + "passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, + "pem": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/pem/-/pem-1.12.3.tgz", + "integrity": "sha512-hT7GwvQL35+0iqgYUl8vn5I5pAVR0HcJas07TXL8bNaR4c5kAFRquk4ZqQk1F9YMcQOr6WjGdY5OnDC0RBnzig==", + "dev": true, + "requires": { + "md5": "2.2.1", + "os-tmpdir": "1.0.2", + "safe-buffer": "5.1.1", + "which": "1.3.0" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "prebuild-install": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-2.5.0.tgz", + "integrity": "sha512-3wlyZgmkeeyduOR8Ursu5gKr3yWAYObACa5aJOtt2farRRFV/+zXk/Y3wM6yQRMqmqHh+pHAwyKp5r82K699Rg==", + "requires": { + "detect-libc": "1.0.3", + "expand-template": "1.1.0", + "github-from-package": "0.0.0", + "minimist": "1.2.0", + "mkdirp": "0.5.1", + "node-abi": "2.1.2", + "noop-logger": "0.1.1", + "npmlog": "4.1.2", + "os-homedir": "1.0.2", + "pump": "1.0.3", + "rc": "1.2.4", + "simple-get": "1.4.3", + "tar-fs": "1.16.0", + "tunnel-agent": "0.6.0", + "xtend": "4.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "predefine": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/predefine/-/predefine-0.1.2.tgz", + "integrity": "sha1-KqkrRJa8H4VU5DpF92v75Q0z038=", + "dev": true, + "requires": { + "extendible": "0.1.1" + } + }, + "primus": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/primus/-/primus-7.1.1.tgz", + "integrity": "sha512-PXogAiNJ6Dmjmx4ODz+uwzNmUZU7IWj6uvVKKnoijRpsTV10aSeHkaFfIfq+kVIrdagOmz5k3HOR5NP9OCtpZw==", + "dev": true, + "requires": { + "access-control": "1.0.0", + "asyncemit": "3.0.1", + "create-server": "1.0.1", + "diagnostics": "1.1.0", + "eventemitter3": "3.0.0", + "forwarded-for": "1.0.1", + "fusing": "1.0.0", + "setheader": "0.0.4", + "ultron": "1.1.1", + "yeast": "0.1.2" + } + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + }, + "promirepl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promirepl/-/promirepl-1.0.1.tgz", + "integrity": "sha1-KVGq66K/P+InT/Y6FtlMBMpghy4=" + }, + "proxy-addr": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz", + "integrity": "sha1-ZXFQT0e7mI7IGAJT+F3X4UlSvew=", + "dev": true, + "requires": { + "forwarded": "0.1.2", + "ipaddr.js": "1.5.2" + } + }, + "pump": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", + "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", + "requires": { + "end-of-stream": "1.4.1", + "once": "1.4.0" + } + }, + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", + "dev": true + }, + "raw-body": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "dev": true, + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "unpipe": "1.0.0" + } + }, + "rc": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.4.tgz", + "integrity": "sha1-oPYGyq4qO4YrvQ74VILAElsxX6M=", + "requires": { + "deep-extend": "0.4.2", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "request": { + "version": "2.83.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", + "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", + "dev": true, + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.3.1", + "har-validator": "5.0.3", + "hawk": "6.0.2", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.17", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.1", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.3", + "tunnel-agent": "0.6.0", + "uuid": "3.2.1" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "samsam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", + "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", + "dev": true + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + }, + "send": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", + "integrity": "sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "1.1.2", + "destroy": "1.0.4", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "etag": "1.8.1", + "fresh": "0.5.2", + "http-errors": "1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "2.3.0", + "range-parser": "1.2.0", + "statuses": "1.3.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "dev": true + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", + "dev": true + } + } + }, + "serialport": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/serialport/-/serialport-6.0.4.tgz", + "integrity": "sha512-ohiyBppkW0rRbd7CksNSsH8kTx5Fdh1TRL0yp+Yoefgcz12x8j2x+CLsIi23OxSuCTf+tJ9SjJSh93wzTl6tEw==", + "requires": { + "bindings": "1.3.0", + "commander": "2.11.0", + "debug": "3.1.0", + "nan": "2.6.2", + "prebuild-install": "2.5.0", + "promirepl": "1.0.1", + "safe-buffer": "5.1.1" + }, + "dependencies": { + "bindings": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.0.tgz", + "integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw==" + } + } + }, + "serve-static": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", + "integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==", + "dev": true, + "requires": { + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "parseurl": "1.3.2", + "send": "0.16.1" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "setheader": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/setheader/-/setheader-0.0.4.tgz", + "integrity": "sha1-km7SjPdiFJYgkx566j8blYFuxpQ=", + "dev": true, + "requires": { + "debug": "0.7.4" + }, + "dependencies": { + "debug": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz", + "integrity": "sha1-BuHqgILCyxTjmAbiLi9vdX+Srzk=", + "dev": true + } + } + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "signalk-server": { + "version": "1.0.0-0", + "resolved": "https://registry.npmjs.org/signalk-server/-/signalk-server-1.0.0-0.tgz", + "integrity": "sha512-tiXTMSFMOPmZysCr93bKJ3Qjdr2bOMvsf81MUUlZl1gyPIqE4/79BgjbLFL0/Mh8xR4zrLWXQOP5Ocp9NuemJA==", + "dev": true, + "requires": { + "@signalk/aisreporter": "0.0.3", + "@signalk/client": "0.2.1", + "@signalk/freeboard-sk": "0.0.1", + "@signalk/instrumentpanel": "0.2.5", + "@signalk/maptracker": "1.0.0", + "@signalk/n2k-signalk": "1.0.1", + "@signalk/nmea0183-signalk": "2.1.2", + "@signalk/playground": "1.0.0", + "@signalk/sailgauge": "1.1.0", + "@signalk/set-system-time": "1.0.0", + "@signalk/signalk-schema": "1.0.3", + "@signalk/signalk-to-nmea0183": "0.1.1", + "@signalk/simplegauges": "1.0.1", + "@signalk/sk-simple-token-security": "2.2.2", + "@signalk/zones": "1.0.0", + "baconjs": "1.0.1", + "body-parser": "1.18.2", + "colors": "1.1.2", + "compare-versions": "3.1.0", + "cookie": "0.3.1", + "cors": "2.8.4", + "debug": "3.1.0", + "deep-get-set": "1.1.0", + "dev-null-stream": "0.0.1", + "ejson": "2.1.2", + "errorhandler": "1.5.0", + "express": "4.16.2", + "express-namespace": "0.1.1", + "file-timestamp-stream": "0.1.0", + "flatmap": "0.0.3", + "lodash": "4.17.4", + "mdns": "2.3.4", + "minimist": "1.2.0", + "moment": "2.20.1", + "morgan": "1.9.0", + "node-gpsd": "0.3.0", + "pem": "1.12.3", + "primus": "7.1.1", + "serialport": "6.0.4", + "split": "1.0.1", + "stat-mode": "0.2.2", + "stream-throttle": "0.1.3", + "through": "2.3.8", + "ws": "3.3.3", + "xml2js": "0.4.19" + }, + "dependencies": { + "baconjs": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/baconjs/-/baconjs-1.0.1.tgz", + "integrity": "sha512-f4YHGKrxagnsgzrd5E33VIcfTJaFBgBwvwINP0vi0mJW9Rddjda6sG/ICBeT5a5foZmqwWDhRbWpbSH+TLVKxw==", + "dev": true + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "simple-get": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-1.4.3.tgz", + "integrity": "sha1-6XVe2kB+ltpAxeUVjJ6jezO+y+s=", + "requires": { + "once": "1.4.0", + "unzip-response": "1.0.2", + "xtend": "4.0.1" + } + }, + "sinon": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.2.1.tgz", + "integrity": "sha512-lx9ZCoScNhvs6+n3ku78CqIpQNIiY9gLfvuIdhZjnKOkcVL6FfWfA1jkOrcO+n3mX4BIg2XwQnyCS/Ive21QMw==", + "dev": true, + "requires": { + "diff": "3.3.1", + "formatio": "1.2.0", + "lodash.get": "4.4.2", + "lolex": "2.3.1", + "nise": "1.2.0", + "supports-color": "5.1.0", + "type-detect": "4.0.7" + }, + "dependencies": { + "supports-color": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.1.0.tgz", + "integrity": "sha512-Ry0AwkoKjDpVKK4sV4h6o3UJmNRbjYm2uXhwfj3J56lMVdvnUNqzQVRztOOMGQ++w1K/TjNDFvpJk0F/LoeBCQ==", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + }, + "type-detect": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.7.tgz", + "integrity": "sha512-4Rh17pAMVdMWzktddFhISRnUnFIStObtUMNGzDwlA6w/77bmGv3aBbRdCmQR6IjzfkTo9otnW+2K/cDRhKSxDA==", + "dev": true + } + } + }, + "sntp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", + "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", + "dev": true, + "requires": { + "hoek": "4.2.0" + } + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true, + "optional": true, + "requires": { + "amdefine": "1.0.1" + } + }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "requires": { + "through": "2.3.8" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", + "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "dev": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + } + }, + "stat-mode": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-0.2.2.tgz", + "integrity": "sha1-5sgLYjEj19gM8TLOU480YokHJQI=", + "dev": true + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true + }, + "stream-throttle": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/stream-throttle/-/stream-throttle-0.1.3.tgz", + "integrity": "sha1-rdV8jXzHOoFjDTHNVdOWHPr7qcM=", + "dev": true, + "requires": { + "commander": "2.11.0", + "limiter": "1.1.2" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "requires": { + "safe-buffer": "5.1.1" + } + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "superagent": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.2.tgz", + "integrity": "sha512-gVH4QfYHcY3P0f/BZzavLreHW3T1v7hG9B+hpMQotGQqurOvhv87GcMCd6LWySmBuf+BDR44TQd0aISjVHLeNQ==", + "dev": true, + "requires": { + "component-emitter": "1.2.1", + "cookiejar": "2.1.1", + "debug": "3.1.0", + "extend": "3.0.1", + "form-data": "2.3.1", + "formidable": "1.1.1", + "methods": "1.1.2", + "mime": "1.6.0", + "qs": "6.5.1", + "readable-stream": "2.3.3" + } + }, + "superagent-promise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/superagent-promise/-/superagent-promise-1.1.0.tgz", + "integrity": "sha1-uvIti73UOamwfdEPjAj1T+JQNTM=", + "dev": true + }, + "supports-color": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + }, + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "dev": true, + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } + }, + "tar-fs": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.0.tgz", + "integrity": "sha512-I9rb6v7mjWLtOfCau9eH5L7sLJyU2BnxtEZRQ5Mt+eRKmf1F0ohXmT/Jc3fr52kDvjJ/HV5MH3soQfPL5bQ0Yg==", + "requires": { + "chownr": "1.0.1", + "mkdirp": "0.5.1", + "pump": "1.0.3", + "tar-stream": "1.5.5" + } + }, + "tar-pack": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.1.tgz", + "integrity": "sha512-PPRybI9+jM5tjtCbN2cxmmRU7YmqT3Zv/UDy48tAh2XRkLa9bAORtSWLkVc13+GJF+cdTh1yEnHEk3cpTaL5Kg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "fstream": "1.0.11", + "fstream-ignore": "1.0.5", + "once": "1.4.0", + "readable-stream": "2.3.3", + "rimraf": "2.6.2", + "tar": "2.2.1", + "uid-number": "0.0.6" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "tar-stream": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.5.tgz", + "integrity": "sha512-mQdgLPc/Vjfr3VWqWbfxW8yQNiJCbAZ+Gf6GDu1Cy0bdb33ofyiNGBtAY96jHFhDuivCwgW1H9DgTON+INiXgg==", + "requires": { + "bl": "1.2.1", + "end-of-stream": "1.4.1", + "readable-stream": "2.3.3", + "xtend": "4.0.1" + } + }, + "text-encoding": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", + "dev": true + }, + "text-hex": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-0.0.0.tgz", + "integrity": "sha1-V4+8haapJjbkLdF7QdAhjM6esrM=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "topo": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/topo/-/topo-1.1.0.tgz", + "integrity": "sha1-6ddRYV0buH3IZdsYL6HKCl71NtU=", + "dev": true, + "requires": { + "hoek": "2.16.3" + }, + "dependencies": { + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", + "dev": true + } + } + }, + "tough-cookie": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", + "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", + "dev": true, + "requires": { + "punycode": "1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "5.1.1" + } + }, + "tv4": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tv4/-/tv4-1.3.0.tgz", + "integrity": "sha1-0CDIRvrdUMhVq7JeuuzGj8EPeWM=", + "dev": true + }, + "tv4-formats": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tv4-formats/-/tv4-formats-2.2.2.tgz", + "integrity": "sha1-gz2mNt7jtOd85kHRUOYIAY3GW40=", + "dev": true, + "requires": { + "moment": "2.20.1", + "validator": "7.2.0" + }, + "dependencies": { + "validator": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-7.2.0.tgz", + "integrity": "sha512-c8NGTUYeBEcUIGeMppmNVKHE7wwfm3mYbNZxV+c5mlv9fDHI7Ad3p07qfNrn/CvpdkK2k61fOLRO2sTEhgQXmg==", + "dev": true + } + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, + "optional": true + }, + "type-is": { + "version": "1.6.15", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", + "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "2.1.17" + } + }, + "uid-number": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", + "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=", + "dev": true + }, + "ultra-strftime": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ultra-strftime/-/ultra-strftime-1.0.2.tgz", + "integrity": "sha1-v6NLVY9Rgdo+Ybk2GaL3kqtSdjw=", + "dev": true + }, + "ultron": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", + "dev": true + }, + "underscore": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.1.7.tgz", + "integrity": "sha1-QLq4S60Z0jAJbo1u9ii/8FXYPbA=", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "unzip-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.2.tgz", + "integrity": "sha1-uYTwh3/AqJwsdzzB73tbIytbBv4=" + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, + "uuid": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", + "dev": true + }, + "validator": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-9.2.0.tgz", + "integrity": "sha512-6Ij4Eo0KM4LkR0d0IegOwluG5453uqT5QyF5SV5Ezvm8/zmkKI/L4eoraafZGlZPC9guLkwKzgypcw8VGWWnGA==", + "dev": true + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + } + }, + "which": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", + "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "dev": true, + "requires": { + "isexe": "2.0.0" + } + }, + "wide-align": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", + "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", + "requires": { + "string-width": "1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "dev": true, + "requires": { + "async-limiter": "1.0.0", + "safe-buffer": "5.1.1", + "ultron": "1.1.1" + } + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "dev": true, + "requires": { + "sax": "1.2.4", + "xmlbuilder": "9.0.4" + } + }, + "xmlbuilder": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.4.tgz", + "integrity": "sha1-UZy0ymhtAFqEINNJbz8MruzKWA8=", + "dev": true + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", + "dev": true + }, + "z-schema": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-3.19.0.tgz", + "integrity": "sha512-V94f3ODuluBS4kQLLjNhwoMek0dyIXCsvNu/A17dAyJ6sMhT5KkJQwSn07R0naByLIXJWMDk+ruMfI/3G3hS4Q==", + "dev": true, + "requires": { + "commander": "2.11.0", + "lodash.get": "4.4.2", + "lodash.isequal": "4.5.0", + "validator": "9.2.0" + } + } + } +} diff --git a/package.json b/package.json index 2688ab7..d345d5b 100644 --- a/package.json +++ b/package.json @@ -21,10 +21,10 @@ "license": "ISC", "dependencies": { "baconjs": "^0.7.88", + "canboatjs": "0.0.1", + "concentrate2": "^1.0.0", "debug": "^3.1.0", - "lodash": "^4.17.4", - "to-n2k": "^1.0.0", - "concentrate2": "^1.0.0" + "lodash": "^4.17.4" }, "repository": { "type": "git", From 4500e8a4285bebf9950476dbae4c265b14eaf79d Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Mon, 29 Jan 2018 19:19:36 -0500 Subject: [PATCH 19/24] fix: change option keys because they are different from v1 --- conversions/ais.js | 2 +- conversions/battery.js | 5 ++++- conversions/cogSOG.js | 2 +- conversions/depth.js | 2 +- conversions/gps.js | 2 +- conversions/heading.js | 2 +- conversions/systemTime.js | 2 +- conversions/wind.js | 2 +- 8 files changed, 11 insertions(+), 8 deletions(-) diff --git a/conversions/ais.js b/conversions/ais.js index 1eaf9e7..ee9810e 100644 --- a/conversions/ais.js +++ b/conversions/ais.js @@ -25,7 +25,7 @@ module.exports = (app, plugin) => { title: `AIS (${static_pgn}, ${position_pgn}, ${aton_pgn})`, sourceType: 'onDelta', outputType: 'buffer', - optionKey: 'AIS', + optionKey: 'AISv2', callback: (delta) => { var selfContext = 'vessels.' + app.selfId diff --git a/conversions/battery.js b/conversions/battery.js index b028085..e21c9eb 100644 --- a/conversions/battery.js +++ b/conversions/battery.js @@ -15,7 +15,7 @@ module.exports = (app, plugin) => { return { title: 'Battery (127506 & 127508)', - optionKey: 'BATTERY', + optionKey: 'BATTERYv2', context: 'vessels.self', properties: { batteries: { @@ -38,6 +38,9 @@ module.exports = (app, plugin) => { }, conversions: (options) => { + if ( !_.get(options, 'BATTERY.batteries') ) { + return null + } return options.BATTERY.batteries.map(battery => { return { keys: batteryKeys.map(key => `electrical.batteries.${battery.signalkId}.${key}`), diff --git a/conversions/cogSOG.js b/conversions/cogSOG.js index 395c05a..2c3be36 100644 --- a/conversions/cogSOG.js +++ b/conversions/cogSOG.js @@ -7,7 +7,7 @@ module.exports = (app, plugin) => { return { title: 'COG & SOG (129026)', - optionKey: 'COG_SOG', + optionKey: 'COG_SOGv2', keys: ["navigation.courseOverGroundTrue", "navigation.speedOverGround"], callback: (course, speed) => { try { diff --git a/conversions/depth.js b/conversions/depth.js index e054541..57ff4fa 100644 --- a/conversions/depth.js +++ b/conversions/depth.js @@ -4,7 +4,7 @@ const _ = require('lodash') module.exports = (app, plugin) => { return { title: 'Depth (128267)', - optionKey: 'DEPTH', + optionKey: 'DEPTHv2', keys: ["environment.depth.belowTransducer"], callback: (belowTransducer) => { var surfaceToTransducer = _.get(app.signalk.self, diff --git a/conversions/gps.js b/conversions/gps.js index db68801..1f1da53 100644 --- a/conversions/gps.js +++ b/conversions/gps.js @@ -7,7 +7,7 @@ module.exports = (app, plugin) => { return { title: 'Location (129025,129029)', - optionKey: 'GPS_LOCATION', + optionKey: 'GPS_LOCATIONv2', keys: ["navigation.position"], callback: (position) => { //debug(`position: ${JSON.stringify(position)}`) diff --git a/conversions/heading.js b/conversions/heading.js index c26449e..0a2faef 100644 --- a/conversions/heading.js +++ b/conversions/heading.js @@ -3,7 +3,7 @@ module.exports = (app, plugin) => { return { pgn: 127250, title: 'Heading (127250)', - optionKey: 'HEADING', + optionKey: 'HEADINGv2', keys: [ "navigation.headingMagnetic", 'navigation.magneticVariation' diff --git a/conversions/systemTime.js b/conversions/systemTime.js index ec50bff..8219122 100644 --- a/conversions/systemTime.js +++ b/conversions/systemTime.js @@ -5,7 +5,7 @@ module.exports = (app, plugin) => { title: 'System Time (126992)', sourceType: 'timer', interval: 1000, - optionKey: 'SYSTEM_TIME', + optionKey: 'SYSTEM_TIMEv2', callback: (app) => { var dateObj = new Date(); var date = Math.trunc(dateObj.getTime() / 86400 / 1000); diff --git a/conversions/wind.js b/conversions/wind.js index 175dd61..e93a870 100644 --- a/conversions/wind.js +++ b/conversions/wind.js @@ -3,7 +3,7 @@ const Concentrate2 = require("concentrate2"); module.exports = (app, plugin) => { return { title: 'Wind (130306)', - optionKey: 'WIND', + optionKey: 'WINDv2', keys: ["environment.wind.angleApparent", "environment.wind.speedApparent"], callback: (angle, speed) => { try { From eb0b054536e807fdb991c4c3e04e1fec94e1289a Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Mon, 29 Jan 2018 19:20:25 -0500 Subject: [PATCH 20/24] fix: allow conversions to return null --- index.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/index.js b/index.js index 76665e1..1d64b34 100644 --- a/index.js +++ b/index.js @@ -104,18 +104,20 @@ module.exports = function(app) { } else if ( _.isFunction(subConversions) ) { subConversions = subConversions(options) } - subConversions.forEach(subConversion => { - var type = _.isUndefined(subConversion.sourceType) ? 'baconjs' : subConversion.sourceType - var mapper = sourceTypes[type] - if ( _.isUndefined(mapper) ) { - console.error(`Unknown conversion type: ${type}`) - } else { - if ( _.isUndefined(subConversion.outputType) ) { - subConversion.outputType = 'to-n2k' + if ( subConversions != null ) { + subConversions.forEach(subConversion => { + var type = _.isUndefined(subConversion.sourceType) ? 'baconjs' : subConversion.sourceType + var mapper = sourceTypes[type] + if ( _.isUndefined(mapper) ) { + console.error(`Unknown conversion type: ${type}`) + } else { + if ( _.isUndefined(subConversion.outputType) ) { + subConversion.outputType = 'to-n2k' + } + mapper(subConversion, options) } - mapper(subConversion, options) - } - }) + }) + } } }) }) From 1dd043e72b1cf5f01d1d7dfcaaed99585fdaaefb Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Mon, 29 Jan 2018 19:21:05 -0500 Subject: [PATCH 21/24] chore: update conboatjs to 0.0.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d345d5b..e65b2f9 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "license": "ISC", "dependencies": { "baconjs": "^0.7.88", - "canboatjs": "0.0.1", + "canboatjs": "0.0.2", "concentrate2": "^1.0.0", "debug": "^3.1.0", "lodash": "^4.17.4" From d1b247b2a4006c2c11e227ecbe64794dc9530498 Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Mon, 29 Jan 2018 19:28:04 -0500 Subject: [PATCH 22/24] chore: rename 'baconjs' source type to 'onValueChange' --- index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 1d64b34..3cb2419 100644 --- a/index.js +++ b/index.js @@ -20,7 +20,7 @@ module.exports = function(app) { onDelta - You will get all deltas via app.signalk.on('delta', ...). Please do no use this unless absolutely necessary. - bacanjs - The conversion should specify a variable called 'keys' which is an array of the Signal K paths that the convesion needs + onValueChange - The conversion should specify a variable called 'keys' which is an array of the Signal K paths that the convesion needs timer - The conversions callback will get called per the givien 'interval' variable @@ -30,13 +30,13 @@ module.exports = function(app) { 'buffer' - The output should be a buffer that is sent directly to nmea2000out - sourceType defaults to 'bacanjs' + sourceType defaults to 'onValueChange' outputType defaults to 'to-n2k' */ var sourceTypes = { 'onDelta': mapOnDelta, - 'baconjs': mapBaconjs, + 'onValueChange': mapBaconjs, 'timer': mapTimer } @@ -106,7 +106,7 @@ module.exports = function(app) { } if ( subConversions != null ) { subConversions.forEach(subConversion => { - var type = _.isUndefined(subConversion.sourceType) ? 'baconjs' : subConversion.sourceType + var type = _.isUndefined(subConversion.sourceType) ? 'onValueChange' : subConversion.sourceType var mapper = sourceTypes[type] if ( _.isUndefined(mapper) ) { console.error(`Unknown conversion type: ${type}`) From 5d751f901826a34ded0e5e1c2330bed78a6de0a0 Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Mon, 29 Jan 2018 19:30:53 -0500 Subject: [PATCH 23/24] chore: remove test script until I get the test working --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index e65b2f9..6648e19 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,6 @@ "description": "Signal K server plugin to convert Signal K to NMEA2000", "main": "index.js", "scripts": { - "test": "mocha" }, "keywords": [ "signalk-node-server-plugin" From fd1f6a668d68b4559ab063381e76ed3b1f21d1b3 Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Mon, 29 Jan 2018 19:31:17 -0500 Subject: [PATCH 24/24] chore: checkin non working test script --- test/battery_status_127508.js | 77 +++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 test/battery_status_127508.js diff --git a/test/battery_status_127508.js b/test/battery_status_127508.js new file mode 100644 index 0000000..addaea4 --- /dev/null +++ b/test/battery_status_127508.js @@ -0,0 +1,77 @@ +const mocha = require('mocha') +const should = require('chai').should() +const sinon = require('sinon') + +const Sk2n2K = require('../') +const Server = require('signalk-server/lib/') + +'electrical.batteries.house.voltage', 'electrical.batteries.house.current', 'electrical.batteries.house.temperature' + +describe('Battery status 127508', function () { + it('sequence with some missing data works', function (done) { + const deltas = [ + { + interval: 0, + path: 'electrical.batteries.house.voltage', + value: 12 + }, + { + interval: 0, + path: 'electrical.batteries.house.current', + value: 2 + }, + { + interval: 0, + path: 'electrical.batteries.house.temperature', + value: 280 + }, + { + interval: 1100, + path: 'electrical.batteries.house.current', + value: 1 + } + ] + + const app = new Server().app + app.providerStatistics = [] + const n2kSpy = sinon.spy() + app.on('nmea2000out', n2kSpy) + setTimeout(() => { + console.log(`n2kSpy ${n2kSpy}`) + n2kSpy.callCount.should.equal(2) + // TODO assert proper fastformat output here + done() + }, 1500) + const sk2n2k = new Sk2n2K(app) + sk2n2k.start({ + BATTERYSTATUS: [ + { + signalkId: 'house', + instanceId: 1 + } + ] + }) + sendDeltas(app, deltas) + }) +}) + +function sendDeltas (app, deltas) { + let cumulativeTimeout = 0 + deltas.forEach(deltaSpec => { + cumulativeTimeout += deltaSpec.interval + setTimeout(() => { + app.handleMessage('testInput', { + updates: [ + { + values: [ + { + path: deltaSpec.path, + value: deltaSpec.value + } + ] + } + ] + }) + }, cumulativeTimeout) + }) +}