UNPKG

node-gpsd

Version:

Node.js gpsd client for GPS tracking device.

284 lines (242 loc) 9.02 kB
/******************************************************************************* * Code contributed to the webinos project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *******************************************************************************/ var spawn = require('child_process').spawn; var util = require('util'); var events = require('events'); var fs = require('fs'); var net = require('net'); var path = require('path'); var DEFAULT_PORT = 2947; function createServiceSocket(self) { let sock = new net.Socket(); sock.setEncoding('ascii'); sock.on('connect', function (socket) { self.logger.info('Socket connected.'); self.connected = true; self.partialMessages = []; self.emit('connected'); }); sock.on("data", function (payload) { const buf = Buffer.from(payload, "utf-8"); if (payload[payload.length - 1] === '\n') { // End of a possible sequence of messages. Squish all the messages together if necessary, then parse. if (self.partialMessages.length === 0) { // Short-circuit: there's no prior bytes. parseOneOrMoreCompleteMessages(buf); } else { // There are prior bytes. self.partialMessages.push(buf); parseOneOrMoreCompleteMessages(Buffer.concat(self.partialMessages)); self.partialMessages = []; } } else { // Not a complete message. Push the buffer until we know more. self.partialMessages.push(buf); } function parseOneOrMoreCompleteMessages(completeMessages) { // Payload is a Buffer, and we ideally want to do some string-splitting on it. Convert to string, therefore. var completeMessagesString = completeMessages.toString('ascii'); var stringPayload = completeMessagesString.replace(/\}\{/g, '}\n{'); var info = stringPayload.split('\n'); for (var index = 0; index < info.length; index++) { if (info[index]) { if (!self.parse) { self.emit('raw', info[index]); } else { try { var data = JSON.parse(info[index]); self.emit(data.class, data); } catch (error) { self.logger.error("Bad message format", info[index], error); self.emit('error', { message : "Bad message format", cause : info[index], error : error }); continue; } } } } } }); sock.on("close", function (err) { self.logger.info('Socket disconnected.'); self.emit('disconnected', err); self.connected = false; }); sock.on('error', function (error) { if (error.code === 'ECONNREFUSED') { self.logger.error('socket connection refused'); self.emit('error.connection'); } else { self.logger.error('socket error', error); self.emit('error.socket', error); } }); return sock; } /* Construct the listener */ function Listener(options) { this.port = DEFAULT_PORT; this.hostname = 'localhost'; this.logger = { info: function() {}, warn: console.warn, error: console.error }; this.parse = true; if (options !== undefined) { if (options.port !== undefined) this.port = options.port; if (options.hostname !== undefined) this.hostname = options.hostname; if (options.logger !== undefined) this.logger = options.logger; if (options.parse !== undefined) this.parse = options.parse; } events.EventEmitter.call(this); var self = this; this.connected = false; return (this); } util.inherits(Listener, events.EventEmitter); exports.Listener = Listener; /* connects to GPSd */ Listener.prototype.connect = function(callback) { // create or destroy the socket if (this.serviceSocket) { this.serviceSocket.destroy(); this.serviceSocket.removeAllListeners(); this.serviceSocket = null; } this.serviceSocket = createServiceSocket(this); this.serviceSocket.connect(this.port, this.hostname); if(callback !== undefined) { this.serviceSocket.once('connect', function(socket) { callback(socket); }); } }; /* disconnects from GPSd */ Listener.prototype.disconnect = function(callback) { this.unwatch(); this.serviceSocket.end(); if(callback !== undefined) { this.serviceSocket.once('close', function(err) { callback(err); }); }}; /* Checks the state of the connection */ Listener.prototype.isConnected = function() { return this.connected; }; /* Start the watching mode: see ?WATCH command */ Listener.prototype.watch = function(options) { var watch = { class: 'WATCH', json: true, nmea: false }; if (options) watch = options; this.serviceSocket.write('?WATCH=' + JSON.stringify(watch)); }; /* Stop watching */ Listener.prototype.unwatch = function() { this.serviceSocket.write('?WATCH={"class": "WATCH", "json":true, "enable":false}\n'); }; /* Send the ?VERSION command */ Listener.prototype.version = function() { this.serviceSocket.write('?VERSION;\n'); }; /* Send the ?DEVICES command */ Listener.prototype.devices = function() { this.serviceSocket.write('?DEVICES;\n'); }; /* Send the ?DEVICE command */ Listener.prototype.device = function() { this.serviceSocket.write('?DEVICE;\n'); }; function Daemon(options) { this.program = 'gpsd'; this.device = '/dev/ttyUSB0'; this.port = DEFAULT_PORT; this.pid = '/tmp/gpsd.pid'; this.readOnly = false; this.logger = { info: function() {}, warn: console.warn, error: console.error }; events.EventEmitter.call(this); if (options !== undefined) { if (options.program !== undefined) this.program = options.program; if (options.device !== undefined) this.device = options.device; if (options.port !== undefined) this.port = options.port; if (options.pid !== undefined) this.pid = options.pid; if (options.readOnly !== undefined) this.readOnly = options.readOnly; if (options.logger !== undefined) this.logger = options.logger; } this.arguments = []; /* fg process */ this.arguments.push('-N'); this.arguments.push('-P'); this.arguments.push(this.pid); this.arguments.push('-S'); this.arguments.push(this.port); this.arguments.push(this.device); if (this.readOnly) this.arguments.push('-b'); } util.inherits(Daemon, events.EventEmitter); exports.Daemon = Daemon; /* starts the daemon */ Daemon.prototype.start = function(callback) { var self = this; fs.exists(this.device, function (exists) { var p = function (callback) { if (self.gpsd === undefined) { self.logger.info('Spawning gpsd.'); self.gpsd = spawn(self.program, self.arguments); self.gpsd.on('exit', function (code) { self.logger.warn('gpsd died.'); self.gpsd = undefined; self.emit('died'); }); self.gpsd.on('error', function (err) { self.emit('error', err); }); // give the daemon a change to startup before making the callback. setTimeout(function() { if (callback !== undefined) callback.call(); }, 100); } }; if (exists) { p.apply(this, [ callback ]); } else { self.logger.info("Device not found. watching device."); fs.watchFile(self.device, function (curr, prev) { self.logger.info("device status changed."); p.apply(this, [ callback ]); }); } }); }; /* stops the daemon */ Daemon.prototype.stop = function(callback) { this.gpsd.on('exit', function (code) { if (callback !== undefined) { callback.call(); } }); if (this.gpsd !== undefined) { this.gpsd.kill(); } };