UNPKG

11 kBJavaScriptView Raw
1var tessel = require('tessel');
2var bgLib = require('bglib');
3bgLib.setPacketMode(1);
4var EventEmitter = require('events').EventEmitter;
5var util = require('util');
6
7var DEBUG = 0;
8
9
10// This needs to become a property on Bluetooth Messenger
11// Associative Array of packet objects to timeoutIDs
12// Actually, since we don't have clearTimeout, it's an
13// associative array of packet objects to a boolean
14// of whether the timeout should be acted upon once fired...
15var awaitingResponse = [];
16
17// This is a global var so that I can access the controllers'
18// events.
19var controller;
20var TX_HANDLE=20;
21
22var characteristicHandles = [21, 25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 65];
23
24/*************************************************************
25Function: connect
26Description: Set the module port of the Bluetooth module
27 so the Tessel can begin communicating.
28Params: hardware - the module port ble was plugged in to
29 next - a callback for what happens after connecting
30*************************************************************/
31function connect(hardware, next) {
32 controller = new BluetoothController(hardware, next);
33
34 return controller;
35}
36
37/*************************************************************
38Function: BluetoothController
39Description: Instantiate a Bluetooth Controller object. Controls
40 all BLE Central and Peripheral methods (depending)
41 on role.
42Params: hardware - the module port ble was plugged in to
43*************************************************************/
44function BluetoothController(hardware, next) {
45 this.hardware = hardware;
46 this.isAdvertising = false;
47 this.messenger = new BluetoothMessenger(hardware);
48 this.connected = false;
49 var self = this;
50 controller = self;
51
52 this.verifyCommunication(function(err, response) {
53 if (err) {
54 console.log("ERROR: Could not establish comms with BLE...");
55 console.log(err.message);
56 next && next(err);
57 }
58
59 else {
60
61 console.log("Comms established with BLE!");
62 self.connected = true;
63 self.emit('connected');
64 next && next(null);
65 }
66 });
67}
68
69util.inherits(BluetoothController, EventEmitter);
70
71/*************************************************************
72Function: scanForPeripherals (Central Role)
73Description: Start looking for peripheral devices to connect with
74Params: timeout - number of milliseconds to search before giving up.
75 If this value, is ignored, will scan forever.
76 serviceUUIDs - An array of Service UUIDs
77 next - A callback that should expect to receive
78 an array of available devices.
79*************************************************************/
80BluetoothController.prototype.scanForPeripherals = function(next) {
81 this.messenger.execute(bgLib.api.gapDiscover, [bgLib.GAPDiscoverMode.gap_discover_generic], next);
82}
83
84BluetoothController.prototype.stopScanning = function(next) {
85 this.messenger.execute(bgLib.api.gapEndProcedure, next);
86}
87
88BluetoothController.prototype.connectToPeripheral = function(address, address_type, conn_interval_min, conn_interval_max, timeout, latency, next) {
89 this.messenger.execute(bgLib.api.gapConnectDirect, [address, address_type, conn_interval_min, conn_interval_max, timeout, latency], next);
90}
91
92BluetoothController.prototype.disconnectFromPeripheral = function(handle, next) {
93 this.messenger.execute(bgLib.api.connectionDisconnect, [handle], next);
94}
95
96BluetoothController.prototype.findInformation = function(connection, start, end, next) {
97 this.messenger.execute(bgLib.api.attClientFindInformation, [connection, start, end], next);
98}
99
100BluetoothController.prototype.startAdvertising = function(next) {
101 this.messenger.execute(bgLib.api.gapSetMode, [bgLib.GAPDiscoverableModes.gap_general_discoverable, bgLib.GAPConnectableMode.gap_directed_connectable], next);
102}
103
104BluetoothController.prototype.readLocalHandle = function(handle, next) {
105 this.messenger.execute(bgLib.api.attributesRead, [handle, 0], next);
106}
107
108BluetoothController.prototype.readRemoteHandle = function(connection, handle, next) {
109 this.messenger.execute(bgLib.api.attClientReadByHandle, [connection, handle], next);
110}
111
112BluetoothController.prototype.writeValue = function(handle, value, next) {
113 // Quick hack to get around folks who haven't updated their firmware
114 if (!next) {
115 handle = TX_HANDLE;
116 }
117 else {
118 handle = characteristicHandles[handle];
119 }
120 this.messenger.execute(bgLib.api.attributesWrite, [handle, 0, value], next);
121}
122
123BluetoothController.prototype.verifyCommunication = function(next) {
124 this.messenger.execute(bgLib.api.systemHello, next);
125}
126
127function BluetoothMessenger(hardware) {
128 this.uart = hardware.UART({baudrate:9600});
129 // this.uart = tessel.port('GPIO').UART();
130 this.uart.on('data', this.parseIncomingPackets);
131
132 this.commandPacketTimeoutDuration = 10000;
133
134 // Set up the wake pin
135 this.wakePin = hardware.gpio(3);
136 this.wakePin.output().low();
137
138 this.wakePin.high();
139}
140
141// TODO: Make this use the correct GPIO
142/*************************************************************
143Function: wakeBLE
144Description: Either wake the ble chip up for comm or put it to sleep
145Params: wakeBool - a boolean of whether it should be awake or asleep
146*************************************************************/
147BluetoothMessenger.prototype.wakeBLE = function(wakeBool) {
148 tessel.sleep(10);
149 this.wakePin.set(wakeBool);
150 tessel.sleep(10);
151}
152
153BluetoothMessenger.prototype.execute = function(command, params, callback) {
154
155 // Shouldn't need to do this once bind is fixed
156 var self = this;
157
158 if (!command || command == 'undefined') {
159 callback && callback (new Error("Invalid API call."), null);
160 return;
161 }
162
163 // If they didn't enter any params and only a function
164 // Assume there are no params
165 if (typeof params == 'function') {
166 callback = params;
167 params = [];
168 }
169 // Grab the packet from the library
170 bgLib.getPacket(command, params, function(err, packet) {
171
172 if (err) {
173 // If we got a problem, let me hear it
174 throw err;
175 }
176 // Else we're going to send it down the wire
177 else {
178 // Get the bytes for the packet
179 packet.getByteArray(function(bArray) {
180
181
182 if (DEBUG) console.log("Byte Array to be sent: ", bArray);
183
184 // Pull up the wake up pin
185 self.wakeBLE(1);
186
187 controller.once('hardwareIOPortStatusChange', function() {
188 if (DEBUG) console.log("Port Change. Sending Packet");
189 self.sendBytes(packet, bArray, callback);
190 });
191
192 });
193 }
194 });
195}
196
197BluetoothMessenger.prototype.sendBytes = function(packet, bArray, callback) {
198 // Send it along
199 var numSent = this.uart.write(bArray);
200
201 // Pull wake pin back down
202 this.wakeBLE(0);
203
204 // If we didn't send every byte, something went wrong
205 // Return immediately
206 if (bArray.length != numSent) {
207
208 // Not enough bytes sent, somethign went wrong
209 callback && callback(new Error("Not all bytes were sent..."), null);
210
211 // Add the callback to the packet and set it to waiting
212 } else {
213
214 // Add the callback to the packet for later
215 if (callback) {
216 // Set the callback the packet should respond to
217 packet.callback = callback;
218
219 // Add a timeout
220 var self = this;
221 packet.timeout = setTimeout(function() {
222 self.commandPacketTimeout(packet);
223 }, this.commandPacketTimeoutDuration);
224
225
226 // Push packet into awaiting queue
227 awaitingResponse.push(packet);
228 }
229 }
230}
231
232BluetoothMessenger.prototype.commandPacketTimeout = function(commandPacket) {
233
234 popMatchingResponsePacket(commandPacket, function(err, packet) {
235
236 if (err) return console.log(err);
237 if (packet) {
238 try {
239 return packet.callback(new Error("Packet Timeout..."));
240 }
241 catch (e) {
242 console.log(e);
243 }
244 }
245 })
246}
247
248var popMatchingResponsePacket = function(parsedPacket, callback) {
249
250 // Grab the first packet that matches the command and class
251 var matchedPacket;
252 // Iterating through packets waiting response
253 for (var i in awaitingResponse) {
254 // Grab the packet
255 var packet = awaitingResponse[i];
256
257 // If the class and ID are the same, we have a match
258 if (packet.packetHeader.cClass == parsedPacket.packetHeader.cClass
259 && packet.packetHeader.cID == parsedPacket.packetHeader.cID) {
260
261 // Clear the packet timeout
262 clearTimeout(packet.timeout);
263
264 // Delete it from the array
265 awaitingResponse.splice(i, 1);
266
267 // Save the command and break the loop
268 matchedPacket = packet;
269 break;
270 }
271 }
272
273 callback && callback(null, matchedPacket);
274
275 return matchedPacket;
276}
277
278
279
280BluetoothMessenger.prototype.parseIncomingPackets = function(data) {
281
282 if (DEBUG) console.log("Just received this data: ", data);
283
284 // Grab the one or more responses from the library
285 var incomingPackets = bgLib.parseIncoming(data, function(err, parsedPackets) {
286 if (DEBUG) console.log("And that turned into ", parsedPackets.length, "packets")
287 if (err){
288 console.log(err);
289 return;
290 }
291 //For each response
292 for (var i in parsedPackets) {
293 var parsedPacket = parsedPackets[i];
294 var cClass = parsedPacket.packet.packetHeader.cClass;
295 var cID = parsedPacket.packet.packetHeader.cID;
296
297 // If it's an event, emit the event
298 // Find the type of packet
299 switch (parsedPacket.packet.packetHeader.mType & 0x80) {
300 // It's an event
301 case 0x80:
302 if (parsedPacket.response) {
303 if (cClass == 0x07 && cID == 0x00) {
304 controller.emit("hardwareIOPortStatusChange", parsedPacket.response);
305 }
306 else if (cClass == 0x06 && cID == 0x00) {
307 controller.emit("discoveredPeripheral", parsedPacket.response);
308 }
309 else if (cClass == 0x00 && cID == 0x00) {
310 controller.emit("booted");
311 }
312 else if (cClass == 0x03 && cID == 0x00) {
313 controller.emit("connectionStatus", parsedPacket.response);
314 }
315 else if (cClass == 0x03 && cID == 0x04) {
316 controller.emit('disconnectedPeripheral', parsedPacket.response);
317 }
318 else if (cClass == 0x04 && cID == 0x04) {
319 controller.emit('foundInformation', parsedPacket.response);
320 }
321 else if (cClass == 0x04 && cID == 0x01) {
322 controller.emit('completedProcedure', parsedPacket.response);
323 }
324 else if (cClass == 0x04 && cID == 0x05) {
325 controller.emit('readValue', parsedPacket.response);
326 }
327 }
328
329 break;
330
331 // It's a response
332 case 0x00:
333 // Find the command that requested it
334 popMatchingResponsePacket(parsedPacket.packet, function(err, packet) {
335
336 // Call the original callback
337 if (packet && packet.callback) packet.callback(err, parsedPacket.response);
338
339 });
340 break;
341 default:
342 throw new Error("Malformed packet returned...");
343 }
344 }
345 });
346}
347
348/*************************************************************
349PUBLIC API
350*************************************************************/
351module.exports.connect = connect;
352module.exports.BluetoothController = BluetoothController;
353module.exports.Events = Events;
\No newline at end of file