UNPKG

7.73 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;
12var Attiny = require('attiny-common');
13var MODULE_ID = 0x0B;
14var TINY84_SIGNATURE = 0x930C;
15var FIRMWARE_FILE = __dirname + '/firmware/src/infrared-attx4.hex';
16
17// Confirmation Signals
18var PACKET_CONF = 0x55;
19var ACK_CONF = 0x33;
20var FIN_CONF = 0x16;
21
22// Available commands
23var IR_TX_CMD = 0x02;
24var IR_RX_AVAIL_CMD = 0x03;
25var IR_RX_CMD = 0x04;
26var RX_START_CMD = 0x05;
27var RX_STOP_CMD = 0x06;
28var CRC_CMD = 0x07;
29
30// Module specific defines
31var MAX_SIGNAL_DURATION = 200;
32
33// Firmware release info. Updated with each release
34var FIRMWARE_VERSION = 0x03;
35var CRC = 13777;
36
37var Infrared = function(hardware, callback) {
38
39 var self = this;
40
41 // Create a new tiny agent
42 this.attiny = new Attiny(hardware);
43
44 // Store our firmware checking and updating options
45 var firmwareOptions = {
46 firmwareFile : FIRMWARE_FILE,
47 firmwareVersion : FIRMWARE_VERSION,
48 moduleID : MODULE_ID,
49 signature : TINY84_SIGNATURE,
50 crc : CRC,
51 }
52
53 // Initialize (check firmware version, update as necessary)
54 this.attiny.initialize(firmwareOptions, function(err) {
55
56 // If there was an error
57 if (err) {
58 // Emit it
59 self.emit('error', err);
60 // Call the callback
61 if (callback) callback(err);
62 // Abort
63 return;
64 }
65 // There was no error
66 else {
67
68 self.connected = true;
69
70 // If we get a new listener
71 self.on('newListener', function (event) {
72 // And they are listening for rx data and we haven't been yet
73 if (event == 'data' && !this.listeners(event).length) {
74 // Enable GPIO Interrupts on IRQ
75 self._setListening(1);
76 }
77 });
78
79 // Someone stopped listening
80 self.on('removeListener', function (event) {
81 // If this was for the rx data event and there aren't any more listeners
82 if (event == 'data' && !this.listeners(event).length) {
83 // Disable gpio interrupts on IRQ
84 self._setListening(0);
85 }
86 });
87
88 setImmediate(function () {
89 // Emit a ready event
90 self.emit('ready');
91 // Start listening for IRQ interrupts
92 self.attiny.setIRQCallback(self._IRQHandler.bind(self));
93
94 });
95
96 // Make sure we aren't gathering rx data until someone is listening.
97 var listening = self.listeners('data').length ? true : false;
98
99 self._setListening(listening, function listeningSet(err) {
100 // Complete the setup
101 if (callback) {
102 callback(err, self);
103 }
104 });
105 }
106 });
107};
108
109util.inherits(Infrared, EventEmitter);
110
111Infrared.prototype._IRQHandler = function (callback) {
112 var self = this;
113 // If we are not in the middle of transmitting
114 if (!self.transmitting) {
115 // Receive the durations
116 self._fetchRXDurations(function fetched () {
117 // Start listening for IRQ interrupts again
118 self.attiny.setIRQCallback(self._IRQHandler.bind(self));
119 });
120 } else {
121 // If we are, check back in a little bit
122 setTimeout(self._IRQHandler.bind(self), 500);
123 }
124};
125
126Infrared.prototype._setListening = function (set, callback) {
127 var self = this;
128
129 var cmd = set ? RX_START_CMD : RX_STOP_CMD;
130 self.attiny.transceive(new Buffer([cmd, 0x00, 0x00]), function listeningSet (err, response) {
131 self.attiny._validateResponse(response, [PACKET_CONF, cmd], function (valid) {
132 if (!valid) {
133 callback && callback(new Error("Invalid response on setting rx on/off."));
134 } else {
135 self.listening = set ? true : false;
136 // If we aren't listening any more
137 if (!self.listening) {
138 // Remove this GPIO interrupt
139 self.attiny.irq.removeAllListeners();
140 }
141 else {
142 // Make sure it calls the IRQ handler
143 if (!self.attiny.irq.listeners('high').length) {
144 self.attiny.irq.once('high', self._IRQHandler.bind(self));
145 }
146 }
147 callback && callback();
148 }
149 });
150 });
151};
152
153Infrared.prototype._fetchRXDurations = function (callback) {
154 var self = this;
155 // We have to pull chip select high in case we were in the middle of something else
156 self.attiny.transceive(new Buffer([IR_RX_AVAIL_CMD, 0x00, 0x00, 0x00]), function spiComplete (err, response) {
157 // DO something smarter than this eventually
158
159 self.attiny._validateResponse(response, [PACKET_CONF, IR_RX_AVAIL_CMD, 1], function (valid) {
160 if (valid) {
161 var numInt16 = response[3];
162
163 // (We have two bytes per element...);
164 var numBytes = numInt16 * 2;
165
166 var rxHeader = [IR_RX_CMD, 0x00, 0x00];
167 var packet = rxHeader.concat(new Array(numBytes));
168
169 // Push the stop bit on there.
170 packet.push(FIN_CONF);
171
172 self.attiny.transceive(new Buffer(packet), function spiComplete (err, response) {
173 var fin = response[response.length - 1];
174
175 if (fin != FIN_CONF) {
176 console.warn("Warning: Received Packet Out of Frame.");
177
178 callback && callback();
179 } else {
180
181 // Remove the header echoes at the beginning and stop bit
182 var buf = response.slice(rxHeader.length, response.length - 1);
183
184 // Emit the buffer
185 self.emit('data', buf);
186 callback && callback();
187 }
188 });
189 }
190 });
191 });
192};
193
194Infrared.prototype.sendRawSignal = function (frequency, signalDurations, callback) {
195 if (frequency <= 0) {
196 callback && callback(new Error("Invalid frequency. Must be greater than zero. Works best between 36-40."));
197 return;
198 }
199
200 if (signalDurations.length > MAX_SIGNAL_DURATION) {
201 callback && callback(new Error("Invalid buffer length. Must be between 1 and ", MAX_SIGNAL_DURATION));
202 return;
203 }
204
205 if (signalDurations.length % 2 != 0) {
206 if (callback) {
207 callback(new Error("Invalid buffer size. Transmission buffers must be an even length of 8 bit values representing 16-bit words."));
208 }
209 }
210
211 this.transmitting = true;
212
213 var self = this;
214
215 // Make the packet
216 var tx = this._constructTXPacket(frequency, signalDurations);
217
218 // Send it over
219 this.attiny.transceive(tx, function spiComplete (err, response) {
220 self.transmitting = false;
221
222 // If there was an error already, set immediate on the callback
223 var err = null;
224 if (!self.attiny._validateResponse(response, [PACKET_CONF, IR_TX_CMD, frequency, signalDurations.length/2])) {
225 err = new Error("Invalid response from raw signal packet.");
226 }
227
228 callback && callback(err);
229 });
230};
231
232Infrared.prototype._constructTXPacket = function (frequency, signalDurations) {
233 // Create array
234 var tx = [];
235 // Add command
236 tx.push(IR_TX_CMD);
237 // Frequency of PWN
238 tx.push(frequency);
239
240 // Add length of signal durations in terms of int16s
241 tx.push(signalDurations.length / 2);
242
243 // For each signal duration
244 for (var i = 0; i < signalDurations.length; i++) {
245 // Send upper and lower bits
246 tx.push(signalDurations.readUInt8(i));
247 }
248 // Put a dummy bit to get the last echo
249 tx.push(0x00);
250
251 // Put the finish confirmation
252 tx.push(FIN_CONF);
253
254 return new Buffer(tx);
255};
256
257
258
259function use (hardware, callback) {
260 return new Infrared(hardware, callback);
261}
262
263/**
264 * Public API
265 */
266
267exports.Infrared = Infrared;
268exports.use = use;