1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | var util = require('util');
|
11 | var EventEmitter = require('events').EventEmitter;
|
12 | var Attiny = require('attiny-common');
|
13 | var MODULE_ID = 0x0B;
|
14 | var TINY84_SIGNATURE = 0x930C;
|
15 | var FIRMWARE_FILE = __dirname + '/firmware/src/infrared-attx4.hex';
|
16 |
|
17 |
|
18 | var PACKET_CONF = 0x55;
|
19 | var ACK_CONF = 0x33;
|
20 | var FIN_CONF = 0x16;
|
21 |
|
22 |
|
23 | var IR_TX_CMD = 0x02;
|
24 | var IR_RX_AVAIL_CMD = 0x03;
|
25 | var IR_RX_CMD = 0x04;
|
26 | var RX_START_CMD = 0x05;
|
27 | var RX_STOP_CMD = 0x06;
|
28 | var CRC_CMD = 0x07;
|
29 |
|
30 |
|
31 | var MAX_SIGNAL_DURATION = 200;
|
32 |
|
33 |
|
34 | var FIRMWARE_VERSION = 0x03;
|
35 | var CRC = 13777;
|
36 |
|
37 | var Infrared = function(hardware, callback) {
|
38 |
|
39 | var self = this;
|
40 |
|
41 |
|
42 | this.attiny = new Attiny(hardware);
|
43 |
|
44 |
|
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 |
|
54 | this.attiny.initialize(firmwareOptions, function(err) {
|
55 |
|
56 |
|
57 | if (err) {
|
58 |
|
59 | self.emit('error', err);
|
60 |
|
61 | if (callback) callback(err);
|
62 |
|
63 | return;
|
64 | }
|
65 |
|
66 | else {
|
67 |
|
68 | self.connected = true;
|
69 |
|
70 |
|
71 | self.on('newListener', function (event) {
|
72 |
|
73 | if (event == 'data' && !this.listeners(event).length) {
|
74 |
|
75 | self._setListening(1);
|
76 | }
|
77 | });
|
78 |
|
79 |
|
80 | self.on('removeListener', function (event) {
|
81 |
|
82 | if (event == 'data' && !this.listeners(event).length) {
|
83 |
|
84 | self._setListening(0);
|
85 | }
|
86 | });
|
87 |
|
88 | setImmediate(function () {
|
89 |
|
90 | self.emit('ready');
|
91 |
|
92 | self.attiny.setIRQCallback(self._IRQHandler.bind(self));
|
93 |
|
94 | });
|
95 |
|
96 |
|
97 | var listening = self.listeners('data').length ? true : false;
|
98 |
|
99 | self._setListening(listening, function listeningSet(err) {
|
100 |
|
101 | if (callback) {
|
102 | callback(err, self);
|
103 | }
|
104 | });
|
105 | }
|
106 | });
|
107 | };
|
108 |
|
109 | util.inherits(Infrared, EventEmitter);
|
110 |
|
111 | Infrared.prototype._IRQHandler = function (callback) {
|
112 | var self = this;
|
113 |
|
114 | if (!self.transmitting) {
|
115 |
|
116 | self._fetchRXDurations(function fetched () {
|
117 |
|
118 | self.attiny.setIRQCallback(self._IRQHandler.bind(self));
|
119 | });
|
120 | } else {
|
121 |
|
122 | setTimeout(self._IRQHandler.bind(self), 500);
|
123 | }
|
124 | };
|
125 |
|
126 | Infrared.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 |
|
139 | self.attiny.irq.removeAllListeners();
|
140 | }
|
141 | else {
|
142 |
|
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 |
|
153 | Infrared.prototype._fetchRXDurations = function (callback) {
|
154 | var self = this;
|
155 |
|
156 | self.attiny.transceive(new Buffer([IR_RX_AVAIL_CMD, 0x00, 0x00, 0x00]), function spiComplete (err, response) {
|
157 |
|
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 |
|
164 | var numBytes = numInt16 * 2;
|
165 |
|
166 | var rxHeader = [IR_RX_CMD, 0x00, 0x00];
|
167 | var packet = rxHeader.concat(new Array(numBytes));
|
168 |
|
169 |
|
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 |
|
182 | var buf = response.slice(rxHeader.length, response.length - 1);
|
183 |
|
184 |
|
185 | self.emit('data', buf);
|
186 | callback && callback();
|
187 | }
|
188 | });
|
189 | }
|
190 | });
|
191 | });
|
192 | };
|
193 |
|
194 | Infrared.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 |
|
216 | var tx = this._constructTXPacket(frequency, signalDurations);
|
217 |
|
218 |
|
219 | this.attiny.transceive(tx, function spiComplete (err, response) {
|
220 | self.transmitting = false;
|
221 |
|
222 |
|
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 |
|
232 | Infrared.prototype._constructTXPacket = function (frequency, signalDurations) {
|
233 |
|
234 | var tx = [];
|
235 |
|
236 | tx.push(IR_TX_CMD);
|
237 |
|
238 | tx.push(frequency);
|
239 |
|
240 |
|
241 | tx.push(signalDurations.length / 2);
|
242 |
|
243 |
|
244 | for (var i = 0; i < signalDurations.length; i++) {
|
245 |
|
246 | tx.push(signalDurations.readUInt8(i));
|
247 | }
|
248 |
|
249 | tx.push(0x00);
|
250 |
|
251 |
|
252 | tx.push(FIN_CONF);
|
253 |
|
254 | return new Buffer(tx);
|
255 | };
|
256 |
|
257 |
|
258 |
|
259 | function use (hardware, callback) {
|
260 | return new Infrared(hardware, callback);
|
261 | }
|
262 |
|
263 |
|
264 |
|
265 |
|
266 |
|
267 | exports.Infrared = Infrared;
|
268 | exports.use = use;
|