├── .gitignore ├── examples ├── raw.js ├── watch.js ├── version.js └── devices.js ├── package.json ├── README.md └── lib └── gpsd.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.tgz 2 | npm-debug.log 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /examples/raw.js: -------------------------------------------------------------------------------- 1 | var gpsd = require('../lib/gpsd'); 2 | 3 | var listener = new gpsd.Listener({ 4 | port: 2947, 5 | hostname: 'localhost', 6 | logger: { 7 | info: function() {}, 8 | warn: console.warn, 9 | error: console.error 10 | }, 11 | parse: false 12 | }); 13 | 14 | listener.connect(function() { 15 | console.log('Connected'); 16 | }); 17 | 18 | //not going to happen, parse is false 19 | listener.on('TPV', function(data) { 20 | console.log(data); 21 | }); 22 | 23 | // parse is false, so raw data get emitted. 24 | listener.on('raw', function(data) { 25 | console.log(data); 26 | }); 27 | 28 | listener.watch({class: 'WATCH', nmea: true}); 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-gpsd", 3 | "description": "Node.js gpsd client for GPS tracking device.", 4 | "version": "0.3.4", 5 | "keywords": ["gps", "gpsd", "location", "tracking", "position"], 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/eelcocramer/node-gpsd.git" 9 | }, 10 | "homepage": "http://github.com/eelcocramer/node-gpsd.git", 11 | "bugs": { 12 | "url": "http://github.com/eelcocramer/node-gpsd/issues" 13 | }, 14 | "main": "./lib/gpsd", 15 | "engines": { "node": ">=v0.8.0" }, 16 | "dependencies": { }, 17 | "devDependencies": { }, 18 | "contributors": [ 19 | "Eelco Cramer", 20 | "Pascal Deschenes", 21 | "Jeffrey Yang" 22 | ], 23 | "maintainers": [{"name": "Eelco Cramer" 24 | ,"email": "eelco@servicelab.org" 25 | ,"web": "http://www.servicelab.org" 26 | }], 27 | "licences": [{"type": "Apache"}] 28 | } 29 | -------------------------------------------------------------------------------- /examples/watch.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Code contributed to the webinos project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | *******************************************************************************/ 17 | 18 | var gpsd = require('../lib/gpsd.js'); 19 | 20 | var daemon = new gpsd.Daemon({ 21 | program: '/usr/local/sbin/gpsd', 22 | device: '/dev/tty.usbserial', 23 | verbose: true 24 | }); 25 | 26 | daemon.start(function() { 27 | var listener = new gpsd.Listener(); 28 | 29 | listener.on('TPV', function (tpv) { 30 | console.log(tpv); 31 | }); 32 | 33 | listener.connect(function() { 34 | listener.watch(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /examples/version.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Code contributed to the webinos project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | *******************************************************************************/ 17 | 18 | var gpsd = require('../lib/gpsd.js'); 19 | 20 | var daemon = new gpsd.Daemon({ 21 | program: '/usr/local/sbin/gpsd', 22 | device: '/dev/tty.usbserial' 23 | }); 24 | 25 | daemon.start(function() { 26 | console.log('started'); 27 | 28 | var listener = new gpsd.Listener(); 29 | var count = 0; 30 | 31 | listener.logger = console; 32 | 33 | listener.on('VERSION', function (version) { 34 | console.log(version); 35 | count++; 36 | 37 | if (count >= 2) { 38 | listener.disconnect(); 39 | daemon.stop(); 40 | } 41 | }); 42 | 43 | listener.connect(function () { 44 | console.log('connected'); 45 | listener.version(); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /examples/devices.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Code contributed to the webinos project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | *******************************************************************************/ 17 | 18 | var gpsd = require('../lib/gpsd.js'); 19 | 20 | var daemon = new gpsd.Daemon({ 21 | program: '/usr/local/sbin/gpsd', 22 | device: '/dev/tty.usbserial' 23 | }); 24 | 25 | daemon.logger = console; 26 | 27 | daemon.start(function() { 28 | console.log('started'); 29 | 30 | var listener = new gpsd.Listener(); 31 | 32 | listener.logger = console; 33 | 34 | listener.on('DEVICE', function (device) { 35 | console.log(device); 36 | 37 | listener.disconnect(); 38 | daemon.stop(); 39 | }); 40 | 41 | listener.on('DEVICES', function (devices) { 42 | console.log(devices); 43 | listener.device(); 44 | }); 45 | 46 | listener.connect(function () { 47 | console.log('connected'); 48 | listener.devices(); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-gpsd 2 | 3 | Interface to [gpsd](http://www.catb.org/gpsd/). 4 | 5 | ## Installation 6 | 7 | With package manager [npm](http://npmjs.org/): 8 | 9 | npm install node-gpsd 10 | 11 | ## Code instructions 12 | 13 | Require `node-gpsd` by calling: 14 | 15 | ```javascript 16 | var gpsd = require('node-gpsd'); 17 | ``` 18 | 19 | `node-gpsd` has 2 classes: `Daemon` and `Listener`. 20 | 21 | The `Daemon` is a wrapper to start and stop `gpsd` from your program. The `Listener` interfaces with a running `gpsd` (not necessarily instantiated via the `Daemon` class). 22 | 23 | #### Deamon 24 | 25 | A `Daemon` is instantiated by calling: 26 | 27 | ```javascript 28 | var daemon = new gpsd.Daemon({ 29 | program: 'gpsd', 30 | device: '/dev/ttyUSB0', 31 | port: 2947, 32 | pid: '/tmp/gpsd.pid', 33 | readOnly: false, 34 | logger: { 35 | info: function() {}, 36 | warn: console.warn, 37 | error: console.error 38 | } 39 | }); 40 | ``` 41 | 42 | The options that are listed above are the default values so calling `new gpsd.Daemon()` will have the same effect. Change the options according your own setup. 43 | 44 | The `Daemon` can be started and stopped by calling the appropriate methods: 45 | 46 | ```javascript 47 | daemon.start(function() { 48 | console.log('Started'); 49 | }); 50 | ``` 51 | 52 | or: 53 | 54 | ```javascript 55 | daemon.stop(function() { 56 | console.log('Stopped'); 57 | }); 58 | ``` 59 | 60 | The `Daemon` can log to the console if needed. Logging can be controlled by passing a `logger` property in the options when creating the `Daemon` or by setting the logger field: 61 | 62 | ```javascript 63 | daemon.logger = new (winston.Logger) ({ exitOnError: false }); 64 | ``` 65 | 66 | The logger should have `info`, `warn` and `error` functions that all accept a single parameter. 67 | 68 | The `Daemon` is an [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter) and will emit the following events: 69 | 70 | * `died`: when the `Daemon` is killed. 71 | 72 | #### Listener 73 | 74 | A `Listener` is instantiated by calling: 75 | 76 | ```javascript 77 | var listener = new gpsd.Listener({ 78 | port: 2947, 79 | hostname: 'localhost', 80 | logger: { 81 | info: function() {}, 82 | warn: console.warn, 83 | error: console.error 84 | }, 85 | parse: true 86 | }); 87 | ``` 88 | 89 | The options that are listed above are the default values so calling `new gpsd.Listener()` will have the same effect. Change the options according your own setup. 90 | 91 | The `Listener` can be connected to the `gpsd` by calling: 92 | 93 | ```javascript 94 | listener.connect(function() { 95 | console.log('Connected'); 96 | }); 97 | ``` 98 | 99 | and disconnected by calling: 100 | 101 | ```javascript 102 | listener.disconnect(function() { 103 | console.log('Disconnected'); 104 | }); 105 | ``` 106 | 107 | The connection state can be queries by calling: 108 | 109 | ```javascript 110 | listener.isConnected(); 111 | ``` 112 | 113 | To control watching gps events call the methods: 114 | 115 | ```javascript 116 | listener.watch(options); 117 | listener.unwatch(); 118 | ``` 119 | 120 | This will put the `Listener` in and out-of watching mode. The `Listener` is an [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter) and will emit the following events: 121 | 122 | * `gpsd` events like described in the [gpsd documentation](http://www.catb.org/gpsd/gpsd_json.html). All `gpsd` events like: `TPV`, `SKY`, `INFO` and `DEVICE` can be emitted. To receive all `TPV` events just add `listener.on('TPV', function(tpvData))` to your code. When the `parse` option is set to false these events will not be emitted. 123 | * `raw` events contain the raw, unparsed input received from gpsd. Only emitted if `parse` option is set to false. 124 | * `error` when data in a bad format is received from `gpsd`. 125 | * `disconnected` when the connection with `gpsd` is lost. 126 | * `connected` when the connection with `gpsd` is established. 127 | * `error.connection` when the connection is refused. 128 | * `error.socket` on other connection errors. 129 | 130 | You can pass options to be sent on to gpsd when issuing the watch command, the default being `{ class: 'WATCH', json: true, nmea: false }`. 131 | 132 | If you want to receive raw nmea data from gpsd you should create the listener with `new gpsd.Listener({emitraw: true, parsejson: false})` and issue `listener.watch({class: 'WATCH', nmea: true})`. 133 | 134 | It is possible to query the gps device by calling: 135 | 136 | ```javascript 137 | listener.version(); /* a INFO event will be emitted */ 138 | listener.devices(); /* a DEVICES event will be emitted */ 139 | listener.device(); /* a DEVICE event will be emitted */ 140 | ``` 141 | 142 | The `Listener` can log to the console if needed. Logging can be controlled by passing a `logger` property in the options when creating the `Listener` or by setting the logger field: 143 | 144 | ```javascript 145 | listener.logger = new (winston.Logger) ({ exitOnError: false });; 146 | ``` 147 | 148 | ## Shout outs 149 | 150 | Shout outs go to [Pascal Deschenes](http://github.com/pdeschen) for creating the [Bancroft](http://github.com/pdeschen/bancroft) project that formed the basis for `node-gpsd`. 151 | -------------------------------------------------------------------------------- /lib/gpsd.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Code contributed to the webinos project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | *******************************************************************************/ 17 | 18 | var spawn = require('child_process').spawn; 19 | var util = require('util'); 20 | var events = require('events'); 21 | var fs = require('fs'); 22 | var net = require('net'); 23 | var path = require('path'); 24 | 25 | var DEFAULT_PORT = 2947; 26 | 27 | function createServiceSocket(self) { 28 | let sock = new net.Socket(); 29 | sock.setEncoding('ascii'); 30 | 31 | sock.on('connect', function (socket) { 32 | self.logger.info('Socket connected.'); 33 | self.connected = true; 34 | self.partialMessages = []; 35 | self.emit('connected'); 36 | }); 37 | 38 | sock.on("data", function (payload) { 39 | const buf = Buffer.from(payload, "utf-8"); 40 | if (payload[payload.length - 1] === '\n') { 41 | // End of a possible sequence of messages. Squish all the messages together if necessary, then parse. 42 | if (self.partialMessages.length === 0) { 43 | // Short-circuit: there's no prior bytes. 44 | parseOneOrMoreCompleteMessages(buf); 45 | } else { 46 | // There are prior bytes. 47 | self.partialMessages.push(buf); 48 | parseOneOrMoreCompleteMessages(Buffer.concat(self.partialMessages)); 49 | self.partialMessages = []; 50 | } 51 | } else { 52 | // Not a complete message. Push the buffer until we know more. 53 | self.partialMessages.push(buf); 54 | } 55 | 56 | function parseOneOrMoreCompleteMessages(completeMessages) { 57 | // Payload is a Buffer, and we ideally want to do some string-splitting on it. Convert to string, therefore. 58 | var completeMessagesString = completeMessages.toString('ascii'); 59 | var stringPayload = completeMessagesString.replace(/\}\{/g, '}\n{'); 60 | var info = stringPayload.split('\n'); 61 | 62 | for (var index = 0; index < info.length; index++) { 63 | if (info[index]) { 64 | if (!self.parse) { 65 | self.emit('raw', info[index]); 66 | } else { 67 | try { 68 | var data = JSON.parse(info[index]); 69 | self.emit(data.class, data); 70 | } catch (error) { 71 | self.logger.error("Bad message format", info[index], error); 72 | self.emit('error', { 73 | message : "Bad message format", 74 | cause : info[index], 75 | error : error 76 | }); 77 | 78 | continue; 79 | } 80 | } 81 | } 82 | } 83 | } 84 | }); 85 | 86 | sock.on("close", function (err) { 87 | self.logger.info('Socket disconnected.'); 88 | self.emit('disconnected', err); 89 | self.connected = false; 90 | }); 91 | 92 | sock.on('error', function (error) { 93 | if (error.code === 'ECONNREFUSED') { 94 | self.logger.error('socket connection refused'); 95 | self.emit('error.connection'); 96 | } else { 97 | self.logger.error('socket error', error); 98 | self.emit('error.socket', error); 99 | } 100 | }); 101 | 102 | return sock; 103 | } 104 | 105 | /* Construct the listener */ 106 | function Listener(options) { 107 | this.port = DEFAULT_PORT; 108 | this.hostname = 'localhost'; 109 | this.logger = { 110 | info: function() {}, 111 | warn: console.warn, 112 | error: console.error 113 | }; 114 | this.parse = true; 115 | 116 | if (options !== undefined) { 117 | if (options.port !== undefined) this.port = options.port; 118 | if (options.hostname !== undefined) this.hostname = options.hostname; 119 | if (options.logger !== undefined) this.logger = options.logger; 120 | if (options.parse !== undefined) this.parse = options.parse; 121 | } 122 | 123 | events.EventEmitter.call(this); 124 | 125 | var self = this; 126 | this.connected = false; 127 | 128 | return (this); 129 | } 130 | 131 | util.inherits(Listener, events.EventEmitter); 132 | exports.Listener = Listener; 133 | 134 | /* connects to GPSd */ 135 | Listener.prototype.connect = function(callback) { 136 | // create or destroy the socket 137 | if (this.serviceSocket) { 138 | this.serviceSocket.destroy(); 139 | this.serviceSocket.removeAllListeners(); 140 | this.serviceSocket = null; 141 | } 142 | this.serviceSocket = createServiceSocket(this); 143 | this.serviceSocket.connect(this.port, this.hostname); 144 | 145 | if(callback !== undefined) { 146 | this.serviceSocket.once('connect', function(socket) { 147 | callback(socket); 148 | }); 149 | } 150 | }; 151 | 152 | /* disconnects from GPSd */ 153 | Listener.prototype.disconnect = function(callback) { 154 | this.unwatch(); 155 | 156 | this.serviceSocket.end(); 157 | 158 | if(callback !== undefined) { 159 | this.serviceSocket.once('close', function(err) { 160 | callback(err); 161 | }); 162 | }}; 163 | 164 | /* Checks the state of the connection */ 165 | Listener.prototype.isConnected = function() { 166 | return this.connected; 167 | }; 168 | 169 | /* Start the watching mode: see ?WATCH command */ 170 | Listener.prototype.watch = function(options) { 171 | var watch = { class: 'WATCH', json: true, nmea: false }; 172 | if (options) watch = options; 173 | this.serviceSocket.write('?WATCH=' + JSON.stringify(watch)); 174 | }; 175 | 176 | /* Stop watching */ 177 | Listener.prototype.unwatch = function() { 178 | this.serviceSocket.write('?WATCH={"class": "WATCH", "json":true, "enable":false}\n'); 179 | }; 180 | 181 | /* Send the ?VERSION command */ 182 | Listener.prototype.version = function() { 183 | this.serviceSocket.write('?VERSION;\n'); 184 | }; 185 | 186 | /* Send the ?DEVICES command */ 187 | Listener.prototype.devices = function() { 188 | this.serviceSocket.write('?DEVICES;\n'); 189 | }; 190 | 191 | /* Send the ?DEVICE command */ 192 | Listener.prototype.device = function() { 193 | this.serviceSocket.write('?DEVICE;\n'); 194 | }; 195 | 196 | function Daemon(options) { 197 | this.program = 'gpsd'; 198 | this.device = '/dev/ttyUSB0'; 199 | this.port = DEFAULT_PORT; 200 | this.pid = '/tmp/gpsd.pid'; 201 | this.readOnly = false; 202 | this.logger = { 203 | info: function() {}, 204 | warn: console.warn, 205 | error: console.error 206 | }; 207 | 208 | events.EventEmitter.call(this); 209 | 210 | if (options !== undefined) { 211 | if (options.program !== undefined) this.program = options.program; 212 | if (options.device !== undefined) this.device = options.device; 213 | if (options.port !== undefined) this.port = options.port; 214 | if (options.pid !== undefined) this.pid = options.pid; 215 | if (options.readOnly !== undefined) this.readOnly = options.readOnly; 216 | if (options.logger !== undefined) this.logger = options.logger; 217 | } 218 | 219 | this.arguments = []; 220 | /* fg process */ 221 | this.arguments.push('-N'); 222 | this.arguments.push('-P'); 223 | this.arguments.push(this.pid); 224 | this.arguments.push('-S'); 225 | this.arguments.push(this.port); 226 | this.arguments.push(this.device); 227 | if (this.readOnly) this.arguments.push('-b'); 228 | } 229 | 230 | util.inherits(Daemon, events.EventEmitter); 231 | exports.Daemon = Daemon; 232 | 233 | /* starts the daemon */ 234 | Daemon.prototype.start = function(callback) { 235 | var self = this; 236 | 237 | fs.exists(this.device, function (exists) { 238 | var p = function (callback) { 239 | if (self.gpsd === undefined) { 240 | self.logger.info('Spawning gpsd.'); 241 | self.gpsd = spawn(self.program, self.arguments); 242 | 243 | self.gpsd.on('exit', function (code) { 244 | self.logger.warn('gpsd died.'); 245 | self.gpsd = undefined; 246 | self.emit('died'); 247 | }); 248 | 249 | self.gpsd.on('error', function (err) { 250 | self.emit('error', err); 251 | }); 252 | 253 | // give the daemon a change to startup before making the callback. 254 | setTimeout(function() { 255 | if (callback !== undefined) callback.call(); 256 | }, 100); 257 | } 258 | }; 259 | 260 | if (exists) { 261 | p.apply(this, [ callback ]); 262 | } else { 263 | self.logger.info("Device not found. watching device."); 264 | fs.watchFile(self.device, function (curr, prev) { 265 | self.logger.info("device status changed."); 266 | p.apply(this, [ callback ]); 267 | }); 268 | } 269 | }); 270 | }; 271 | 272 | /* stops the daemon */ 273 | Daemon.prototype.stop = function(callback) { 274 | this.gpsd.on('exit', function (code) { 275 | if (callback !== undefined) { 276 | callback.call(); 277 | } 278 | }); 279 | 280 | if (this.gpsd !== undefined) { 281 | this.gpsd.kill(); 282 | } 283 | }; 284 | --------------------------------------------------------------------------------