UNPKG

7.99 kBJavaScriptView Raw
1// Copyright 2014 Technical Machine, Inc. See the COPYRIGHT
2// file at the top-level directory of this distribution.
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10var util = require('util');
11var EventEmitter = require('events').EventEmitter;
12
13var PACKET_CONF = 0x55;
14var ACK_CONF = 0x33;
15var FIN_CONF = 0x16;
16
17var ACK_CMD = 0x00;
18var FIRMWARE_CMD = 0x01;
19var IR_TX_CMD = 0x02;
20var IR_RX_AVAIL_CMD = 0x03;
21var IR_RX_CMD = 0x04;
22var RX_START_CMD = 0x05;
23var RX_STOP_CMD = 0x06;
24var MAX_SIGNAL_DURATION = 200;
25
26var Infrared = function(hardware, callback) {
27
28 this.chipSelect = hardware.digital[0];
29 this.reset = hardware.digital[1];
30 this.irq = hardware.digital[2].rawWrite(false);
31 this.spi = hardware.SPI({clockSpeed : 1000, mode:2, chipSelect:this.chipSelect});
32 this.transmitting = false;
33 this.listening = false;
34 this.chipSelect.output().high();
35 this.reset.output().high();
36
37 var self = this;
38
39 // If we get a new listener
40 this.on('newListener', function (event) {
41 // And they are listening for rx data and we haven't been yet
42 if (event == 'data' && !this.listeners(event).length) {
43 self.setListening(1);
44 }
45 });
46
47 this.on('removeListener', function (event) {
48 // If this was for the rx data event and there aren't any more listeners
49 if (event == 'data' && !this.listeners(event).length) {
50 self.setListening(0);
51 }
52 });
53
54 // Make sure we can communicate with the module
55 this._establishCommunication(3, function (err, version) {
56 if (err) {
57 setImmediate(function () {
58 // Emit an error event
59 self.emit('error');
60 });
61 } else {
62 setImmediate(function () {
63 // Emit a ready event
64 self.emit('ready');
65 // Start listening for IRQ interrupts
66 self.irq.once('high', self._IRQHandler.bind(self));
67 });
68 }
69
70 // Make sure we aren't gathering rx data until someone is listening.
71 self.setListening( function listeningSet(err) {
72 // Complete the setup
73 if (callback) {
74 callback(err, self);
75 }
76 });
77 });
78};
79
80util.inherits(Infrared, EventEmitter);
81
82Infrared.prototype._IRQHandler = function () {
83 var self = this;
84 // If we are not in the middle of transmitting
85 if (!self.transmitting) {
86 // Receive the durations
87 self._fetchRXDurations(function fetched () {
88 // Start listening for IRQ interrupts again
89 self.irq.once('high', self._IRQHandler.bind(self));
90 });
91 } else {
92 // If we are, check back in a little bit
93 setTimeout(self._IRQHandler.bind(self), 500);
94 }
95};
96
97Infrared.prototype.setListening = function (set, callback) {
98 var self = this;
99
100 var cmd = set ? RX_START_CMD : RX_STOP_CMD;
101
102 self.spi.transfer(new Buffer([cmd, 0x00, 0x00]), function listeningSet (err, response) {
103 self._validateResponse(response, [PACKET_CONF, cmd], function (valid) {
104
105 if (!valid) {
106 callback && callback(new Error("Invalid response on setting rx on/off."));
107 } else {
108 self.listening = set ? true : false;
109 callback && callback();
110 }
111 });
112 });
113};
114
115Infrared.prototype._fetchRXDurations = function (callback) {
116 var self = this;
117 // We have to pull chip select high in case we were in the middle of something else
118
119 // this.chipSelect.high();
120 self.spi.transfer(new Buffer([IR_RX_AVAIL_CMD, 0x00, 0x00, 0x00]), function spiComplete (err, response) {
121 // DO something smarter than this eventually
122
123 self._validateResponse(response, [PACKET_CONF, IR_RX_AVAIL_CMD, 1], function (valid) {
124 if (valid) {
125 var numInt16 = response[3];
126
127 // (We have two bytes per element...);
128 var numBytes = numInt16 * 2;
129
130 var rxHeader = [IR_RX_CMD, 0x00, 0x00];
131 var packet = rxHeader.concat(new Array(numBytes));
132
133 // Push the stop bit on there.
134 packet.push(FIN_CONF);
135
136 self.spi.transfer(new Buffer(packet), function spiComplete (err, response) {
137 var fin = response[response.length - 1];
138
139 if (fin != FIN_CONF) {
140 console.warn("Warning: Received Packet Out of Frame.");
141
142 callback && callback();
143 } else {
144 // Remove the header echoes at the beginning and stop bit
145 var buf = response.slice(rxHeader.length, response.length - 1);
146
147 // Emit the buffer
148 self.emit('data', buf);
149 callback && callback();
150 }
151 });
152 }
153 });
154 });
155};
156
157Infrared.prototype.sendRawSignal = function (frequency, signalDurations, callback) {
158 if (frequency <= 0) {
159 callback && callback(new Error("Invalid frequency. Must be greater than zero. Works best between 36-40."));
160 return;
161 }
162
163 if (signalDurations.length > MAX_SIGNAL_DURATION) {
164 callback && callback(new Error("Invalid buffer length. Must be between 1 and ", MAX_SIGNAL_DURATION));
165 return;
166 }
167
168 this.transmitting = true;
169 var self = this;
170
171 // Make the packet
172 var tx = this._constructTXPacket(frequency, signalDurations);
173
174 // Send it over
175 this.spi.transfer(tx, function spiComplete (err, response) {
176 self.transmitting = false;
177
178 // If there was an error already, set immediate on the callback
179 var err = null;
180 if (!self._validateResponse(response, [PACKET_CONF, IR_TX_CMD, frequency, signalDurations.length/2])) {
181 err = new Error("Invalid response from raw signal packet.");
182 }
183
184 callback && callback(err);
185 });
186};
187
188Infrared.prototype._constructTXPacket = function (frequency, signalDurations) {
189 // Create array
190 var tx = [];
191 // Add command
192 tx.push(IR_TX_CMD);
193 // Frequency of PWN
194 tx.push(frequency);
195
196 // Add length of signal durations in terms of int16s
197 tx.push(signalDurations.length / 2);
198
199 // For each signal duration
200 for (var i = 0; i < signalDurations.length; i++) {
201 // Send upper and lower bits
202 tx.push(signalDurations.readUInt8(i));
203 }
204 // Put a dummy bit to get the last echo
205 tx.push(0x00);
206
207 // Put the finish confirmation
208 tx.push(FIN_CONF);
209
210 return new Buffer(tx);
211};
212
213Infrared.prototype._establishCommunication = function (retries, callback){
214 var self = this;
215 // Grab the firmware version
216 self.getFirmwareVersion(function (err, version) {
217 // If it didn't work
218 if (err) {
219 // Subtract number of retries
220 retries--;
221 // If there are no more retries possible
222 if (!retries) {
223 // Throw an error and return
224 return callback && callback(new Error("Can't connect with module..."));
225 }
226 // Else call recursively
227 else {
228 self._establishCommunication(retries, callback);
229 }
230 } else {
231 // Connected successfully
232 self.connected = true;
233 // Call callback with version
234 callback && callback(null, version);
235 }
236 });
237};
238
239Infrared.prototype.getFirmwareVersion = function (callback) {
240 var self = this;
241
242 self.spi.transfer(new Buffer([FIRMWARE_CMD, 0x00, 0x00]), function spiComplete (err, response) {
243 if (err) {
244 return callback(err, null);
245 } else if (self._validateResponse(response, [false, FIRMWARE_CMD]) && response.length === 3) {
246 callback && callback(null, response[2]);
247 } else {
248 callback && callback(new Error("Error retrieving Firmware Version"));
249 }
250 });
251};
252
253Infrared.prototype._validateResponse = function (values, expected, callback) {
254 var res = true;
255 for (var index = 0; index < expected.length; index++) {
256 if (expected[index] == false) {
257 continue;
258 }
259 if (expected[index] != values[index]) {
260 res = false;
261 break;
262 }
263 }
264
265 callback && callback(res);
266 return res;
267};
268
269
270/**
271 * Public API
272 */
273
274exports.Infrared = Infrared;
275exports.use = function (hardware, callback) {
276 return new Infrared(hardware, callback);
277};