UNPKG

16.2 kBJavaScriptView Raw
1var tessel = require('tessel');
2var bglib = require('bglib');
3var uuid = require('./uuid');
4var attributes = require('./attributes.json');
5var events = require('events');
6var util = require('util');
7
8
9var DEBUG = 0;
10
11var __firstHandle = 0x0001;
12var __lastHandle = 0xFFFF;
13
14function Messenger(hardware) {
15
16 this.bgLib = new bglib();
17
18 this.bgLib.setPacketMode(1);
19
20 // Initialize UART
21 this.uart = hardware.UART({baudrate:9600});
22
23 // Start listening for UART data
24 this.uart.on('data', this.parseIncomingPackets.bind(this));
25
26 // Set the reset pin
27 this.resetPin = hardware.gpio(3);
28
29 // Array of packets awaiting a response
30 this.awaitingResponse = [];
31
32 // Amount of time to wait before a packet timeout
33 this.commandPacketTimeoutDuration = 60000;
34
35 // Reset the module to clear any possible comms issues
36 // Should call 'booted' event after reset
37 this.reset();
38}
39
40util.inherits(Messenger, events.EventEmitter);
41
42Messenger.prototype.connectTimeout = function() {
43 // If it gets fired
44 setImmediate(function() {
45 // Emit an error
46 this.emit('error', new Error("Unable to connect to module."));
47 }.bind(this));
48}
49
50Messenger.prototype.bootSequence = function(connectTimeout, callback) {
51
52 // Send data to the module to ensure we have comms
53 this.verifyCommunication( function(err) {
54
55 // Clear the timeout when we get a response
56 clearTimeout(connectTimeout);
57
58 callback && callback(err);
59 // If there was a problem
60 if (err) {
61 // Report the error
62 setImmediate(function() {
63 this.emit('error', err);
64 }.bind(this));
65 }
66 // If not
67 else {
68 // Report that we are ready
69 setImmediate(function() {
70 this.emit('ready');
71 }.bind(this));
72 }
73 }.bind(this));
74
75 // Temporary hack until #179 is fixed
76 this.removeAllListeners('booted');
77}
78
79Messenger.prototype.reset = function(callback) {
80 // Make a timeout for connecting to the module
81 var connectTimeout = setTimeout(this.connectTimeout.bind(this), 5000);
82
83 // When we've booted ensure we can communicate
84 this.once('booted', this.bootSequence.bind(this, connectTimeout, callback));
85
86 this.resetPin.output().low();
87 this.resetPin.high();
88}
89
90Messenger.prototype.verifyCommunication = function(callback) {
91 this.execute(bglib.api.systemHello, callback);
92}
93
94Messenger.prototype.startScanning = function(callback) {
95 this.execute(bglib.api.gapDiscover, [this.bgLib.GAPDiscoverMode.gap_discover_generic], callback);
96}
97
98Messenger.prototype.stopScanning = function(callback) {
99 this.execute(bglib.api.gapEndProcedure, callback);
100}
101
102Messenger.prototype.connect = function(address, addressType, callback) {
103 // params: address, address_type, conn_interval_min, conn_interval_max, timeout, latency
104 // TODO: Make last four params configurable?
105 this.execute(bglib.api.gapConnectDirect, [address, address_type, 1000, 2000, 3200, 0], callback);
106}
107Messenger.prototype.disconnect = function(connection, callback) {
108 // params: address, address_type, conn_interval_min, conn_interval_max, timeout, latency
109 // TODO: Make last four params configurable?
110 this.execute(bglib.api.connectionDisconnect, [connection], callback);
111}
112
113Messenger.prototype.discoverServices = function(peripheral, callback) {
114 this.execute(bglib.api.attClientReadByGroupType, [peripheral.connection, __firstHandle, __lastHandle, [0x00, 0x28]], callback);
115}
116
117Messenger.prototype.discoverAllAttributes = function(peripheral, callback) {
118 this.execute(bglib.api.attClientFindInformation, [peripheral.connection, __firstHandle, __lastHandle], callback);
119}
120
121Messenger.prototype.discoverCharacteristicsInRangeForUUID = function(peripheral, startHandle, endHandle, uuid, callback) {
122 this.execute(bglib.api.attClientReadByType, [peripheral.connection, startHandle, endHandle, uuid.toBuffer()], callback);
123}
124
125Messenger.prototype.discoverCharacteristicUUID = function(characteristic, callback) {
126 this.execute(bglib.api.attClientFindInformation, [characteristic._peripheral.connection, characteristic.handle, characteristic.handle + 1], callback);
127}
128
129Messenger.prototype.discoverCharacteristicsInRange = function(peripheral, startHandle, endHandle, callback) {
130 this.execute(bglib.api.attClientFindInformation, [peripheral.connection, startHandle, endHandle], callback);
131}
132
133Messenger.prototype.findHandle = function(peripheral, offset, callback) {
134 this.execute(bglib.api.attClientFindInformation, [peripheral.connection, offset, offset+1], callback);
135}
136
137Messenger.prototype.readCharacteristicValue = function(characteristic, callback) {
138 this.execute(bglib.api.attClientReadLong, [characteristic._peripheral.connection, characteristic.handle], callback);
139}
140
141Messenger.prototype.writeAttributeImmediately = function(attribute, buf, callback) {
142 this.execute(bglib.api.attClientAttributeWrite, [attribute._peripheral.connection, attribute.handle, buf], callback);
143}
144
145Messenger.prototype.prepareWrite = function(attribute, buf, offset, callback) {
146 this.execute(bglib.api.attClientPrepareWrite, [attribute._peripheral.connection, attribute.handle, offset, buf], callback);
147}
148
149Messenger.prototype.executeWrite = function(attribute, callback) {
150 this.execute(bglib.api.attClientExecuteWrite, [attribute._peripheral.connection, 1], callback);
151}
152
153Messenger.prototype.cancelWrite = function(attribute, callback) {
154 this.execute(bglib.api.attClientExecuteWrite, [attribute._peripheral.connection, 0], callback);
155}
156
157Messenger.prototype.readHandle = function(peripheral, attribute, callback) {
158 this.execute(bglib.api.attClientReadLong, [peripheral.connection, attribute.handle], callback);
159}
160
161Messenger.prototype.confirmIndication = function(characteristic, callback) {
162 this.execute(bglib.api.attClientIndicateConfirm, [characteristic._peripheral.connection], callback);
163}
164
165Messenger.prototype.updateRssi = function(peripheral, callback) {
166 this.execute(bglib.api.connectionGetRSSI, [peripheral.connection], callback);
167}
168
169Messenger.prototype.getAddress = function(callback) {
170 this.execute(bglib.api.systemAddressGet, [], callback);
171}
172
173Messenger.prototype.getMaxConnections = function(callback) {
174 this.execute(bglib.api.systemGetConnections, [], callback);
175}
176
177
178Messenger.prototype.startAdvertising = function(callback) {
179 this.execute(bglib.api.gapSetMode, [this.bgLib.GAPDiscoverableModes.gap_general_discoverable, this.bgLib.GAPConnectableMode.gap_directed_connectable], callback);
180}
181
182Messenger.prototype.stopAdvertising = function(callback) {
183 this.execute(bglib.api.gapSetMode, [this.bgLib.GAPDiscoverableModes.gap_non_discoverable, this.bgLib.GAPConnectableMode.gap_non_connectable], callback);
184}
185
186Messenger.prototype.setAdvertisementData = function(advOrScan, data, callback) {
187 this.execute(bglib.api.gapSetAdvData, [advOrScan, data], callback);
188}
189
190Messenger.prototype.writeLocalValue = function(handle, value, callback) {
191 this.execute(bglib.api.attributesWrite, [handle, 0, value], callback);
192}
193
194Messenger.prototype.readLocalValue = function(handle, offset, callback) {
195 this.execute(bglib.api.attributesRead, [handle, offset], callback);
196}
197
198Messenger.prototype.remoteWriteAck = function(connection, err, callback) {
199 this.execute(bglib.api.attributesUserWriteResponse, [connection, err], callback);
200}
201
202Messenger.prototype.readADC = function(input, decimation, aref, callback) {
203 this.execute(bglib.api.hwADCRead, [input, decimation, aref], callback);
204}
205
206Messenger.prototype.I2CSend = function(address, stopCondition, txbuf, callback) {
207 this.execute(bglib.api.hwI2CWrite, [address, stopCondition, txbuf], callback);
208}
209
210Messenger.prototype.I2CRead = function(address, stopCondition, length, callback) {
211 this.execute(bglib.api.hwI2CRead, [address, stopCondition, length], callback);
212}
213
214Messenger.prototype.readPin = function(port, pinMask, callback) {
215 this.execute(bglib.api.hwIOPortRead, [port, pinMask], callback);
216}
217
218Messenger.prototype.writePin = function(port, pinMask, value, callback) {
219 this.execute(bglib.api.hwIOPortWrite, [port, pinMask, value], callback);
220}
221
222Messenger.prototype.setPinDirections = function(port, pinMask, callback) {
223 this.execute(bglib.api.hwIOPortConfigDirection, [port, pinMask], callback);
224}
225
226Messenger.prototype.watchPin = function(port, mask, edge, callback) {
227 this.execute(bglib.api.hwIOPortConfigIRQ, [port, mask, edge], callback);
228}
229
230Messenger.prototype.setBondable = function(bondable, callback) {
231 this.execute(bglib.api.smSetBondableMode, [bondable], callback);
232}
233
234Messenger.prototype.getBonds = function(callback) {
235 this.execute(bglib.api.smGetBonds, [], callback);
236}
237
238Messenger.prototype.deleteBonds = function(peripheral, callback) {
239 this.execute(bglib.api.smDeleteBonding, [peripheral.bondHandle], callback);
240}
241
242Messenger.prototype.startEncryption = function(peripheral, bond, callback) {
243 this.execute(bglib.api.smEncryptStart, [peripheral.connection, bond], callback);
244}
245
246Messenger.prototype.enterPasskey = function(peripheral, passkey, callback) {
247 this.execute(bglib.api.smPasskeyEntry, [peripheral.connection, passkey], callback);
248}
249
250Messenger.prototype.setOOBData = function(data, callback) {
251 this.execute(bglib.api.smPasskeyEntry, [data], callback);
252}
253
254Messenger.prototype.setSecurityParameters = function(mitm, keysize, smpio, callback) {
255 this.execute(bglib.api.smPasskeyEntry, [mitm, keysize, smpio], callback);
256}
257// TODO:
258
259Messenger.prototype.discoverIncludedServices = function(peripheral, callback) {
260 this.execute(bglib.api.attClientReadByGroupType, [peripheral.connection, 00001, 0xFFFF, [0x02, 0x28]], callback);
261}
262
263
264
265
266Messenger.prototype.execute = function(command, params, callback) {
267
268 if (!command || command == 'undefined') {
269 callback && callback (new Error("Invalid API call."), null);
270 return;
271 }
272 // If they didn't enter any params and only a function
273 // Assume there are no params
274 if (typeof params == 'function') {
275 callback = params;
276 params = [];
277 }
278
279 var self = this;
280
281 // Grab the packet from the library
282 this.bgLib.getPacket(command, params, function(err, packet) {
283
284 if (err) {
285
286 // If we got a problem, let me hear it
287 return callback && callback(err);
288 }
289 // Else we're going to send it down the wire
290 else {
291 // Get the bytes for the packet
292 packet.getByteArray(function(bArray) {
293
294 if (DEBUG) {
295 console.log("Byte Array to be sent: ", bArray);
296 }
297
298 // Send the message
299 self.sendBytes(packet, bArray, callback);
300
301 });
302 }
303 });
304}
305
306Messenger.prototype.sendBytes = function(packet, bArray, callback) {
307
308 // // Send it along
309 var numSent = this.uart.write(bArray);
310
311 // If we didn't send every byte, something went wrong
312 // Return immediately
313 if (bArray.length != numSent) {
314
315 // Not enough bytes sent, somethign went wrong
316 callback && callback(new Error("Not all bytes were sent..."), null);
317
318 // Add the callback to the packet and set it to waiting
319 } else if (callback) {
320
321 // Set the callback the packet should respond to
322 packet.callback = callback;
323
324 // Add a timeout
325 var self = this;
326 packet.timeout = setTimeout(function() {
327 self.commandPacketTimeout.bind(self, packet)();
328 }, this.commandPacketTimeoutDuration);
329
330 // Push packet into awaiting queue
331 this.awaitingResponse.push(packet);
332 }
333}
334
335Messenger.prototype.commandPacketTimeout = function(commandPacket) {
336
337 this.popMatchingResponsePacket(commandPacket, function timeoutPacketFetch(err, packet) {
338
339 if (err) return console.log(err);
340 if (packet) {
341 try {
342 return packet.callback && packet.callback(new Error("Packet Timeout..."));
343 }
344 catch (e) {
345 console.log(e);
346 }
347 }
348 })
349}
350
351Messenger.prototype.popMatchingResponsePacket = function(parsedPacket, callback) {
352
353 // Grab the first packet that matches the command and class
354 var matchedPacket;
355 // Iterating through packets waiting response
356 for (var i = 0; i < this.awaitingResponse.length; i++) {
357 // Grab the packet
358 var packet = this.awaitingResponse[i];
359 // If the class and ID are the same, we have a match
360 if (packet && packet.cClass === parsedPacket.cClass
361 && packet.cID === parsedPacket.cID) {
362
363 // Clear the packet timeout
364 clearTimeout(packet.timeout);
365
366 // Delete it from the array
367 this.awaitingResponse.splice(i, 1);
368
369 // Save the command and break the loop
370 matchedPacket = packet;
371 break;
372 }
373 }
374
375 callback && callback(null, matchedPacket);
376
377 return matchedPacket;
378}
379
380
381
382Messenger.prototype.parseIncomingPackets = function(data) {
383
384 if (DEBUG) console.log("Just received this data: ", data);
385
386 var self = this;
387
388 // Grab the one or more responses from the library
389 var incomingPackets = this.bgLib.parseIncoming(data, function callbackSeeker(err, parsedPackets) {
390 // if (DEBUG) console.log("And that turned into ", parsedPackets.length, "packets")
391
392 if (err){
393 console.warn(err);
394 return;
395 }
396
397 //For each response
398 for (var i = 0; i < parsedPackets.length; i++) {
399 var parsedPacket = parsedPackets[i];
400 var cClass = parsedPacket.packet.cClass;
401 var cID = parsedPacket.packet.cID;
402
403 if (DEBUG) console.log("Got this packet: ", parsedPacket);
404
405 // If it's an event, emit the event
406 // Find the type of packet
407 switch (parsedPacket.packet.mType & 0x80) {
408 // It's an event
409 case 0x80:
410 if (parsedPacket.response) {
411 if (cClass === 0x00 && cID === 0x00) {
412 self.emit("booted");
413 }
414 else if (cClass === 0x02 && cID === 0x00) {
415 self.emit("remoteWrite", parsedPacket.response)
416 }
417 else if (cClass === 0x02 && cID === 0x02) {
418 self.emit("remoteStatus", parsedPacket.response)
419 }
420 else if (cClass === 0x03 && cID === 0x00) {
421 self.emit("connectionStatus", parsedPacket.response);
422 }
423 else if (cClass === 0x03 && cID === 0x04) {
424 self.emit('disconnect', parsedPacket.response);
425 }
426 else if (cClass === 0x04 && cID === 0x00) {
427 self.emit('remoteHandleRead', parsedPacket.response);
428 }
429 else if (cClass === 0x04 && cID === 0x01) {
430 self.emit('completedProcedure', parsedPacket.response);
431 }
432 else if (cClass === 0x04 && cID === 0x02) {
433 self.emit('groupFound', parsedPacket.response);
434 }
435 else if (cClass === 0x04 && cID === 0x04) {
436 self.emit('findInformationFound', parsedPacket.response);
437 }
438 else if (cClass === 0x04 && cID === 0x05) {
439 self.emit('attributeValue', parsedPacket.response);
440 }
441 else if (cClass === 0x05 && cID === 0x04) {
442 self.emit('bondStatus', parsedPacket.response);
443 }
444 else if (cClass === 0x06 && cID === 0x00) {
445 self.emit("discover", parsedPacket.response);
446 }
447 else if (cClass === 0x07 && cID === 0x00) {
448 self.emit("portStatus", parsedPacket.response);
449 }
450 else if (cClass === 0x07 && cID === 0x02) {
451 self.emit("ADCRead", parsedPacket.response);
452 }
453 }
454 break;
455
456 // It's a response
457 case 0x00:
458 // Find the command that requested it
459 self.popMatchingResponsePacket(parsedPacket.packet, function responsePacketPop(err, packet) {
460
461 // If we didn't have a logical error but had a bglib error
462 if (!err && (packet && packet.result != 0)) {
463 // Set bglib error as error
464 err = packet.result;
465 }
466
467 // Call the original callback
468 if (packet && packet.callback) packet.callback(err, parsedPacket.response);
469
470 });
471 break;
472 default:
473 console.warn("Malformed packet returned...");
474 }
475 }
476 });
477}
478
479module.exports = Messenger;