johnny-five
Version:
The JavaScript Arduino Programming Framework.
678 lines (544 loc) • 13.4 kB
JavaScript
var Board = require("../lib/board.js");
var Pins = Board.Pins;
var priv = new Map(),
LEDS = [];
/**
* Led
* @constructor
*
* five.Led(pin);
*
* five.Led({
* pin: number
* });
*
*
* @param {Object} opts [description]
*
*/
function Led(opts) {
var pinValue;
var defaultLed;
var state;
var isFirmata = true;
if (!(this instanceof Led)) {
return new Led(opts);
}
pinValue = typeof opts === "object" ? opts.pin : opts;
// Initialize a Device instance on a Board
Board.Device.call(
this, opts = Board.Options(opts)
);
isFirmata = Pins.isFirmata(this);
if (isFirmata && typeof pinValue === "string" && pinValue[0] === "A") {
pinValue = this.io.analogPins[+pinValue.slice(1)];
}
defaultLed = this.io.defaultLed || 13;
pinValue = +pinValue;
// LED instance properties
this.value = 0;
this.interval = null;
// TODO: use pin capability checks for LED value writing.
// Create a "state" entry for privately
// storing the state of the led
LEDS.push(this);
state = {
isOn: false,
isRunning: false,
value: null,
direction: 1,
mode: null
};
priv.set(this, state);
Object.defineProperties(this, {
value: {
get: function() {
return state.value;
}
},
isOn: {
get: function() {
return !!state.value;
}
},
isRunning: {
get: function() {
return state.isRunning;
}
},
mode: {
set: function(mode) {
// set mode
// TODO: if setting to PWM, check if this pin is capable of PWM
// log error if not capable
if (state.mode !== mode) {
state.mode = mode;
this.io.pinMode(this.pin, mode);
}
},
get: function() {
return state.mode;
}
}
});
if (isFirmata && this.io.analogPins.indexOf(pinValue) !== -1) {
this.pin = isFirmata ? pinValue : this.pin;
this.mode = this.io.MODES.OUTPUT;
} else {
this.pin = typeof opts.pin === "undefined" ? defaultLed : opts.pin;
this.mode = this.io.MODES[
(opts.type && opts.type.toUpperCase()) ||
(this.board.pins.isPwm(this.pin) ? "PWM" : "OUTPUT")
];
}
}
/**
* on Turn the led on
* @return {Led}
*/
Led.prototype.on = function() {
var state = priv.get(this);
if (state.mode === this.io.MODES.OUTPUT) {
this.io.digitalWrite(this.pin, this.io.HIGH);
state.value = this.io.HIGH;
}
if (state.mode === this.io.MODES.PWM) {
// Assume we need to simply turn this all the way on, when:
// ...state.value is null
if (state.value === null) {
state.value = 255;
}
// ...there is no active interval
if (!this.interval) {
state.value = 255;
}
// ...the last value was 0
if (state.value === 0) {
state.value = 255;
}
this.io.analogWrite(this.pin, state.value);
}
return this;
};
/**
* off Turn the led off
* @return {Led}
*/
Led.prototype.off = function() {
var state = priv.get(this);
var value = 0;
if (state.mode === this.io.MODES.OUTPUT) {
this.io.digitalWrite(this.pin, value);
}
if (state.mode === this.io.MODES.PWM) {
this.io.analogWrite(this.pin, value);
}
state.value = value;
return this;
};
/**
* toggle Toggle the on/off state of an led
* @return {Led}
*/
Led.prototype.toggle = function() {
return this[this.isOn ? "off" : "on"]();
};
/**
* brightness
* @param {Number} value analog brightness value 0-255
* @return {Led}
*/
Led.prototype.brightness = function(value) {
var state = priv.get(this);
// If pin is not a PWM pin, emit an error
if (!this.board.pins.isPwm(this.pin)) {
Board.Pins.Error({
pin: this.pin,
type: "PWM",
via: "Led",
});
}
// Reset mode to PWM
this.mode = this.io.MODES.PWM;
this.io.analogWrite(this.pin, value);
state.value = value;
return this;
};
/**
* animate Animate the brightness of an led
* @param {Object} opts {
* step: function to call on each step
* delta: function to calculate each change
* complete: function to call on completion,
* duration: ms duration of animation
* delay: ms interval delay
* }
* @return {Led}
*/
Led.prototype.animate = function(opts) {
var state = priv.get(this);
var start = Date.now();
if (!this.board.pins.isPwm(this.pin)) {
Board.Pins.Error({
pin: this.pin,
type: "PWM",
via: "Led",
});
}
// Reset mode to PWM
this.mode = this.io.MODES.PWM;
// Avoid traffic jams
if (this.interval) {
clearInterval(this.interval);
}
if (!opts.delta) {
opts.delta = function(val) {
return val;
};
}
// TODO: Give opts.step a default
state.isRunning = true;
this.interval = setInterval(function() {
var lapsed = Date.now() - start;
var progress = lapsed / opts.duration;
if (progress > 1) {
progress = 1;
}
var delta = opts.delta(progress);
opts.step(delta);
if (progress === 1) {
opts.complete();
}
}, opts.delay || 10);
return this;
};
/**
* pulse Fade the Led in and out in a loop with specified time
* @param {number} rate Time in ms that a fade in/out will elapse
* @return {Led}
*/
Led.prototype.pulse = function(time, callback) {
var state = priv.get(this);
var max = 0xff;
var target = state.value !== 0 ?
(state.value === max ? 0 : max) : max;
var direction = target === max ? 1 : -1;
var update = state.value <= target ? target : (state.value - target);
if (typeof time === "function") {
callback = time;
time = 1000;
}
var step = function(delta) {
var value = (update * delta);
if (direction === -1) {
value = value ^ 0xff;
}
this.io.analogWrite(this.pin, value);
state.value = value;
state.direction = direction;
}.bind(this);
var complete = function() {
this.pulse(time);
if (typeof callback === "function") {
callback();
}
}.bind(this);
return this.animate({
duration: time || 1000,
complete: complete,
step: step
});
};
/**
* fade Fade an led in and out
* @param {Number} val Analog brightness value 0-255
* @param {Number} time Time in ms that a fade in/out will elapse
* @return {Led}
*/
Led.prototype.fade = function(val, time, callback) {
var state = priv.get(this);
var difference = val - state.value;
var direction = difference > 0 ? 1 : -1;
var previous = state.value || 0;
var update = val - state.value;
if (typeof time === "function") {
callback = time;
time = 1000;
}
var step = function(delta) {
var value = previous + (update * delta);
this.io.analogWrite(this.pin, value);
state.value = value;
}.bind(this);
var complete = function() {
this.stop();
if (typeof callback === "function") {
callback();
}
}.bind(this);
return this.animate({
duration: time || 1000,
complete: complete,
step: step
});
};
Led.prototype.fadeIn = function(time, callback) {
return this.fade(255, time || 1000, callback);
};
Led.prototype.fadeOut = function(time, callback) {
return this.fade(0, time || 1000, callback);
};
/**
* strobe
* @param {Number} rate Time in ms to strobe/blink
* @return {Led}
*/
Led.prototype.strobe = function(rate) {
var isHigh = false;
var state = priv.get(this);
// Avoid traffic jams
if (this.interval) {
clearInterval(this.interval);
}
state.isRunning = true;
this.interval = setInterval(function() {
this.toggle();
}.bind(this), rate || 100);
return this;
};
Led.prototype.blink = Led.prototype.strobe;
/**
* stop Stop the led from strobing, pulsing or fading
* @return {Led}
*/
Led.prototype.stop = function() {
var state = priv.get(this);
clearInterval(this.interval);
state.isRunning = false;
return this;
};
// TODO:
// Led.prototype.color = function() {
// ...
// return this;
// };
/**
* Led.Array()
* new Led.Array()
*
* Create an Array-like object instance of LEDS
*
* @return {Led.Array}
*/
Led.Array = function(pins) {
if (!(this instanceof Led.Array)) {
return new Led.Array(pins);
}
var leds = [];
if (pins) {
while (pins.length) {
leds.push(
new Led(pins.shift())
);
}
} else {
leds = LEDS.slice();
}
this.length = 0;
leds.forEach(function(led, index) {
this[index] = led;
this.length++;
}, this);
};
/**
* each Execute callbackFn for each active led instance in an Led.Array
* @param {Function} callbackFn
* @return {Led.Array}
*/
Led.Array.prototype.each = function(callbackFn) {
var led, i, length;
length = this.length;
for (i = 0; i < length; i++) {
led = this[i];
callbackFn.call(led, led, i);
}
return this;
};
[
"on", "off", "toggle", "brightness",
"fade", "fadeIn", "fadeOut",
"pulse", "strobe",
"stop"
].forEach(function(method) {
// Create Led.Array wrappers for each method listed.
// This will allow us control over all Led instances
// simultaneously.
Led.Array.prototype[method] = function() {
var args = [].slice.call(arguments);
this.each(function(led) {
Led.prototype[method].apply(led, args);
});
return this;
};
});
Led.Array.prototype.blink = Led.Array.prototype.strobe;
/**
* Led.RGB
*
*
* @param {[type]} opts [description]
* @return {[type]} [description]
*/
Led.RGB = function(opts) {
if (!(this instanceof Led.RGB)) {
return new Led.RGB(opts);
}
// Initialize a Device instance on a Board
Board.Device.call(
this, opts = Board.Options(opts)
);
var color, colors, k, state;
colors = Led.RGB.colors.slice();
k = -1;
// This will normalize an array of pins in [ r, g, b ]
// order to an object that's shaped like:
// {
// red: r,
// green: g,
// blue: b
// }
if (Array.isArray(opts.pins)) {
opts.pins = colors.reduce(function(pins, pin, i, list) {
return (pins[list[i]] = opts.pins[i], pins);
}, {});
}
// Initialize each Led instance
while (colors.length) {
color = colors.shift();
this[color] = new Led({
pin: opts.pins[color],
board: opts.board
});
}
var isAnode = opts.isAnode || false;
// Hack: If isAnode invert the analog signals
if (isAnode) {
var _analogWrite = this.io.analogWrite;
this.io.analogWrite = function(pin, value) {
value = 255 - Board.constrain(value, 0, 255);
_analogWrite.call(this, pin, value);
}.bind(this.io);
}
this.interval = null;
state = {
red: 255,
green: 255,
blue: 255,
isRunning: false
};
priv.set(this, state);
Object.defineProperties(this, {
isOn: {
get: function() {
return Led.RGB.colors.some(function(color) {
return this[color].isOn;
}, this);
}
},
isRunning: {
get: function() {
return state.isRunning;
}
},
values: {
get: function() {
return Led.RGB.colors.reduce(function(current, color) {
return (current[color] = this[color].value, current);
}.bind(this), {});
}
}
});
};
Led.RGB.colors = ["red", "green", "blue"];
/**
* color
*
* @param {String} color Hexadecimal color string
* @param {Array} color Array of color values
*
* @return {Led.RGB}
*/
Led.RGB.prototype.color = function(value) {
var state, update;
state = priv.get(this);
update = {
red: null,
green: null,
blue: null
};
if (!value) {
// Return a "copy" of the state values,
// not a reference to the state object itself.
return Led.RGB.colors.reduce(function(current, color) {
return (current[color] = state[color], current);
}, {});
}
// Allows hex colors with leading #:
// eg. #ff00ff
if (value[0] === "#") {
value = value.slice(1);
}
if (typeof value === "string") {
update.red = parseInt(value.slice(0, 2), 16);
update.green = parseInt(value.slice(2, 4), 16);
update.blue = parseInt(value.slice(4, 6), 16);
} else {
update.red = value[0];
update.green = value[1];
update.blue = value[2];
}
Led.RGB.colors.forEach(function(color) {
state[color] = update[color];
this[color].brightness(update[color]);
}, this);
return this;
};
Led.RGB.prototype.on = function() {
var state = priv.get(this);
// If it's not already on, we turn
// them on to previous color value
if (!this.isOn) {
Led.RGB.colors.forEach(function(color) {
this[color].brightness(state[color]);
}, this);
}
return this;
};
Led.RGB.prototype.off = function() {
Led.RGB.colors.forEach(function(color) {
this[color].off();
}, this);
return this;
};
/**
* strobe
* @param {Number} rate Time in ms to strobe/blink
* @return {Led}
*/
Led.RGB.prototype.strobe = function(rate) {
var isHigh = false;
var state = priv.get(this);
// Avoid traffic jams
if (this.interval) {
clearInterval(this.interval);
}
state.isRunning = true;
this.interval = setInterval(function() {
this.toggle();
}.bind(this), rate || 100);
return this;
};
Led.RGB.prototype.blink = Led.RGB.prototype.strobe;
Led.RGB.prototype.toggle = Led.prototype.toggle;
Led.RGB.prototype.stop = Led.prototype.stop;
module.exports = Led;