UNPKG

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