UNPKG

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