API Docs for: 1.1.1
Show:

File: blinkstick-node/node_modules/blinkstick/blinkstick.js

/**
 * Provides access to BlinkStick devices
 *
 * @module blinkstick
 */

var isWin = /^win/.test(process.platform),
    usb;

if (isWin) {
    usb = require('./platform/windows/HID.node');
} else {
    usb = require('usb');
}

var VENDOR_ID = 0x20a0,
    PRODUCT_ID = 0x41e5,

    COLOR_KEYWORDS = {
        "aqua": "#00ffff",
        "aliceblue": "#f0f8ff",
        "antiquewhite": "#faebd7",
        "black": "#000000",
        "blue": "#0000ff",
        "cyan": "#00ffff",
        "darkblue": "#00008b",
        "darkcyan": "#008b8b",
        "darkgreen": "#006400",
        "darkturquoise": "#00ced1",
        "deepskyblue": "#00bfff",
        "green": "#008000",
        "lime": "#00ff00",
        "mediumblue": "#0000cd",
        "mediumspringgreen": "#00fa9a",
        "navy": "#000080",
        "springgreen": "#00ff7f",
        "teal": "#008080",
        "midnightblue": "#191970",
        "dodgerblue": "#1e90ff",
        "lightseagreen": "#20b2aa",
        "forestgreen": "#228b22",
        "seagreen": "#2e8b57",
        "darkslategray": "#2f4f4f",
        "darkslategrey": "#2f4f4f",
        "limegreen": "#32cd32",
        "mediumseagreen": "#3cb371",
        "turquoise": "#40e0d0",
        "royalblue": "#4169e1",
        "steelblue": "#4682b4",
        "darkslateblue": "#483d8b",
        "mediumturquoise": "#48d1cc",
        "indigo": "#4b0082",
        "darkolivegreen": "#556b2f",
        "cadetblue": "#5f9ea0",
        "cornflowerblue": "#6495ed",
        "mediumaquamarine": "#66cdaa",
        "dimgray": "#696969",
        "dimgrey": "#696969",
        "slateblue": "#6a5acd",
        "olivedrab": "#6b8e23",
        "slategray": "#708090",
        "slategrey": "#708090",
        "lightslategray": "#778899",
        "lightslategrey": "#778899",
        "mediumslateblue": "#7b68ee",
        "lawngreen": "#7cfc00",
        "aquamarine": "#7fffd4",
        "chartreuse": "#7fff00",
        "gray": "#808080",
        "grey": "#808080",
        "maroon": "#800000",
        "olive": "#808000",
        "purple": "#800080",
        "lightskyblue": "#87cefa",
        "skyblue": "#87ceeb",
        "blueviolet": "#8a2be2",
        "darkmagenta": "#8b008b",
        "darkred": "#8b0000",
        "saddlebrown": "#8b4513",
        "darkseagreen": "#8fbc8f",
        "lightgreen": "#90ee90",
        "mediumpurple": "#9370db",
        "darkviolet": "#9400d3",
        "palegreen": "#98fb98",
        "darkorchid": "#9932cc",
        "yellowgreen": "#9acd32",
        "sienna": "#a0522d",
        "brown": "#a52a2a",
        "darkgray": "#a9a9a9",
        "darkgrey": "#a9a9a9",
        "greenyellow": "#adff2f",
        "lightblue": "#add8e6",
        "paleturquoise": "#afeeee",
        "lightsteelblue": "#b0c4de",
        "powderblue": "#b0e0e6",
        "firebrick": "#b22222",
        "darkgoldenrod": "#b8860b",
        "mediumorchid": "#ba55d3",
        "rosybrown": "#bc8f8f",
        "darkkhaki": "#bdb76b",
        "silver": "#c0c0c0",
        "mediumvioletred": "#c71585",
        "indianred": "#cd5c5c",
        "peru": "#cd853f",
        "chocolate": "#d2691e",
        "tan": "#d2b48c",
        "lightgray": "#d3d3d3",
        "lightgrey": "#d3d3d3",
        "thistle": "#d8bfd8",
        "goldenrod": "#daa520",
        "orchid": "#da70d6",
        "palevioletred": "#db7093",
        "crimson": "#dc143c",
        "gainsboro": "#dcdcdc",
        "plum": "#dda0dd",
        "burlywood": "#deb887",
        "lightcyan": "#e0ffff",
        "lavender": "#e6e6fa",
        "darksalmon": "#e9967a",
        "palegoldenrod": "#eee8aa",
        "violet": "#ee82ee",
        "azure": "#f0ffff",
        "honeydew": "#f0fff0",
        "khaki": "#f0e68c",
        "lightcoral": "#f08080",
        "sandybrown": "#f4a460",
        "beige": "#f5f5dc",
        "mintcream": "#f5fffa",
        "wheat": "#f5deb3",
        "whitesmoke": "#f5f5f5",
        "ghostwhite": "#f8f8ff",
        "lightgoldenrodyellow": "#fafad2",
        "linen": "#faf0e6",
        "salmon": "#fa8072",
        "oldlace": "#fdf5e6",
        "bisque": "#ffe4c4",
        "blanchedalmond": "#ffebcd",
        "coral": "#ff7f50",
        "cornsilk": "#fff8dc",
        "darkorange": "#ff8c00",
        "deeppink": "#ff1493",
        "floralwhite": "#fffaf0",
        "fuchsia": "#ff00ff",
        "gold": "#ffd700",
        "hotpink": "#ff69b4",
        "ivory": "#fffff0",
        "lavenderblush": "#fff0f5",
        "lemonchiffon": "#fffacd",
        "lightpink": "#ffb6c1",
        "lightsalmon": "#ffa07a",
        "lightyellow": "#ffffe0",
        "magenta": "#ff00ff",
        "mistyrose": "#ffe4e1",
        "moccasin": "#ffe4b5",
        "navajowhite": "#ffdead",
        "orange": "#ffa500",
        "orangered": "#ff4500",
        "papayawhip": "#ffefd5",
        "peachpuff": "#ffdab9",
        "pink": "#ffc0cb",
        "red": "#ff0000",
        "seashell": "#fff5ee",
        "snow": "#fffafa",
        "tomato": "#ff6347",
        "white": "#ffffff",
        "yellow": "#ffff00",
        "warmwhite": "fdf5e6"	// Non-standard. Added to support CheerLights.
    };




/**
 * Initialize new BlinkStick device
 *
 * @class BlinkStick
 * @constructor
 * @param {Object} device The USB device as returned from "usb" package.
 * @param {String} [serialNumber] Serial number of the device. Used only in Windows.
 * @param {String} [manufacturer] Manufacturer of the device. Used only in Windows.
 * @param {String} [product] Product name of the device. Used only in Windows.
 */

function BlinkStick (device, serialNumber, manufacturer, product) {

    if (isWin) {
        if (device) {
            this.device = new usb.HID(device);
            this.serial = serialNumber;
            this.manufacturer = manufacturer;
            this.product = product;
        }
    } else {
        if (device) {
            device.open ();
            this.device = device;
        }
    }

    this.inverse = false;
    this.animationsEnabled = true;

    var self = this;

    this.getSerial(function (err, result) {
        if (typeof(err) === 'undefined')
        {
            self.requiresSoftwareColorPatch = self.getVersionMajor() == 1 &&
                self.getVersionMinor() >= 1 && self.getVersionMinor() <= 3;
        }
    });
}




/**
 * Returns the serial number of device.
 *
 * <pre>
 * BSnnnnnn-1.0
 * ||  |    | |- Software minor version
 * ||  |    |--- Software major version
 * ||  |-------- Denotes sequential number
 * ||----------- Denotes BlinkStick device
 * </pre>
 *
 * Software version defines the capabilities of the device
 *
 * Usage:
 *
 * @example
 *     getSerial(function(err, serial) {
 *         console.log(serial);
 *     });
 *
 * @method getSerial
 * @param {function} callback Callback to receive serial number
 */
BlinkStick.prototype.getSerial = function (callback) {
    if (isWin) {
        if (callback) callback(undefined, this.serial);
    } else {
        var self = this;
        this.device.getStringDescriptor(3, function(err, result) {
            self.serial = result;
            if (callback) callback(err, result);
        });
    }
};




/**
 * Close BlinkStick device and stop all animations
 *
 * @method close
 */
BlinkStick.prototype.close = function (callback) {
    this.stop();

    try {
        this.device.close();
    } catch (ex) {
        if (callback) callback(ex);
        return;
    }

    if (callback) callback();
};




/**
 * Stop all animations
 *
 * @method stop
 */
BlinkStick.prototype.stop = function () {
    this.animationsEnabled = false;
};




/**
 * Get the major version from serial number
 *
 * @method getVersionMajor
 * @return {Number} Major version number from serial
 */
BlinkStick.prototype.getVersionMajor = function () {
    return parseInt(this.serial.substring(this.serial.length - 3, this.serial.length - 2));
};




/**
 * Get the minor version from serial number
 *
 * @method getVersionMinor
 * @return {Number} Minor version number from serial
 */
BlinkStick.prototype.getVersionMinor = function () {
    return parseInt(this.serial.substring(this.serial.length - 1, this.serial.length));
};




/**
 * Get the manufacturer of the device
 *
 * Usage:
 *
 * @example
 *     getManufacturer(function(err, data) {
 *         console.log(data);
 *     });
 *
 * @method getManufacturer
 * @param {function} callback Callback to receive manufacturer name
 */
BlinkStick.prototype.getManufacturer = function (callback) {
    if (isWin) {
        if (callback) callback(undefined, this.manufacturer);
    } else {
        this.device.getStringDescriptor(1, function(err, result) {
            if (callback) callback(err, result);
        });
    }
};




/**
 * Get the description of the device
 *
 * Usage:
 *
 * @example
 *     getDescription(function(err, data) {
 *         console.log(data);
 *     });
 *
 * @method getDescription
 * @param {function} callback Callback to receive description
*/
BlinkStick.prototype.getDescription = function (callback) {
    if (isWin) {
        if (callback) callback(undefined, this.product);
    } else {
        this.device.getStringDescriptor(2, function(err, result) {
            if (callback) callback(err, result);
        });
    }
};




/**
 * Determines report ID and number of LEDs for the report
 *
 * @private
 * @method _determineReportId
 * @return {object} data.reportId and data.ledCount
*/
function _determineReportId(ledCount)
{
    var reportId = 9;
    var maxLeds = 64;

    if (ledCount <= 8 * 3) {
        reportId = 6;
        maxLeds = 8;
    } else if (ledCount <= 16 * 3) {
        reportId = 7;
        maxLeds = 16;
    } else if (ledCount <= 32 * 3) {
        reportId = 8;
        maxLeds = 32;
    }

    return { 'reportId': reportId, 'maxLeds': maxLeds };
}




/**
 * Set the color of LEDs
 *
 * @example
 *     //Available overloads
 *     setColor(red, green, blue, [options], [callback]); // use [0..255] ranges for intensity
 *
 *     setColor(color, [options], [callback]); // use '#rrggbb' format
 *
 *     setColor(color_name, [options], [callback]); // use 'random', 'red', 'green', 'yellow' and other CSS supported names
 *
 * @method setColor
 * @param {Number|String} red Red color intensity 0 is off, 255 is full red intensity OR string CSS color keyword OR hex color, eg "#BADA55".
 * @param {Number} [green] Green color intensity 0 is off, 255 is full green intensity.
 * @param {Number} [blue] Blue color intensity 0 is off, 255 is full blue intensity.
 * @param {Object}   [options] additional options {"channel": 0, "index": 0}. Channel is represented as 0=R, 1=G, 2=B
 * @param {Function} [callback] Callback, called when complete.
 */
BlinkStick.prototype.setColor = function (red, green, blue, options, callback) {
    var params = this.interpretParameters(red, green, blue, options, callback);

    if (typeof(params) === 'undefined')
    {
        return;
    }

    var self = this;

    var sendColorInternal = function (r, g, b, callback) {
        try {
            if (params.options.channel === 0 && params.options.index === 0) {
                self.setFeatureReport(1, [1, r, g, b], callback);
            } else {
                self.setFeatureReport(5, [5, params.options.channel, params.options.index, r, g, b], callback);
            }
        } catch (ex) {
            if (callback) callback(ex);
        }
    };

    if (this.requiresSoftwareColorPatch) {
        this.getColor(function (err, cr, cg, cb) {
            if (typeof(err) !== 'undefined') {
                if (params.callback) params.callback(err);
                return;
            }

            if (params.red == cg && params.green == cr && params.blue == cb) {
                if (cr > 0) {
                    cr = cr - 1;
                } else if (cg > 0) {
                    cg = cg - 1;
                }

                sendColorInternal(cr, cg, cb, function (err) {
                    if (typeof(err) !== 'undefined') {
                        if (params.callback) params.callback(err);
                        return;
                    }

                    sendColorInternal(params.red, params.green, params.blue, function (err) {
                        if (typeof(err) !== 'undefined') {
                            if (params.callback) params.callback(err);
                            return;
                        } else {
                            if (params.callback) params.callback();
                        }
                    });
                });
            } else {
                sendColorInternal(params.red, params.green, params.blue, function (err) {
                    if (typeof(err) !== 'undefined') {
                        if (params.callback) params.callback(err);
                        return;
                    } else {
                        if (params.callback) params.callback();
                    }
                });
            }
        });
    } else {
        sendColorInternal(params.red, params.green, params.blue, params.callback);
    }
};




/**
 * Set inverse mode for IKEA DIODER in conjunction with BlinkStick v1.0
 *
 * @method setInverse
 * @param {Boolean} inverse Set true for inverse mode and false otherwise
 */
BlinkStick.prototype.setInverse = function (inverse) {
    this.inverse = inverse;
};




/**
 * Get inverse mode setting for IKEA DIODER in conjunction with BlinkStick v1.0
 *
 * @method getInverse
 * @return {Boolean} true for enabled inverse mode and false otherwise
 */
BlinkStick.prototype.getInverse = function (inverse) {
    return this.inverse;
};




/**
 * Set mode for BlinkStick Pro
 *
 * - 0 = Normal
 * - 1 = Inverse
 * - 2 = WS2812
 *
 * You can read more about BlinkStick modes by following this link:
 *
 * http://www.blinkstick.com/help/tutorials/blinkstick-pro-modes
 *
 * @method setMode
 * @param {Number} mode Set the desired mode for BlinkStick Pro
 */
BlinkStick.prototype.setMode = function (mode, callback) {
    this.setFeatureReport(0x0004, [4, mode], callback);
};




/**
 * Get mode for BlinkStick Pro
 *
 * - 0 = Normal
 * - 1 = Inverse
 * - 2 = WS2812
 *
 * You can read more about BlinkStick modes by following this link:
 *
 * http://www.blinkstick.com/help/tutorials/blinkstick-pro-modes
 *
 * Usage:
 *
 * @example
 *     getMode(function(err, data) {
 *         console.log(data);
 *     });
 *
 * @method getMode
 * @param {callback} callback receive mode with callback
 */
BlinkStick.prototype.getMode = function (callback) {
    try
    {
        this.getFeatureReport(4, 33, function (err, buffer) {
            if (callback) callback(err, buffer[1]);
        });
    }
    catch (err)
    {
        if (callback) callback(err, 0);
    }
};




/**
 * Get the current color visible on BlinkStick
 *
 * Function supports the following overloads:
 *
 * @example
 *     //Available overloads
 *     getColor(callback); //index defaults to 0
 *
 *     getColor(index, callback);
 *
 * @example
 *     getColor(0, function(err, r, g, b) {
 *         console.log(r, g, b);
 *     });
 *
 * @method getColor
 * @param {Number=0} index The index of the LED 
 * @param {Function} callback Callback to which to pass the color values.
 * @return {Number, Number, Number} Callback returns three numbers: R, G and B [0..255].
 */
BlinkStick.prototype.getColor = function (index, callback) {
    if (typeof(index) == 'function') {
        callback = index;
        index = 0;
    }

    if (index === 0) {
        this.getFeatureReport(0x0001, 33, function (err, buffer) {
            if (callback) {
                if (typeof(err) === 'undefined') {
                    if (buffer) {
                        callback(err, buffer[1], buffer[2], buffer[3]);
                    } else {
                        callback(err, 0, 0, 0);
                    }
                } else {
                    callback(err);
                }
            }
        });
    } else {
        this.getColors(index, function(err, buffer) {
            if (callback) {
                if (typeof(err) === 'undefined') {
                    callback(err, buffer[index * 3 + 1], buffer[index * 3], buffer[index * 3 + 2]);
                } else {
                    callback(err);
                }
            }
        });
    }
};




/**
 * Get the current color frame on BlinkStick Pro
 *
 * Usage:
 *
 * @example
 *     getColors(8, function(err, data) {
 *         console.log(data);
 *     });
 *
 * @method getColors
 * @param {Number} count How many LEDs should return
 * @param {Function} callback Callback to which to pass the color values.
 * @return {Array} Callback returns an array of LED data in the following format: [g0, r0, b0, g1, r1, b1...]
 */
BlinkStick.prototype.getColors = function (count, callback) {
    params = _determineReportId(count);

    this.getFeatureReport(params.reportId, params.maxLeds * 3 + 2, function (err, buffer) {
        if (callback) {
            if (typeof(buffer) !== 'undefined') {
                buffer = buffer.slice(2, buffer.length -1);
            }

            callback(err, buffer);
        }
    });
};




/**
 * Set the color frame on BlinkStick Pro
 *
 * @example
 *     var data = [255, 0, 0, 0, 255, 0];
 *
 *     setColors(0, data, function(err) {
 *     });
 *
 * @method setColors
 * @param {Number} channel Channel is represented as 0=R, 1=G, 2=B
 * @param {Array} data LED data in the following format: [g0, r0, b0, g1, r1, b1...]
 * @param {Function} callback Callback when the operation completes
 */
BlinkStick.prototype.setColors = function (channel, data, callback) {
    params = _determineReportId(data.length);

    var i = 0;

    report = [params.reportId, channel];

    data.forEach(function(item) {
        if (i < params.maxLeds * 3) {
            report.push(item);
            i += 1;
        }
    });

    for (var j = i; j < params.maxLeds * 3; j++) {
        report.push(0);
    }

    this.setFeatureReport(params.reportId, report, callback);
};




/**
 * Converts decimal number to hex with zero padding
 *
 * @private
 * @method decimalToHex
 * @param {Number} d Decimal number to convert
 * @param {Number} padding How many zeros to use for padding
 * @return {String} Decimal number converted to hex string
*/
function decimalToHex(d, padding) {
    var hex = Number(d).toString(16);
    padding = typeof (padding) === "undefined" || padding === null ? padding = 2 : padding;

    while (hex.length < padding) {
        hex = "0" + hex;
    }

    return hex;
}

/**
 * Get the current color as hex string.
 *
 * Function supports the following overloads:
 *
 * @example
 *     //Available overloads
 *     getColorString(callback); //index defaults to 0
 *
 *     getColorString(index, callback);
 *
 * @example
 *     getColorString(0, function(err, color) {
 *         console.log(color);
 *     });
 *
 *     getColorString(function(err, color) {
 *         console.log(color);
 *     });
 *
 * @method getColorString
 * @param {Number} index The index of the LED to retrieve data
 * @param {Function} callback Callback to which to pass the color string.
 * @return {String} Hex string, eg "#BADA55".
 */
BlinkStick.prototype.getColorString = function (index, callback) {
    if (typeof(index) == 'function') {
        callback = index;
        index = 0;
    }

    this.getColor(index, function (err, r, g, b) {
        if (callback) {
            if (typeof(err) === 'undefined') {
                callback(err, '#' + decimalToHex(r, 2) + decimalToHex(g, 2) + decimalToHex(b, 2) );
            } else { 
                callback(err);
            }
        }
    });
};




/**
 * Get an infoblock from a device.
 *
 * @private
 * @static
 * @method getInfoBlock
 * @param {BlinkStick} device Device from which to get the value.
 * @param {Number} location Address to seek the data.
 * @param {Function} callback Callback to which to pass the value.
 */
function getInfoBlock (device, location, callback) {
    getFeatureReport(location, 33, function (err, buffer) {
        if (typeof(err) !== 'undefined')
        {
            callback(err);
            return;
        }

        var result = '',
        i, l;

        for (i = 1, l = buffer.length; i < l; i++) {
            if (i === 0) break;
            result += String.fromCharCode(buffer[i]);
        }

        callback(err, result);
    });
}




/**
 * Get default value from options
 *
 * @private
 * @static
 * @method opt
 * @param {Object} options Option object to operate on
 * @param {String} name The name of the parameter
 * @param {Object} defaultValue Default value if name is not found in option object
 */
function opt(options, name, defaultValue){
    return options && options[name]!==undefined ? options[name] : defaultValue;
}

/**
 * Sets an infoblock on a device.
 *
 * @private
 * @static
 * @method setInfoBlock
 * @param {BlinkStick} device Device on which to set the value.
 * @param {Number} location Address to seek the data.
 * @param {String} data The value to push to the device. Should be <= 32 chars.
 * @param {Function} callback Callback to which to pass the value.
 */
function setInfoBlock (device, location, data, callback) {
    var i,
    l = Math.min(data.length, 33),
    buffer = new Buffer(33);

    buffer[0] = 0;
    for (i = 0; i < l; i++) buffer[i + 1] = data.charCodeAt(i);
    for (i = l; i < 33; i++) buffer[i + 1] = 0;

    setFeatureReport(location, buffer, callback);
}




/**
 * Get the infoblock1 of the device.
 * This is a 32 byte array that can contain any data. It's supposed to
 * hold the "Name" of the device making it easier to identify rather than
 * a serial number.
 *
 * Usage:
 *
 * @example
 *     getInfoBlock1(function(err, data) {
 *         console.log(data);
 *     });
 *
 * @method getInfoBlock1
 * @param {Function} callback Callback to which to pass the value.
 */
BlinkStick.prototype.getInfoBlock1 = function (callback) {
    getInfoBlock(this.device, 0x0002, callback);
};




/**
 * Get the infoblock2 of the device.
 * This is a 32 byte array that can contain any data.
 *
 * Usage:
 *
 * @example
 *     getInfoBlock2(function(err, data) {
 *         console.log(data);
 *     });
 *
 * @method getInfoBlock2
 * @param {Function} callback Callback to which to pass the value.
 */
BlinkStick.prototype.getInfoBlock2 = function (callback) {
    getInfoBlock(this.device, 0x0003, callback);
};




/**
 * Sets the infoblock1 with specified string.
 * It fills the rest of bytes with zeros.
 *
 * Usage:
 *
 * @example
 *     setInfoBlock1("abcdefg", function(err) {
 *     });
 *
 * @method setInfoBlock1
 * @param {String} data Data value for InfoBlock
 * @param {Function} callback Callback when the operation completes
 */
BlinkStick.prototype.setInfoBlock1 = function (data, callback) {
    setInfoBlock(this.device, 0x0002, data, callback);
};




/**
 * Sets the infoblock2 with specified string.
 * It fills the rest of bytes with zeros.
 *
 * Usage:
 *
 * @example
 *     setInfoBlock2("abcdefg", function(err) {
 *     });
 *
 * @method setInfoBlock2
 * @param {String} data Data value for InfoBlock
 * @param {Function} callback Callback when the operation completes
 */
BlinkStick.prototype.setInfoBlock2 = function (data, callback) {
    setInfoBlock(this.device, 0x0003, data, callback);
};




/**
 * Sets the LED to a random color.
 *
 * @method setRandomColor
 */
BlinkStick.prototype.setRandomColor = function () {
    var args = [],
    i;

    for (i = 0; i < 3; i++) args.push(Math.floor(Math.random() * 256));
    this.setColor.apply(this, args);
};




/**
 * Turns the LED off.
 *
 * @method turnOff
 */
BlinkStick.prototype.turnOff = function () {
    this.setColor();
};




/**
 * Generate random integer number within a range.
 *
 * @private
 * @static
 * @method randomIntInc
 * @param {Number} low the low value of the number
 * @param {Number} high the high value of the number
 * @return {Number} Random number in the range of [low..high] inclusive of low and high
 */
function randomIntInc (low, high) {
    return Math.floor(Math.random() * (high - low + 1) + low);
}




/**
 * Automatically interpret parameters supplied for functions to generate
 * overrides.
 *
 * @private
 * @method interpretParameters
 * @param {Number|String} red Red color intensity 0 is off, 255 is full red intensity OR string CSS color keyword OR hex color, eg "#BADA55".
 * @param {Number} [green] Green color intensity 0 is off, 255 is full green intensity.
 * @param {Number} [blue] Blue color intensity 0 is off, 255 is full blue intensity.
 * @param {Object}   [options] additional options
 * @param {Function} [callback] Callback, called when complete.
 * @return {Object} Conains sanitized RGB value, options and callback if they have been assigned
 */
BlinkStick.prototype.interpretParameters = function (red, green, blue, options, callback) {
    var hex;

    if (typeof red == 'string') {
        if (typeof green == 'object') {
            options = green;
            callback = blue;
        } else {
            callback = green;
        }

        if (red == 'random') {
            red = randomIntInc(0, 255);
            green = randomIntInc(0, 255);
            blue = randomIntInc(0, 255);
        } else if  (red.match(/^\#[A-Za-z0-9]{6}$/)) {
            hex = red;
        } else if (!(hex = COLOR_KEYWORDS[red])) {
            if (callback)
                callback(new ReferenceError('Invalid CSS color keyword'));
            return;
        }
    } else if (typeof(options) == 'function') {
        callback = options;
    }


    if (hex) {
        red = parseInt(hex.substr(1, 2), 16);
        green = parseInt(hex.substr(3, 2), 16);
        blue = parseInt(hex.substr(5, 2), 16);

    } else {
        red = red || 0;
        green = green || 0;
        blue = blue || 0;
    }

    if (options === undefined) {
        options = {};
    }
    options.channel = opt(options, 'channel', 0);
    options.index = opt(options, 'index', 0);

    red = Math.max(Math.min(red, 255), 0);
    green = Math.max(Math.min(green, 255), 0);
    blue = Math.max(Math.min(blue, 255), 0);

    if (this.inverse)
    {
        red = 255 - red;
        green = 255 - green;
        blue = 255 - blue;
    }

    return {'red': red, 'green': green, 'blue': blue, 'options': options, 'callback': callback};
};




/**
 * Blinks specified RGB color.
 *
 * Function supports the following overloads:
 *
 * @example
 *     //Available overloads
 *     blink(red, green, blue, [options], [callback]); // use [0..255] ranges for intensity
 *
 *     blink(color, [options], [callback]); // use '#rrggbb' format
 *
 *     blink(color_name, [options], [callback]); // use 'random', 'red', 'green', 'yellow' and other CSS supported names
 *
 * Options can contain the following parameters for object:
 *
 * - channel=0: Channel is represented as 0=R, 1=G, 2=B
 * - index=0: The index of the LED
 * - repeats=1: How many times to blink
 * - delay=1: Delay between on/off cycles in milliseconds
 *
 * @method blink
 * @param {Number|String} red Red color intensity 0 is off, 255 is full red intensity OR string CSS color keyword OR hex color, eg "#BADA55".
 * @param {Number}   [green] Green color intensity 0 is off, 255 is full green intensity.
 * @param {Number}   [blue] Blue color intensity 0 is off, 255 is full blue intensity.
 * @param {Object}   [options] additional options {"channel": 0, "index": 0, "repeats": 1, "delay": 500}
 * @param {Function} [callback] Callback when the operation completes
 */
BlinkStick.prototype.blink = function (red, green, blue, options, callback) {
    var params = this.interpretParameters(red, green, blue, options, callback);

    if (typeof(params) === 'undefined')
    {
        return;
    }

    var repeats = opt(params.options, 'repeats', 1);
    var delay = opt(params.options, 'delay', 500);

    var self = this;

    var blinker = function (count) {
        self.setColor(params.red, params.green, params.blue, params.options, function (err) {
            if (typeof(err) !== 'undefined') {
                if (params.callback) params.callback(err);
            } else {
                if (!self.animationsEnabled) return;

                setTimeout(function() {
                    if (!self.animationsEnabled) return;

                    self.setColor(0, 0, 0, params.options, function (err) {
                        if (typeof(err) !== 'undefined') {
                            if (params.callback) params.callback(err);
                        } else {
                            if (!self.animationsEnabled) return;

                            setTimeout(function() {
                                if (!self.animationsEnabled) return;

                                if (count == repeats - 1) {
                                    if (params.callback) params.callback();
                                } else {
                                    blinker(count + 1);
                                }
                            }, delay);
                        }
                    });

                }, delay);
            }
        });
    };

    blinker(0);
};




/**
 * Morphs to specified RGB color from current color.
 *
 * Function supports the following overloads:
 *
 * @example
 *     //Available overloads
 *     morph(red, green, blue, [options], [callback]); // use [0..255] ranges for intensity
 *
 *     morph(color, [options], [callback]); // use '#rrggbb' format
 *
 *     morph(color_name, [options], [callback]); // use 'random', 'red', 'green', 'yellow' and other CSS supported names
 *
 * Options can contain the following parameters for object:
 *
 * - channel=0: Channel is represented as 0=R, 1=G, 2=B
 * - index=0: The index of the LED
 * - duration=1000: How long should the morph animation last in milliseconds
 * - steps=50: How many steps for color changes
 *
 * @method morph
 * @param {Number|String} red Red color intensity 0 is off, 255 is full red intensity OR string CSS color keyword OR hex color, eg "#BADA55".
 * @param {Number} [green] Green color intensity 0 is off, 255 is full green intensity.
 * @param {Number} [blue] Blue color intensity 0 is off, 255 is full blue intensity.
 * @param {Object}   [options] additional options {"channel": 0, "index": 0, "duration": 1000, "steps": 50}
 * @param {Function} [callback] Callback when the operation completes
 */
BlinkStick.prototype.morph = function (red, green, blue, options, callback) {
    var params = this.interpretParameters(red, green, blue, options, callback);

    if (typeof(params) === 'undefined')
    {
        return;
    }

    var duration = opt(params.options, 'duration', 1000);
    var steps = opt(params.options, 'steps', 50);

    var self = this;

    this.getColor(params.options.index, function(err, cr, cg, cb) {
        if (typeof(err) !== 'undefined') {
            if (params.callback) params.callback(err);
        } else {
            var morpher = function (count) {
                if (!self.animationsEnabled) return;

                var nextRed = parseInt(cr + (params.red - cr) / steps * count),
                    nextGreen = parseInt(cg + (params.green - cg) / steps * count),
                    nextBlue = parseInt(cb + (params.blue - cb) / steps * count);

                self.setColor(nextRed, nextGreen, nextBlue, params.options, function (err) {
                    if (typeof(err) !== 'undefined') {
                        if (params.callback) params.callback(err);
                    } else{
                        if (!self.animationsEnabled) return;

                        setTimeout(function() {
                            if (!self.animationsEnabled) return;

                            if (count >= steps) {
                                if (params.callback) params.callback();
                            } else {
                                morpher(count + 1);
                            }
                        }, parseInt(duration/steps));
                    }
                });

            };

            morpher(1);
        }
    });
};


/**
 * Pulses specified RGB color.
 *
 * Function supports the following overloads:
 *
 * @example
 *     //Available overloads
 *     pulse(red, green, blue, [options], [callback]); // use [0..255] ranges for intensity
 *
 *     pulse(color, [options], [callback]); // use '#rrggbb' format
 *
 *     pulse(color_name, [options], [callback]); // use 'random', 'red', 'green', 'yellow' and other CSS supported names
 *
 * Options can contain the following parameters for object:
 *
 * - channel=0: Channel is represented as 0=R, 1=G, 2=B
 * - index=0: The index of the LED
 * - duration=1000: How long should the pulse animation last in milliseconds
 * - steps=50: How many steps for color changes
 *
 * @method pulse
 * @param {Number|String} red Red color intensity 0 is off, 255 is full red intensity OR string CSS color keyword OR hex color, eg "#BADA55".
 * @param {Number} [green] Green color intensity 0 is off, 255 is full green intensity.
 * @param {Number} [blue] Blue color intensity 0 is off, 255 is full blue intensity.
 * @param {Object}   [options] additional options {"channel": 0, "index": 0, "duration": 1000, "steps": 50}
 * @param {Function} [callback] Callback when the operation completes
 */
BlinkStick.prototype.pulse = function (red, green, blue, options, callback) {
    var params = this.interpretParameters(red, green, blue, options, callback);

    if (typeof(params) === 'undefined')
    {
        return;
    }

    var self = this;

    self.morph(params.red, params.green, params.blue, params.options, function(err) {
        if (!self.animationsEnabled) return;

        if (typeof(err) !== 'undefined') {
            if (params.callback) params.callback(err);
        } else {
            self.morph(0, 0, 0, params.options, params.callback);
        }
    });
};




/**
* Find BlinkSticks using a filter.
*
* @method findBlinkSticks
* @param {Function} [filter] Filter function.
* @return {Array} BlickStick objects.
*/
function findBlinkSticks (filter) {
    if (filter === undefined) filter = function () { return true; };

    var result = [], device, i, devices;

    if (isWin) {
        devices = usb.devices();

        for (i in devices) {
            device = devices[i];

            if (device.vendorId === VENDOR_ID &&
                device.productId === PRODUCT_ID &&
                    filter(device))
                {
                    result.push(new BlinkStick(device.path, device.serialNumber, device.manufacturer, device.product));
                }
        }

    } else {
        devices = usb.getDeviceList();

        for (i in devices) {
            device = devices[i];
            if (device.deviceDescriptor.idVendor === VENDOR_ID &&
                device.deviceDescriptor.idProduct === PRODUCT_ID &&
                    filter(device))
                result.push(new BlinkStick(device));
        }
    }

    return result;
}




/**
* Set feature report to the device.
*
* @method setFeatureReport
* @param {Number} reportId Report ID to receive
* @param {Array} data Data to send to the device
* @param {Function} callback Function called when report sent
*/
BlinkStick.prototype.setFeatureReport = function (reportId, data, callback) {
    var retries = 0;
    var error;
    var self = this;

    var retryTransfer = function () {
        retries = retries + 1;

        if (retries > 5) {
            if (callback) callback(error);
            return;
        }

        try {
            if (isWin) {
                self.device.sendFeatureReport(data);
                if (callback) { callback(); }
            } else {
                self.device.controlTransfer(0x20, 0x9, reportId, 0, new Buffer(data), function (err) {
                    if (typeof(err) === 'undefined') {
                        if (callback) callback();
                    } else {
                        if (typeof(error) === 'undefined') {
                            //Store only the first error
                            error = err;
                        }

                        retryTransfer();
                    }
                });
            }
        } catch (ex) {
            if (typeof(error) === 'undefined') {
                //Store only the first error
                error = ex;
            }

            retryTransfer();
        }
    };

    retryTransfer();
};




/**
* Get feature report from the device.
*
* @method getFeatureReport
* @param {Number} reportId Report ID to receive
* @param {Number} length Expected length of the report
* @param {Function} callback Function called when report received
*/
BlinkStick.prototype.getFeatureReport = function (reportId, length, callback) {
    var retries = 0;
    var error;
    var self = this;

    var retryTransfer = function () {
        retries = retries + 1;

        if (retries > 5) {
            if (callback) callback(error);
            return;
        }

        try {
            if (isWin) {
                var buffer = self.device.getFeatureReport(reportId, length);
                if (callback) callback(undefined, buffer);
            } else {
                self.device.controlTransfer(0x80 | 0x20, 0x1, reportId, 0, length, function (err, data) {
                    if (typeof(err) === 'undefined') {
                        if (callback) callback(err, data);
                    } else {
                        if (typeof(error) === 'undefined') {
                            //Store only the first error
                            error = err;
                        }

                        retryTransfer();
                    }
                });
            }
        } catch (ex) {
            if (typeof(error) === 'undefined') {
                //Store only the first error
                error = ex;
            }

            retryTransfer();
        }
    };

    retryTransfer();
};

/**
Publicly available functions to find BlinkSticks on the computer.

@class module
@static
**/
module.exports = {

    /**
     * Find first attached BlinkStick.
     *
     * @example
     *     var blinkstick = require('blinkstick');
     *     var led = blinkstick.findFirst();
     *
     * @static
     * @method findFirst
     * @return {BlinkStick|undefined} The first BlinkStick, if found.
     */
    findFirst: function () {
        if (isWin) {
            var devices = findBlinkSticks();

            return devices.length > 0 ? devices[0] : null;
        } else {
            var device = usb.findByIds(VENDOR_ID, PRODUCT_ID);
            if (device) return new BlinkStick(device);
        }
    },




    /**
     * Find all attached BlinkStick devices.
     *
     * @example
     *     var blinkstick = require('blinkstick');
     *     var leds = blinkstick.findAll();
     *
     * @static
     * @method findAll
     * @return {Array} BlinkSticks.
     */
    findAll: function () {
        return findBlinkSticks();
    },




    /**
     * Returns the serial numbers of all attached BlinkStick devices.
     *
     * @static
     * @method findAllSerials
     * @param {Function} callback Callback when all serials have been collected
     * @return {Array} Serial numbers.
     */
    findAllSerials: function (callback) {
        var result = [];

        var devices = findBlinkSticks();
        var i = 0;

        var finder = function() {

            if (i == devices.length) {
                if (callback) callback(result);
            } else {
                devices[i].getSerial(function (err, serial) {
                    result.push(serial);
                    i += 1;
                    finder();
                });
            }
        };

        finder();
    },




    /**
     * Find BlinkStick device based on serial number.
     *
     * @static
     * @method findBySerial
     * @param {Number} serial Serial number.
     * @param {Function} callback Callback when BlinkStick has been found
     */
    findBySerial: function (serial, callback) {
        var result = [];

        var devices = findBlinkSticks();
        var i = 0;

        var finder = function() {

            if (i == devices.length) {
                if (callback) callback();
            } else {
                devices[i].getSerial(function (err, serialNumber) {
                    if (serialNumber == serial) {
                        if (callback) callback(devices[i]);
                    } else {
                        i += 1;
                        finder();
                    }
                });
            }
        };

        finder();
    }

};