UNPKG

14 kBJavaScriptView Raw
1var Board = require("./board");
2var EVS = require("./evshield");
3var within = require("./mixins/within");
4var Fn = require("./fn");
5var Emitter = require("events").EventEmitter;
6var util = require("util");
7var priv = new Map();
8// var int16 = Fn.int16;
9var uint16 = Fn.uint16;
10var toFixed = Fn.toFixed;
11
12var Controllers = {
13 DEFAULT: {
14 initialize: {
15 value: function(opts, dataHandler) {
16 this.io.pinMode(this.pin, this.io.MODES.ANALOG);
17 this.io.analogRead(this.pin, dataHandler);
18 },
19 },
20 toIntensityLevel: {
21 value: function(raw) {
22 return toFixed(Fn.scale(raw, 0, 1023, 0, 100) / 100, 2);
23 }
24 }
25 },
26 EVS_EV3: {
27 initialize: {
28 value: function(opts, dataHandler) {
29 var state = priv.get(this);
30
31 if (opts.mode) {
32 opts.mode = opts.mode.toUpperCase();
33 }
34
35 state.mode = opts.mode === "REFLECTED" ? EVS.Type_EV3_LIGHT_REFLECTED : EVS.Type_EV3_LIGHT;
36
37 state.shield = EVS.shieldPort(opts.pin);
38 state.ev3 = new EVS(Object.assign(opts, {
39 io: this.io
40 }));
41 state.ev3.setup(state.shield, EVS.Type_EV3);
42 state.ev3.write(state.shield, 0x81 + state.shield.offset, state.mode);
43 state.ev3.read(state.shield, EVS.Light, EVS.Light_Bytes, function(data) {
44 var value = data[0] | (data[1] << 8);
45 dataHandler(value);
46 });
47 }
48 },
49 toIntensityLevel: {
50 value: function(raw) {
51 return toFixed(raw / 100, 2);
52 }
53 }
54 },
55 EVS_NXT: {
56 initialize: {
57 value: function(opts, dataHandler) {
58 var state = priv.get(this);
59
60 if (opts.mode) {
61 opts.mode = opts.mode.toUpperCase();
62 }
63
64 state.mode = opts.mode === "REFLECTED" ? EVS.Type_NXT_LIGHT_REFLECTED : EVS.Type_NXT_LIGHT;
65
66 state.shield = EVS.shieldPort(opts.pin);
67 state.ev3 = new EVS(Object.assign(opts, {
68 io: this.io
69 }));
70 state.ev3.setup(state.shield, state.mode);
71 state.ev3.read(state.shield, state.shield.analog, EVS.Analog_Bytes, function(data) {
72 var value = data[0] | (data[1] << 8);
73 dataHandler(value);
74 });
75 }
76 },
77 toIntensityLevel: {
78 value: function(raw) {
79 return toFixed(Fn.scale(raw, 0, 1023, 100, 0) / 100, 2);
80 }
81 }
82 },
83
84 TSL2561: {
85 ADDRESSES: {
86 value: [0x29, 0x39, 0x49]
87 },
88 REGISTER: {
89 value: {
90 CONTROL: 0x00,
91 TIMING: 0x01,
92 READ: 0x2C,
93 },
94 },
95
96 initialize: {
97 value: function(opts, dataHandler) {
98 var address = opts.address || 0x39;
99 var command = function(byte) {
100 // byte | 0b10000000;
101 return byte | 0x80;
102 };
103
104 opts.address = address;
105
106 this.io.i2cConfig(opts);
107
108 // Page 15
109 // Control Register (0h)
110 // RESV 7:2
111 // POWER 1:0
112 //
113 // Power up/power down.
114 // By writing a 03h to this register, the device is powered up.
115 // By writing a 00h to this register, the device is powered down.
116 //
117 // 0b00000011 = 0x03
118 // 0b00000000 = 0x00
119 this.io.i2cWriteReg(address, command(this.REGISTER.CONTROL), 0x03);
120
121 // Gain & Integration
122 // var isAutoGain = false;
123
124 // Page 24
125 // Integration time scaling factors
126 var LUX_SCALE = 14; // scale by (2 ** 14)
127 var RATIO_SCALE = 9; // scale ratio by (2 ** 9)
128
129 // Page 24
130 // T, FN, and CL Package coefficients
131 var K1T = 0x0040; // 0.125 * (2 ** RATIO_SCALE)
132 var B1T = 0x01F2; // 0.0304 * (2 ** LUX_SCALE)
133 var M1T = 0x01BE; // 0.0272 * (2 ** LUX_SCALE)
134 var K2T = 0x0080; // 0.250 * (2 ** RATIO_SCALE)
135 var B2T = 0x0214; // 0.0325 * (2 ** LUX_SCALE)
136 var M2T = 0x02D1; // 0.0440 * (2 ** LUX_SCALE)
137 var K3T = 0x00C0; // 0.375 * (2 ** RATIO_SCALE)
138 var B3T = 0x023F; // 0.0351 * (2 ** LUX_SCALE)
139 var M3T = 0x037B; // 0.0544 * (2 ** LUX_SCALE)
140 var K4T = 0x0100; // 0.50 * (2 ** RATIO_SCALE)
141 var B4T = 0x0270; // 0.0381 * (2 ** LUX_SCALE)
142 var M4T = 0x03FE; // 0.0624 * (2 ** LUX_SCALE)
143 var K5T = 0x0138; // 0.61 * (2 ** RATIO_SCALE)
144 var B5T = 0x016F; // 0.0224 * (2 ** LUX_SCALE)
145 var M5T = 0x01FC; // 0.0310 * (2 ** LUX_SCALE)
146 var K6T = 0x019A; // 0.80 * (2 ** RATIO_SCALE)
147 var B6T = 0x00D2; // 0.0128 * (2 ** LUX_SCALE)
148 var M6T = 0x00FB; // 0.0153 * (2 ** LUX_SCALE)
149 var K7T = 0x029A; // 1.3 * (2 ** RATIO_SCALE)
150 var B7T = 0x0018; // 0.00146 * (2 ** LUX_SCALE)
151 var M7T = 0x0012; // 0.00112 * (2 ** LUX_SCALE)
152 var K8T = 0x029A; // 1.3 * (2 ** RATIO_SCALE)
153 var B8T = 0x0000; // 0.000 * (2 ** LUX_SCALE)
154 var M8T = 0x0000; // 0.000 * (2 ** LUX_SCALE)
155
156 // Auto-gain thresholds
157 // Max value at Ti 13ms = 5047
158 // var AGT_LO_13MS = 100;
159 // var AGT_HI_13MS = 4850;
160
161 // // Max value at Ti 101ms = 37177
162 // var AGT_LO_101MS = 200;
163 // var AGT_HI_101MS = 36000;
164
165 // // Max value at Ti 402ms = 65535
166 // var AGT_LO_402MS = 500;
167 // var AGT_HI_402MS = 63000;
168
169 // var agtRanges = [
170 // // 0, TI_13MS
171 // [100, 4850],
172 // // 1, TI_101MS
173 // [200, 36000],
174 // // 2, TI_402MS
175 // [500, 63000],
176 // ];
177
178 // var CLIPPING_13MS = 4900;
179 // var CLIPPING_101MS = 37000;
180 // var CLIPPING_402MS = 65000;
181
182 // var clipping = [
183 // // 0, TI_13MS
184 // 4900,
185 // // 1, TI_101MS
186 // 37000,
187 // // 2, TI_402MS
188 // 65000,
189 // ];
190
191
192 var GAIN_1X = 0x00;
193 var GAIN_16X = 0x10;
194
195 // var TI_13MS = 0x00;
196 // var TI_101MS = 0x01;
197 // var TI_402MS = 0x02;
198
199 var TintMs = [
200 // 0, TI_13MS
201 13,
202 // 1, TI_101MS
203 101,
204 // 2, TI_402MS
205 402,
206 ];
207
208 var TintDelayMs = [
209 // 0, TI_13MS
210 15,
211 // 1, TI_101MS
212 120,
213 // 2, TI_402MS
214 450,
215 ];
216
217 // Page 23 - 28
218 // Simplified Lux Calculation
219 // var CH_SCALE_D = 0x0010;
220 // var CH_SCALE_0 = 0x7517;
221 // var CH_SCALE_1 = 0x0FE7;
222
223 var chScales = [
224 // 0, TI_13MS
225 0x07517,
226 // 1, TI_101MS
227 0x00FE7,
228 // 2, TI_402MS
229 0x10000,
230 ];
231
232 // Gain and Tint defaults;
233 var gain = GAIN_16X;
234 var TintIndex = 0;
235 var Tint = TintMs[TintIndex];
236 var lux = 0;
237
238 // if (typeof opts.gain !== "undefined") {
239 // isAutoGain = false;
240 // gain = opts.gain;
241 // }
242
243 // if (typeof opts.integration !== "undefined") {
244 // isAutoGain = false;
245 // Tint = opts.integration;
246 // }
247
248
249 // TODO: reduce duplication here
250 Object.defineProperties(this, {
251 gain: {
252 get: function() {
253 return gain;
254 },
255 set: function(value) {
256 if (value !== GAIN_1X && value !== GAIN_16X) {
257 throw new RangeError("Invalid gain. Expected one of: 0, 16");
258 }
259 gain = value;
260
261 this.io.i2cWriteReg(address, command(this.REGISTER.TIMING), TintIndex | gain);
262 }
263 },
264 integration: {
265 get: function() {
266 return Tint;
267 },
268 set: function(value) {
269 TintIndex = TintMs.indexOf(value);
270
271 if (TintIndex === -1) {
272 throw new RangeError("Invalid integration. Expected one of: 13, 101, 402");
273 }
274
275 Tint = value;
276
277 this.io.i2cWriteReg(address, command(this.REGISTER.TIMING), TintIndex | gain);
278 }
279 },
280 lux: {
281 get: function() {
282 return lux;
283 }
284 }
285 });
286
287 // Assign the default gain and integration values
288 // These are validated and written to the device.
289 // These will invoke the accessors above.
290 this.gain = gain;
291 this.integration = Tint;
292
293 // Page 1
294 // Description
295 // Page 2
296 // Functional Block Diagram
297 // var data = {
298 // broadband: null,
299 // infrared: null,
300 // };
301
302 var read = function() {
303 setTimeout(function() {
304 // Page 19
305 // Read ADC Channels Using Read Word Protocol − RECOMMENDED
306 this.io.i2cReadOnce(address, command(this.REGISTER.READ), 4, function(data) {
307 // Page 23 - 28
308 // Simplified Lux Calculation
309 var ch0 = uint16(data[1], data[0]);
310 var ch1 = uint16(data[3], data[2]);
311 var b = 0;
312 var m = 0;
313
314 // Page 26
315 // CalculateLux(...)
316 var chScale = chScales[TintIndex];
317
318
319 if (!gain) {
320 chScale = chScale << 4;
321 }
322
323 // Page 27
324 // CalculateLux(...)
325 ch0 = (ch0 * chScale) >> 10;
326 ch1 = (ch1 * chScale) >> 10;
327
328 var ratio1 = 0;
329
330 if (ch0) {
331 ratio1 = (ch1 << (RATIO_SCALE + 1)) / ch0;
332 }
333
334 ratio1 = Math.round(ratio1);
335
336 var ratio = (ratio1 + 1) >> 1;
337
338 if (ratio >= 0 && ratio <= K1T) {
339 b = B1T;
340 m = M1T;
341 } else if (ratio <= K2T) {
342 b = B2T;
343 m = M2T;
344 } else if (ratio <= K3T) {
345 b = B3T;
346 m = M3T;
347 } else if (ratio <= K4T) {
348 b = B4T;
349 m = M4T;
350 } else if (ratio <= K5T) {
351 b = B5T;
352 m = M5T;
353 } else if (ratio <= K6T) {
354 b = B6T;
355 m = M6T;
356 } else if (ratio <= K7T) {
357 b = B7T;
358 m = M7T;
359 } else if (ratio > K8T) {
360 b = B8T;
361 m = M8T;
362 }
363 // I followed the datasheet and it had no else clause here.
364
365 var temp = (ch0 * b) - (ch1 * m);
366
367 if (temp < 0) {
368 temp = 0;
369 }
370
371 temp += 1 << (LUX_SCALE - 1);
372
373 // Updating the `lux` binding
374 // in the outer scope.
375 lux = temp >>> LUX_SCALE;
376
377 dataHandler(lux);
378 read();
379 });
380 }.bind(this), TintDelayMs[TintIndex]);
381 }.bind(this);
382
383 read();
384 }
385 },
386 toLux: {
387 value: function(raw) {
388 return raw;
389 },
390 },
391 toIntensityLevel: {
392 value: function(raw) {
393 return toFixed(Fn.scale(raw, 0, 17000, 0, 100) / 100, 2);
394 },
395 },
396 },
397 BH1750: {
398 // code based on Arduino library https://github.com/claws/BH1750
399 // currently only "continuous H-resolution" mode supported
400 ADDRESSES: {
401 value: [0x23, 0x5C]
402 },
403 initialize: {
404 value: function(opts, dataHandler) {
405 var address = opts.address || 0x23;
406 var mode = opts.mode || 0x10;
407 opts.address = address;
408 this.io.i2cConfig(opts);
409 this.io.i2cWrite(address, mode);
410 var read = function() {
411 setTimeout(function() {
412 this.io.i2cReadOnce(address, 2, function(data) {
413 var raw = data[0];
414 raw <<= 8;
415 raw |= data[1];
416 dataHandler(raw);
417 read();
418 });
419 }.bind(this), 120);
420 }.bind(this);
421 read();
422 },
423 },
424 toLux: {
425 value: function(raw) {
426 // Page 2
427 // H-Resolution Mode Resolution rHR - 1 - lx
428 return Math.round(raw / 1.2);
429 },
430 },
431 toIntensityLevel: {
432 value: function(raw) {
433 return toFixed(Fn.scale(raw / 1.2, 0, 65535, 0, 100) / 100, 2);
434 },
435 },
436 },
437};
438
439Controllers.ALSPT19 = Controllers["ALS-PT19"] = Controllers.DEFAULT;
440
441
442/**
443 * Light
444 * @constructor
445 *
446 */
447
448function Light(opts) {
449
450 if (!(this instanceof Light)) {
451 return new Light(opts);
452 }
453
454 var controller = null;
455 var state = {};
456 var raw = 0;
457 var last = 0;
458 var freq = opts.freq || 25;
459
460 Board.Component.call(
461 this, opts = Board.Options(opts)
462 );
463
464 if (typeof opts.controller === "string") {
465 controller = Controllers[opts.controller];
466 } else {
467 controller = opts.controller || Controllers.DEFAULT;
468 }
469
470 Board.Controller.call(this, controller, opts);
471
472 if (!this.toIntensityLevel) {
473 this.toIntensityLevel = opts.toIntensityLevel || function(x) {
474 return x;
475 };
476 }
477
478 if (!this.toLux) {
479 this.toLux = opts.toLux || function(x) {
480 return x;
481 };
482 }
483
484 Object.defineProperties(this, {
485 value: {
486 get: function() {
487 return raw;
488 },
489 },
490 level: {
491 get: function() {
492 return this.toIntensityLevel(raw);
493 },
494 },
495 });
496
497 priv.set(this, state);
498
499 /* istanbul ignore else */
500 if (typeof this.initialize === "function") {
501 this.initialize(opts, function(data) {
502 raw = data;
503 });
504 }
505
506 if (typeof this.lux === "undefined") {
507 Object.defineProperty(this, "lux", {
508 get: function() {
509 return this.toLux(raw);
510 },
511 });
512 }
513
514 var data = {
515 level: 0,
516 lux: 0,
517 };
518
519 setInterval(function() {
520 data.level = this.level;
521 data.lux = this.lux;
522
523 this.emit("data", data);
524
525 if (raw !== last) {
526 last = raw;
527 this.emit("change", data);
528 }
529 }.bind(this), freq);
530}
531
532util.inherits(Light, Emitter);
533
534Object.assign(Light.prototype, within);
535
536
537/* istanbul ignore else */
538if (!!process.env.IS_TEST_MODE) {
539 Light.Controllers = Controllers;
540 Light.purge = function() {
541 priv.clear();
542 };
543}
544
545module.exports = Light;
546