UNPKG

8.85 kBJavaScriptView Raw
1var util = require('util');
2var EventEmitter = require('events').EventEmitter;
3
4var PACKET_CONF = 0x55;
5var ACK_CONF = 0x33;
6var FIN_CONF = 0x16;
7
8var ACK_CMD = 0x00;
9var FIRMWARE_CMD = 0x01;
10var IR_TX_CMD = 0x02;
11var IR_RX_AVAIL_CMD = 0x03
12var IR_RX_CMD = 0x04;
13var RX_START_CMD = 0x05;
14var RX_STOP_CMD = 0x06;
15var MAX_SIGNAL_DURATION = 200;
16
17
18var Infrared = function(hardware, callback) {
19
20 this.spi = hardware.SPI({clockSpeed : 1000});
21 this.chipSelect = hardware.gpio(1);
22 this.reset = hardware.gpio(2);
23 this.irq = hardware.gpio(3);
24 this.transmitting = false;
25 this.listening = false;
26 this.chipSelect.output().high();
27 this.reset.output().high();
28 this.irq.output().low().input();
29
30 var self = this;
31
32 // If we get a new listener
33 this.on('newListener', function(event) {
34 // And they are listening for rx data and we haven't been yet
35 if (!self.listening && event == "data") {
36 self.setListening(1);
37 }
38 });
39
40 this.on('removeListener', function(event) {
41 console.log("Remove listener!", event);
42 // If this was for the rx data event and there aren't any more listeners
43 if (event == "data" && !self.listeners(event).length) {
44 self.setListening(0);
45 }
46 });
47
48 // Make sure we can communicate with the module
49 this.establishCommunication(3, function(err, version) {
50 setImmediate(function() {
51 if (!err) {
52 self.emit('ready', self);
53 }
54 else {
55 self.emit('error', err);
56 }
57 });
58 // Make sure we aren't gathering rx data until someone is listening.
59 self.setListening(0);
60
61 // Start listening for IRQ interrupts
62 self.irq.watch('rise', self.IRQHandler.bind(self));
63
64 // Complete the setup
65 callback && callback(err, self);
66
67 return self;
68 });
69}
70
71util.inherits(Infrared, EventEmitter);
72
73Infrared.prototype.IRQHandler = function() {
74
75 // If we are not in the middle of transmitting
76 if (!this.transmitting) {
77 // Receive the durations
78 this.fetchRXDurations();
79 } else {
80 // If we are, check back in 2 seconds
81 // TODO: reduce this timeout when we're running faster
82 setTimeout(this.IRQHandler.bind(this), 2000);
83 }
84}
85
86Infrared.prototype.setListening = function(set, callback) {
87 var self = this;
88
89 var cmd = set ? RX_START_CMD : RX_STOP_CMD;
90
91 var response = this.SPITransfer([cmd, 0x00, 0x00]);
92
93 self.validateResponse(response, [PACKET_CONF, cmd], function(valid) {
94
95 if (!valid) {
96 return callback && callback(new Error("Invalid response on setting rx on/off."));
97 }
98
99 callback && callback();
100 })
101}
102
103Infrared.prototype.fetchRXDurations = function() {
104 var self = this;
105 // We have to pull chip select high in case we were in the middle of something else
106
107 // this.chipSelect.high();
108 this.SPITransfer([IR_RX_AVAIL_CMD, 0x00, 0x00, 0x00], function(response) {
109 // DO something smarter than this eventually
110
111 self.validateResponse(response, [PACKET_CONF, IR_RX_AVAIL_CMD, 1], function(valid) {
112 if (valid) {
113 var numInt16 = response[3];
114
115 // (We have two bytes per element...);
116 var numBytes = numInt16 * 2;
117
118 var rxHeader = [IR_RX_CMD, 0x00, 0x00];
119 var packet = rxHeader.concat(EmptyArray(numBytes))
120
121
122 // Push the stop bit on there.
123 packet.push(FIN_CONF);
124
125 self.SPITransfer(packet, function(response) {
126
127 var fin = response.pop();
128
129 if (fin != FIN_CONF) {
130 console.log("Warning: Received Packet Out of Frame.");
131 return;
132 }
133
134 if (err){
135 return;// console.log("Issue sending dummy bytes...");
136 }
137
138 else {
139 // Remove the header echoes at the beginning
140 var arr = response.splice(rxHeader.length, response.length-rxHeader.length);
141
142 // Emit the buffer
143 self.emit('data', new Buffer(arr));
144 }
145 })
146 }
147 else {
148 // Pull chip select high because we won't be continuing to read.
149 self.chipSelect.high();
150 // We were told to read and then there wasn't data available... wtf.
151 return;
152 }
153 })
154 })
155}
156
157// Remove once Array class works...
158function EmptyArray(size) {
159 var arr = [];
160
161 for (var i = 0; i < size; i++) {
162 arr[i] = 0;
163 }
164
165 return arr;
166}
167
168Infrared.prototype.sendRawSignal = function(frequency, signalDurations, callback) {
169 if (frequency <= 0) {
170 setImmediate(function() {
171 callback && callback(new Error("Invalid frequency. Must be greater than zero. Works best between 36-40."));
172 });
173 }
174 else if (signalDurations.length > MAX_SIGNAL_DURATION) {
175 setImmediate(function() {
176 callback && callback(new Error("Invalid buffer length. Must be between 1 and ", MAX_SIGNAL_DURATION));
177 })
178 } else {
179
180 this.transmitting = true;
181
182 var self = this;
183
184 // Make the packet
185 var tx = this.constructTXPacket(frequency, signalDurations);
186
187 // Send it over
188 this.SPITransfer(tx, function(response) {
189
190 self.transmitting = false;
191
192 // If there was an error already, set immediate on the callback
193 if (err) {
194 setImmediate(function() {
195 callback && callback(err);
196 });
197 return;
198 }
199
200 else if (!self.validateResponse(response, [PACKET_CONF, IR_TX_CMD, frequency, signalDurations.length/2])) {
201 err = new Error("Invalid response from raw signal packet: ", response);
202 }
203
204 setImmediate(function() {
205 callback && callback(err);
206 });
207 });
208 }
209}
210
211Infrared.prototype.constructTXPacket = function(frequency, signalDurations) {
212 // Create array
213 var tx = [];
214 // Add command
215 tx.push(IR_TX_CMD);
216 // Frequency of PWN
217 tx.push(frequency);
218
219 // Add length of signal durations in terms of int16s
220 tx.push(signalDurations.length/2)
221
222 // For each signal duration
223 for (var i = 0; i < signalDurations.length; i++) {
224 // Send upper and lower bits
225 tx.push(signalDurations.readUInt8(i));
226 }
227 // Put a dummy bit to get the last echo
228 tx.push(0x00);
229
230 // Put the finish confirmation
231 tx.push(FIN_CONF);
232
233 // return
234 return tx;
235}
236
237Infrared.prototype.getFirmwareVersion = function(callback) {
238
239 var self = this;
240 this.SPITransfer([FIRMWARE_CMD, 0x00, 0x00], function(response) {
241
242 if (err) return callback(err, null);
243 if (self.validateResponse(response, [PACKET_CONF]) && response.length == 3) {
244 setImmediate(function() {
245 callback && callback(null, response[2]);
246 });
247 } else {
248 setImmediate(function() {
249 callback && callback(new Error("Error retrieving Firmware Version"));
250 });
251 }
252 });
253}
254
255Infrared.prototype.establishCommunication = function(retries, callback){
256 var response;
257 while (retries) {
258 response = this.SPITransfer([FIRMWARE_CMD, 0x00, 0x00]);
259 if (this.validateResponse(response, [PACKET_CONF, FIRMWARE_CMD]) && response.length == 3) {
260 this.connected = true;
261 callback && callback(null, response[2]);
262 break;
263 } else {
264 retries--;
265 if (!retries) {
266 callback && callback(new Error("Can't connect with module..."));
267 break;
268 }
269 }
270 }
271}
272
273Infrared.prototype.validateResponse = function(values, expected, callback) {
274 var res = true;
275
276 // TODO: Replace with the 'every' method
277 expected.forEach(function(element, index) {
278 if (element != values[index]) {
279 res = false;
280 }
281 });
282
283 setImmediate(function() {
284 callback && callback(res);
285 })
286
287 return res;
288}
289
290Infrared.prototype.SPITransfer = function(data, callback) {
291
292 // Pull Chip select down prior to transfer
293 this.chipSelect.low();
294
295 // Send over the data
296 var ret = this.spi.transferSync(data);
297
298 // Pull chip select back up
299 this.chipSelect.high();
300 // Call any callbacks
301 callback && callback(ret);
302
303 // Return the data
304 return ret;
305}
306
307exports.Infrared = Infrared;
308exports.use = function (hardware, callback) {
309 return new Infrared(hardware, callback);
310};
\No newline at end of file