johnny-five-electron
Version:
Temporary fork to support Electron (to be deprecated)
437 lines (365 loc) • 10.1 kB
JavaScript
var Board = require("../board.js");
var nanosleep = require("../sleep.js").nano;
var __ = require("../fn.js");
var priv = new Map();
var Controllers = {
DEFAULT: {
initialize: {
value: function(opts) {
RGB.colors.forEach(function(color, index) {
var pin = opts.pins[index];
if (!this.board.pins.isPwm(pin)) {
Board.Pins.Error({
pin: pin,
type: "PWM",
via: "Led.RGB"
});
}
this.io.pinMode(pin, this.io.MODES.PWM);
this.pins[index] = pin;
}, this);
}
},
write: {
writable: true,
value: function(colors) {
var state = priv.get(this);
RGB.colors.forEach(function(color, index) {
var pin = this.pins[index];
var value = colors[color];
if (state.isAnode) {
value = 255 - Board.constrain(value, 0, 255);
}
this.io.analogWrite(pin, value);
}, this);
}
}
},
PCA9685: {
REGISTER: {
value: {
PCA9685_MODE1: 0x0,
PCA9685_PRESCALE: 0xFE,
LED0_ON_L: 0x6
}
},
initialize: {
value: function(opts) {
this.address = opts.address || 0x40;
if (!this.board.Drivers[this.address]) {
this.io.i2cConfig(opts);
this.board.Drivers[this.address] = {
initialized: false
};
// Reset
this.io.i2cWrite(this.address, [this.REGISTER.PCA9685_MODE1, 0x0]);
// Sleep
this.io.i2cWrite(this.address, [this.REGISTER.PCA9685_MODE1, 0x10]);
// Set prescalar
this.io.i2cWrite(this.address, [this.REGISTER.PCA9685_PRESCALE, 0x70]);
// Wake up
this.io.i2cWrite(this.address, [this.REGISTER.PCA9685_MODE1, 0x0]);
// Wait 5 nanoseconds for restart
nanosleep(5);
// Auto-increment
this.io.i2cWrite(this.address, [this.REGISTER.PCA9685_MODE1, 0xa1]);
this.board.Drivers[this.address].initialized = true;
}
RGB.colors.forEach(function(color, index) {
var pin = opts.pins[index];
this.pins[index] = pin;
}, this);
}
},
write: {
writable: true,
value: function(colors) {
var state = priv.get(this);
RGB.colors.forEach(function(color, index) {
var pin = this.pins[index];
var value = colors[color];
var on, off;
if (state.isAnode) {
value = 255 - Board.constrain(value, 0, 255);
}
on = 0;
off = value * 4095 / 255;
// Special value for fully off
if (state.isAnode && value === 255) {
on = 4096;
off = 0;
}
this.io.i2cWrite(this.address, [this.REGISTER.LED0_ON_L + 4 * pin, on, on >> 8, off, off >> 8]);
}, this);
}
}
},
BLINKM: {
REGISTER: {
value: {
GO_TO_RGB_COLOR_NOW: 0x6e,
STOP_SCRIPT: 0x6f
}
},
initialize: {
value: function(opts) {
this.address = opts.address || 0x09;
if (!this.board.Drivers[this.address]) {
this.io.i2cConfig(opts);
this.board.Drivers[this.address] = {
initialized: false
};
// Stop the current script
this.io.i2cWrite(this.address, [this.REGISTER.STOP_SCRIPT]);
this.board.Drivers[this.address].initialized = true;
}
}
},
write: {
writable: true,
value: function(colors) {
this.io.i2cWrite(this.address,
[this.REGISTER.GO_TO_RGB_COLOR_NOW, colors.red, colors.green, colors.blue]);
}
}
}
};
Controllers.ESPLORA = {
initialize: {
value: function(opts) {
opts.pins = [5, 10, 9];
this.pins = [];
Controllers.DEFAULT.initialize.value.call(this, opts);
}
},
write: Controllers.DEFAULT.write
};
/**
* RGB
* @constructor
*
* @param {Object} opts [description]
* @alias Led.RGB
*/
var RGB = function(opts) {
if (!(this instanceof RGB)) {
return new RGB(opts);
}
var state;
var controller;
if (Array.isArray(opts)) {
// RGB([Byte, Byte, Byte]) shorthand
// Convert to opts.pins array definition
opts = {
pins: opts
};
// If opts.pins is an object, convert to array
} else if (typeof opts.pins === "object" && !Array.isArray(opts.pins)) {
opts.pins = [opts.pins.red, opts.pins.green, opts.pins.blue];
}
Board.Component.call(
this, opts = Board.Options(opts)
);
if (opts.controller && typeof opts.controller === "string") {
controller = Controllers[opts.controller.toUpperCase()];
} else {
controller = opts.controller;
}
if (controller == null) {
controller = Controllers["DEFAULT"];
}
Object.defineProperties(this, controller);
// The default color is #ffffff, but the light will be off
state = {
red: 255,
green: 255,
blue: 255,
intensity: 100,
isAnode: opts.isAnode || false,
interval: null
};
// red, green, and blue store the raw color set via .color()
// values takes state into account, such as on/off and intensity
state.values = {
red: state.red,
green: state.green,
blue: state.blue
};
priv.set(this, state);
Object.defineProperties(this, {
isOn: {
get: function() {
return RGB.colors.some(function(color) {
return state[color] > 0;
});
}
},
isRunning: {
get: function() {
return !!state.interval;
}
},
isAnode: {
get: function () {
return state.isAnode;
}
},
values: {
get: function() {
return Object.assign({}, state.values);
}
},
update: {
value: function(colors) {
var state = priv.get(this);
var scale = state.intensity / 100;
colors = colors || this.color();
var scaledColors = RGB.colors.reduce(function(current, color) {
return (current[color] = Math.round(colors[color] * scale), current);
}, {});
this.write(scaledColors);
state.values = scaledColors;
Object.assign(state, colors);
}
}
});
this.initialize(opts);
this.off();
};
RGB.colors = ["red", "green", "blue"];
/**
* color
*
* @param {String} color Hexadecimal color string
* @param {Array} color Array of color values
* @param {Object} color object {red, green, blue}
*
* @return {RGB}
*/
RGB.prototype.color = function(red, green, blue) {
var state = priv.get(this);
var update = {};
var input;
var colors;
if (arguments.length === 0) {
// Return a copy of the state values,
// not a reference to the state object itself.
colors = this.isOn ? state : state.prev;
return RGB.colors.reduce(function(current, color) {
return (current[color] = Math.round(colors[color]), current);
}, {});
}
if (arguments.length === 1) {
input = red;
if (input == null) {
throw new Error("Led.RGB.color: invalid color (" + input + ")");
}
if (Array.isArray(input)) {
// color([Byte, Byte, Byte])
update = {
red: input[0],
green: input[1],
blue: input[2]
};
} else if (typeof input === "object") {
// colors({
// red: Byte,
// green: Byte,
// blue: Byte
// });
update = {
red: input.red,
green: input.green,
blue: input.blue
};
} else if (typeof input === "string") {
// color("#ffffff")
if (input.length === 7 && input[0] === "#") {
input = input.slice(1);
}
if (!input.match(/^[0-9A-Fa-f]{6}$/)) {
throw new Error("Led.RGB.color: invalid color (#" + input + ")");
}
// color("ffffff")
update = {
red: parseInt(input.slice(0, 2), 16),
green: parseInt(input.slice(2, 4), 16),
blue: parseInt(input.slice(4, 6), 16)
};
}
} else {
// color(Byte, Byte, Byte)
update = {
red: red,
green: green,
blue: blue
};
}
// Validate all color values before writing any values
RGB.colors.forEach(function(color) {
var value = update[color];
if (value == null) {
throw new Error("Led.RGB.color: invalid color ([" + [update.red, update.green, update.blue].join(",") + "])");
}
value = __.constrain(value, 0, 255);
update[color] = value;
}, this);
this.update(update);
return this;
};
RGB.prototype.on = function() {
var state = priv.get(this);
var colors;
// If it's not already on, we set them to the previous color
if (!this.isOn) {
colors = state.prev || { red: 255, green: 255, blue: 255 };
delete state.prev;
this.update(colors);
}
return this;
};
RGB.prototype.off = function() {
var state = priv.get(this);
// If it's already off, do nothing so the pervious state stays intact
if (this.isOn) {
state.prev = RGB.colors.reduce(function(current, color) {
return (current[color] = state[color], current);
}.bind(this), {});
this.update({ red: 0, green: 0, blue: 0 });
}
return this;
};
/**
* strobe
* @param {Number} rate Time in ms to strobe/blink
* @return {Led}
*/
RGB.prototype.strobe = function(rate) {
var state = priv.get(this);
// Avoid traffic jams
if (state.interval) {
clearInterval(state.interval);
}
state.interval = setInterval(this.toggle.bind(this), rate || 100);
return this;
};
RGB.prototype.blink = RGB.prototype.strobe;
RGB.prototype.toggle = function() {
return this[this.isOn ? "off" : "on"]();
};
RGB.prototype.stop = function() {
var state = priv.get(this);
clearInterval(state.interval);
delete state.interval;
return this;
};
RGB.prototype.intensity = function(intensity) {
var state = priv.get(this);
if (arguments.length === 0) {
return state.intensity;
}
state.intensity = __.constrain(intensity, 0, 100);
this.update();
return this;
};
module.exports = RGB;