UNPKG

10.8 kBJavaScriptView Raw
1const Board = require("../board");
2const Animation = require("../animation");
3const Expander = require("../expander");
4const { constrain, map, scale } = require("../fn");
5const Pins = Board.Pins;
6const priv = new Map();
7
8const Controllers = {
9 PCA9685: {
10 initialize: {
11 value({address, pwmRange, frequency, pin}) {
12
13 const state = priv.get(this);
14
15 this.address = address || 0x40;
16 this.pwmRange = pwmRange || [0, 4095];
17 this.frequency = frequency || 200;
18
19 state.expander = Expander.get({
20 address: this.address,
21 controller: this.controller,
22 bus: this.bus,
23 pwmRange: this.pwmRange,
24 frequency: this.frequency,
25 });
26
27 this.pin = state.expander.normalize(pin);
28
29 state.mode = this.io.MODES.PWM;
30 }
31 },
32 update: {
33 writable: true,
34 value(input) {
35 const state = priv.get(this);
36 const output = typeof input !== "undefined" ? input : state.value;
37 const value = state.isAnode ? 255 - Board.constrain(output, 0, 255) : output;
38 this.write(value);
39 }
40 },
41 write: {
42 writable: true,
43 value(value) {
44 const state = priv.get(this);
45 state.expander.analogWrite(this.pin, value);
46 }
47 }
48 },
49 DEFAULT: {
50 initialize: {
51 value({pin}, pinValue) {
52
53 const state = priv.get(this);
54 const isFirmata = Pins.isFirmata(this);
55 let defaultLed;
56
57 if (isFirmata && typeof pinValue === "string" &&
58 (pinValue.length > 1 && pinValue[0] === "A")) {
59 pinValue = this.io.analogPins[+pinValue.slice(1)];
60 }
61
62 defaultLed = this.io.defaultLed || 13;
63 pinValue = +pinValue;
64
65 if (isFirmata && this.io.analogPins.includes(pinValue)) {
66 this.pin = pinValue;
67 state.mode = this.io.MODES.OUTPUT;
68 } else {
69 this.pin = typeof pin === "undefined" ? defaultLed : pin;
70 state.mode = this.io.MODES[
71 (this.board.pins.isPwm(this.pin) ? "PWM" : "OUTPUT")
72 ];
73 }
74
75 this.io.pinMode(this.pin, state.mode);
76 }
77 },
78 update: {
79 writable: true,
80 value(input) {
81 const state = priv.get(this);
82 const output = typeof input !== "undefined" ? input : state.value;
83 let value = state.isAnode ? 255 - Board.constrain(output, 0, 255) : output;
84 value = map(value, 0, 255, 0, this.board.RESOLUTION.PWM);
85
86 // If pin is not a PWM pin and brightness is not HIGH or LOW, emit an error
87 if (value !== this.io.LOW && value !== this.io.HIGH && this.mode !== this.io.MODES.PWM) {
88 Board.Pins.Error({
89 pin: this.pin,
90 type: "PWM",
91 via: "Led"
92 });
93 }
94
95 if (state.mode === this.io.MODES.OUTPUT) {
96 value = output;
97 }
98
99 this.write(value);
100 }
101 },
102 write: {
103 writable: true,
104 value(value) {
105 const state = priv.get(this);
106
107 if (state.mode === this.io.MODES.OUTPUT) {
108 this.io.digitalWrite(this.pin, value);
109 }
110
111 if (state.mode === this.io.MODES.PWM) {
112 this.io.analogWrite(this.pin, value);
113 }
114 }
115 }
116 }
117};
118
119/**
120 * Led
121 * @constructor
122 *
123 * five.Led(pin);
124 *
125 * five.Led({
126 * pin: number
127 * });
128 *
129 *
130 * @param {Object} opts [description]
131 *
132 */
133
134class Led {
135 constructor(options) {
136 const pinValue = typeof options === "object" ? options.pin : options;
137
138 Board.Component.call(
139 this, options = Board.Options(options)
140 );
141
142 Board.Controller.call(
143 this, Controllers, options
144 );
145
146 const state = {
147 isAnode: options.isAnode,
148 isOn: false,
149 isRunning: false,
150 value: null,
151 direction: 1,
152 mode: null,
153 intensity: 0,
154 interval: null
155 };
156
157 priv.set(this, state);
158
159 Object.defineProperties(this, {
160 value: {
161 get() {
162 return state.value;
163 }
164 },
165 mode: {
166 get() {
167 return state.mode;
168 }
169 },
170 isOn: {
171 get() {
172 return !!state.value;
173 }
174 },
175 isRunning: {
176 get() {
177 return state.isRunning;
178 }
179 },
180 animation: {
181 get() {
182 return state.animation;
183 }
184 }
185 });
186
187 /* istanbul ignore else */
188 if (typeof this.initialize === "function") {
189 this.initialize(options, pinValue);
190 }
191 }
192
193 /**
194 * on Turn the led on
195 * @return {Led}
196 */
197
198 on() {
199 const state = priv.get(this);
200
201 if (state.mode === this.io.MODES.OUTPUT) {
202 state.value = this.io.HIGH;
203 }
204
205 if (state.mode === this.io.MODES.PWM) {
206 // Assume we need to simply turn this all the way on, when:
207
208 // ...state.value is null
209 if (state.value === null) {
210 state.value = 255;
211 }
212
213 // ...there is no active interval
214 if (!state.interval) {
215 state.value = 255;
216 }
217
218 // ...the last value was 0
219 if (state.value === 0) {
220 state.value = 255;
221 }
222 }
223
224 this.update();
225
226 return this;
227 }
228
229 /**
230 * off Turn the led off
231 * @return {Led}
232 */
233 off() {
234 const state = priv.get(this);
235
236 state.value = 0;
237
238 this.update();
239
240 return this;
241 }
242
243 /**
244 * toggle Toggle the on/off state of an led
245 * @return {Led}
246 */
247 toggle() {
248 return this[this.isOn ? "off" : "on"]();
249 }
250
251 /**
252 * brightness
253 * @param {Number} value analog brightness value 0-255
254 * @return {Led}
255 */
256 brightness(brightness) {
257 const state = priv.get(this);
258 state.value = brightness;
259
260 this.update();
261
262 return this;
263 }
264
265 /**
266 * intensity
267 * @param {Number} value Light intensity 0-100
268 * @return {Led}
269 */
270 intensity(intensity) {
271 const state = priv.get(this);
272
273 if (arguments.length === 0) {
274 return state.intensity;
275 }
276
277 state.intensity = constrain(intensity, 0, 100);
278
279 return this.brightness(scale(state.intensity, 0, 100, 0, 255));
280 }
281
282 /**
283 * Animation.normalize
284 *
285 * @param [number || object] keyFrames An array of step values or a keyFrame objects
286 */
287
288 [Animation.normalize](keyFrames) {
289 const state = priv.get(this);
290
291 // If user passes null as the first element in keyFrames use current value
292 /* istanbul ignore else */
293 if (keyFrames[0] === null) {
294 keyFrames[0] = {
295 value: state.value || 0
296 };
297 }
298
299 return keyFrames.map(frame => {
300 const value = frame;
301 /* istanbul ignore else */
302 if (frame !== null) {
303 // frames that are just numbers represent values
304 if (typeof frame === "number") {
305 frame = {
306 value,
307 };
308 } else {
309 if (typeof frame.brightness === "number") {
310 frame.value = frame.brightness;
311 delete frame.brightness;
312 }
313 if (typeof frame.intensity === "number") {
314 frame.value = scale(frame.intensity, 0, 100, 0, 255);
315 delete frame.intensity;
316 }
317 }
318
319 /* istanbul ignore else */
320 if (!frame.easing) {
321 frame.easing = "linear";
322 }
323 }
324 return frame;
325 });
326 }
327
328 /**
329 * Animation.render
330 *
331 * @position [number] value to set the led to
332 */
333
334 [Animation.render](position) {
335 const state = priv.get(this);
336 state.value = position[0];
337 return this.update();
338 }
339
340 /**
341 * pulse Fade the Led in and out in a loop with specified time
342 * @param {number} duration Time in ms that a fade in/out will elapse
343 * @return {Led}
344 *
345 * - or -
346 *
347 * @param {Object} val An Animation() segment config object
348 */
349
350 pulse(duration, callback) {
351 const state = priv.get(this);
352
353 this.stop();
354
355 const options = {
356 duration: typeof duration === "number" ? duration : 1000,
357 keyFrames: [0, 0xff],
358 metronomic: true,
359 loop: true,
360 easing: "inOutSine",
361 onloop() {
362 /* istanbul ignore else */
363 if (typeof callback === "function") {
364 callback();
365 }
366 }
367 };
368
369 if (typeof duration === "object") {
370 Object.assign(options, duration);
371 }
372
373 if (typeof duration === "function") {
374 callback = duration;
375 }
376
377 state.isRunning = true;
378
379 state.animation = state.animation || new Animation(this);
380 state.animation.enqueue(options);
381 return this;
382 }
383
384 /**
385 * fade Fade an led in and out
386 * @param {Number} val Analog brightness value 0-255
387 * @param {Number} duration Time in ms that a fade in/out will elapse
388 * @return {Led}
389 *
390 * - or -
391 *
392 * @param {Object} val An Animation() segment config object
393 */
394
395 fade(val, duration, callback) {
396
397 const state = priv.get(this);
398
399 this.stop();
400
401 const options = {
402 duration: typeof duration === "number" ? duration : 1000,
403 keyFrames: [null, typeof val === "number" ? val : 0xff],
404 easing: "outSine",
405 oncomplete() {
406 state.isRunning = false;
407 /* istanbul ignore else */
408 if (typeof callback === "function") {
409 callback();
410 }
411 }
412 };
413
414 if (typeof val === "object") {
415 Object.assign(options, val);
416 }
417
418 if (typeof val === "function") {
419 callback = val;
420 }
421
422 if (typeof duration === "object") {
423 Object.assign(options, duration);
424 }
425
426 if (typeof duration === "function") {
427 callback = duration;
428 }
429
430 state.isRunning = true;
431
432 state.animation = state.animation || new Animation(this);
433 state.animation.enqueue(options);
434
435 return this;
436 }
437
438 fadeIn(duration, callback) {
439 return this.fade(255, duration || 1000, callback);
440 }
441
442 fadeOut(duration, callback) {
443 return this.fade(0, duration || 1000, callback);
444 }
445
446 /**
447 * blink
448 * @param {Number} duration Time in ms on, time in ms off
449 * @return {Led}
450 */
451 blink(duration, callback) {
452 const state = priv.get(this);
453
454 // Avoid traffic jams
455 this.stop();
456
457 if (typeof duration === "function") {
458 callback = duration;
459 duration = null;
460 }
461
462 state.isRunning = true;
463
464 state.interval = setInterval(() => {
465 this.toggle();
466 if (typeof callback === "function") {
467 callback();
468 }
469 }, duration || 100);
470
471 return this;
472 }
473
474
475 /**
476 * stop Stop the led from strobing, pulsing or fading
477 * @return {Led}
478 */
479 stop() {
480 const state = priv.get(this);
481
482 if (state.interval) {
483 clearInterval(state.interval);
484 }
485
486 if (state.animation) {
487 state.animation.stop();
488 }
489
490 state.interval = null;
491 state.isRunning = false;
492
493 return this;
494 }
495}
496
497Led.prototype.strobe = Led.prototype.blink;
498
499/* istanbul ignore else */
500if (!!process.env.IS_TEST_MODE) {
501 Led.Controllers = Controllers;
502 Led.purge = function() {
503 priv.clear();
504 };
505}
506
507
508module.exports = Led;