UNPKG

10.1 kBJavaScriptView Raw
1var events = require("events");
2var util = require("util");
3
4var Board = require("./board");
5var Fn = require("./fn");
6var Pin = require("./pin");
7
8var toFixed = Fn.toFixed;
9
10
11var priv = new Map();
12
13var Breakouts = {
14
15 /*
16 * https://www.adafruit.com/products/746
17 */
18 ADAFRUIT_ULTIMATE_GPS: {
19 receiver: {
20 value: "FGPMMOPA6H"
21 }
22 }
23
24};
25
26// GPS Antenna Modules
27var Receivers = {
28
29 /*
30 * http://www.gtop-tech.com/en/product/LadyBird-1-PA6H/MT3339_GPS_Module_04.html
31 */
32 FGPMMOPA6H: {
33 // Later, when we add logging that code will go here
34 chip: {
35 value: "MT3339"
36 }
37 }
38
39};
40
41// GPS chips
42var Chips = {
43
44 DEFAULT: {
45 baud: {
46 value: 9600,
47 writable: true
48 },
49 configure: {
50 value: function(callback) {
51 process.nextTick(callback);
52 }
53 }
54 },
55
56 /*
57 * http://www.mediatek.com/en/products/connectivity/gps/mt3339/
58 */
59 MT3339: {
60 baud: {
61 value: 9600,
62 writable: true
63 },
64 configure: {
65 value: function(callback) {
66 process.nextTick(callback);
67 }
68 },
69 frequency: {
70 get: function() {
71 var state = priv.get(this);
72 return state.frequency;
73 },
74 set: function(frequency) {
75 var state = priv.get(this);
76
77 // Enforce maximum frequency of 10hz
78 if (frequency < 10) {
79 frequency = 10;
80 }
81
82 state.frequency = frequency;
83 this.sendCommand("$PMTK220," + String(1000 / state.frequency));
84 }
85 },
86 restart: {
87 // Reboot the receiver
88 value: function(coldRestart) {
89
90 if (coldRestart === true) {
91 this.sendCommand("$PMTK103");
92 } else {
93 this.sendCommand("$PMTK101");
94 setTimeout(function() {
95 this.sendCommand("");
96 }.bind(this), 1000);
97 }
98
99 }
100 }
101 }
102
103};
104
105
106/**
107 *
108 * @constructor
109 *
110 * @param {Object} opts Options: pin(s), chip, receiver, breakout, fixed, serialport, frequency
111 *
112 * Sample initialization
113 *
114 * new five.GPS({ pins: {rx: 10, tx: 11});
115 *
116 */
117
118function GPS(opts) {
119
120 var breakout, receiver, chip, state;
121
122 if (!(this instanceof GPS)) {
123 return new GPS(opts);
124 }
125
126 // Allow users to pass in a 2 element array for rx and tx pins
127 if (Array.isArray(opts)) {
128 opts = {
129 pins: {
130 rx: opts[0],
131 tx: opts[1],
132 onOff: opts[2]
133 }
134 };
135 }
136
137 if (typeof opts.pins === "undefined") {
138 opts.pins = {};
139 }
140
141 Board.Component.call(
142 this, opts = Board.Options(opts)
143 );
144
145
146
147 // Get user values for breakout, receiver and chip
148 breakout = opts.breakout || {};
149 receiver = opts.receiver;
150 chip = opts.chip;
151
152 // If a breakout is defined check for receiver and chip
153 if (Breakouts[breakout]) {
154 if (!receiver && Breakouts[breakout].receiver) {
155 receiver = Breakouts[breakout].receiver.value;
156 }
157
158 if (!chip && Breakouts[breakout].chip) {
159 chip = Breakouts[breakout].chip.value;
160 }
161 }
162
163 // If a receiver was defined or derived but chip was not
164 if (!chip) {
165 if (receiver && Receivers[receiver].chip) {
166 chip = Receivers[receiver].chip.value;
167 } else {
168 chip = "DEFAULT";
169 }
170 }
171
172 // Allow users to pass in custom chip types
173 chip = typeof chip === "string" ?
174 Chips[chip] : opts.chip;
175
176 // Allow users to pass in custom receiver types
177 receiver = typeof receiver === "string" ?
178 Receivers[receiver] : opts.receiver;
179
180 // Chip decorates the instance
181 Object.defineProperties(this, chip);
182
183 // Receiver decorates this instance
184 if (receiver) {
185 Object.defineProperties(this, receiver);
186 }
187
188 // breakout decorates the instance
189 if (opts.breakout) {
190 breakout = typeof opts.breakout === "string" ?
191 Breakouts[opts.breakout] : opts.breakout;
192
193 Board.Controller.call(this, breakout, opts);
194 }
195
196 // If necessary set default property values
197 this.fixed = opts.fixed || 6;
198 this.baud = opts.baud || this.baud;
199
200 // Create a "state" entry for privately
201 // storing the state of the instance
202 state = {
203 sat: {},
204 latitude: 0.0,
205 longitude: 0.0,
206 altitude: 0.0,
207 speed: 0.0,
208 course: 0.0,
209 frequency: 1,
210 lowPowerMode: false
211 };
212
213 priv.set(this, state);
214
215 // Getters for private state values
216 Object.defineProperties(this, {
217 latitude: {
218 get: function() {
219 return state.latitude;
220 }
221 },
222 longitude: {
223 get: function() {
224 return state.longitude;
225 }
226 },
227 altitude: {
228 get: function() {
229 return state.altitude;
230 }
231 },
232 sat: {
233 get: function() {
234 return state.sat;
235 }
236 },
237 speed: {
238 get: function() {
239 return state.speed;
240 }
241 },
242 course: {
243 get: function() {
244 return state.course;
245 }
246 },
247 time: {
248 get: function() {
249 return state.time;
250 }
251 }
252 });
253
254 if (this.initialize) {
255 this.initialize(opts);
256 }
257
258}
259
260util.inherits(GPS, events.EventEmitter);
261
262/*
263 * Default intialization for serial GPS
264 */
265GPS.prototype.initialize = function(opts) {
266
267 var state = priv.get(this);
268 state.portId = opts.serialPort || opts.portId || opts.port || opts.bus || this.io.SERIAL_PORT_IDs.DEFAULT;
269
270 // Set the pin modes
271 ["tx", "rx"].forEach(function(pin) {
272 if (this.pins[pin]) {
273 this.io.pinMode(this.pins[pin], this.io.MODES.SERIAL);
274 }
275 }, this);
276
277 if (this.pins.onOff) {
278 this.io.pinMode(this.pins.onOff, this.io.MODES.OUTPUT);
279 this.onOff = new Pin(this.pins.onOff);
280 }
281
282 this.io.serialConfig({
283 portId: state.portId,
284 baud: this.baud,
285 rxPin: this.pins.rx,
286 txPin: this.pins.tx
287 });
288
289 if (this.configure) {
290 this.configure(function() {
291 this.listen();
292 if (opts.frequency) {
293 this.frequency = opts.frequency;
294 }
295 }.bind(this));
296 }
297
298};
299
300GPS.prototype.sendCommand = function(string) {
301
302 var state = priv.get(this);
303 var cc = [];
304
305 // Convert the string to a charCode array
306 for (var i = 0; i < string.length; ++i) {
307 cc[i] = string.charCodeAt(i);
308 }
309
310 // Append *, checksum and cr/lf
311 var hexsum = getNmeaChecksum(string.substring(1));
312 cc.push(42, hexsum.charCodeAt(0), hexsum.charCodeAt(1), 13, 10);
313
314 this.io.serialWrite(state.portId, cc);
315};
316
317GPS.prototype.listen = function() {
318
319 var state = priv.get(this);
320 var input = "";
321
322 // Start the read loop
323 this.io.serialRead(state.portId, function(data) {
324
325 input += new Buffer(data).toString("ascii");
326 var sentences = input.split("\r\n");
327
328 if (sentences.length > 1) {
329 for (var i = 0; i < sentences.length - 1; i++) {
330 this.parseNmeaSentence(sentences[i]);
331 }
332 input = sentences[sentences.length - 1];
333 }
334 }.bind(this));
335};
336
337/*
338 * NMEA Sentence Information
339 * http://aprs.gids.nl/nmea
340 */
341GPS.prototype.parseNmeaSentence = function(sentence) {
342
343 var state = priv.get(this);
344 var cksum = sentence.split("*");
345
346 // Check for valid sentence
347 if (cksum[1] !== getNmeaChecksum(cksum[0].substring(1))) {
348 return;
349 }
350
351 this.emit("sentence", sentence);
352
353 var segments = cksum[0].split(",");
354 var last = {
355 latitude: state.latitude,
356 longitude: state.longitude,
357 altitude: state.altitude,
358 speed: state.speed,
359 course: state.course
360 };
361
362 switch (segments[0]) {
363 case "$GPGGA":
364 // Time, position and fix related data
365 state.time = segments[1];
366 state.latitude = degToDec(segments[2], 2, segments[3], this.fixed);
367 state.longitude = degToDec(segments[4], 3, segments[5], this.fixed);
368 state.altitude = Number(segments[9]);
369 break;
370
371 case "$GPGSA":
372 // Operating details
373 state.sat.satellites = segments.slice(3, 15);
374 state.sat.pdop = Number(segments[15]);
375 state.sat.hdop = Number(segments[16]);
376 state.sat.vdop = Number(segments[17]);
377 this.emit("operations", sentence);
378 break;
379
380 case "$GPRMC":
381 // GPS & Transit data
382 state.time = segments[1];
383 state.latitude = degToDec(segments[3], 2, segments[4], this.fixed);
384 state.longitude = degToDec(segments[5], 3, segments[6], this.fixed);
385 state.course = Number(segments[8]);
386 state.speed = toFixed(segments[7] * 0.514444, this.fixed);
387 break;
388
389 case "$GPVTG":
390 // Track Made Good and Ground Speed
391 state.course = Number(segments[1]);
392 state.speed = toFixed(segments[5] * 0.514444, this.fixed);
393 break;
394
395 case "$GPGSV":
396 // Satellites in view
397 break;
398
399 case "$PGACK":
400 // Acknowledge command
401 this.emit("acknowledge", sentence);
402 break;
403
404 default:
405 this.emit("unknown", sentence);
406 break;
407 }
408
409 this.emit("data", {
410 latitude: state.latitude,
411 longitude: state.longitude,
412 altitude: state.altitude,
413 speed: state.speed,
414 course: state.course,
415 sat: state.sat,
416 time: state.time
417 });
418
419 if (last.latitude !== state.latitude ||
420 last.longitude !== state.longitude ||
421 last.altitude !== state.altitude) {
422
423 this.emit("change", {
424 latitude: state.latitude,
425 longitude: state.longitude,
426 altitude: state.altitude
427 });
428 }
429
430 if (last.speed !== state.speed ||
431 last.course !== state.course) {
432
433 this.emit("navigation", {
434 speed: state.speed,
435 course: state.course
436 });
437 }
438
439};
440
441// Convert Lat or Lng to decimal degrees
442function degToDec(degrees, intDigitsLength, cardinal, fixed) {
443 if (degrees) {
444 var decimal = Number(degrees.substring(0, intDigitsLength)) + Number(degrees.substring(intDigitsLength)) / 60;
445
446 if (cardinal === "S" || cardinal === "W") {
447 decimal *= -1;
448 }
449 return Number(decimal.toFixed(fixed));
450 } else {
451 return 0;
452 }
453}
454
455function getNmeaChecksum(string) {
456 var cksum = 0x00;
457 for (var i = 0; i < string.length; ++i) {
458 cksum ^= string.charCodeAt(i);
459 }
460 cksum = cksum.toString(16).toUpperCase();
461
462 if (cksum.length < 2) {
463 cksum = ("00" + cksum).slice(-2);
464 }
465
466 return cksum;
467}
468
469/* istanbul ignore else */
470if (!!process.env.IS_TEST_MODE) {
471 GPS.Breakouts = Breakouts;
472 GPS.Chips = Chips;
473 GPS.Receivers = Receivers;
474 GPS.purge = function() {
475 priv.clear();
476 };
477}
478module.exports = GPS;