@simontaga/rpi-ws281x-native
Version:
(raspberry-pi *only*) native bindings to control a strip of WS281x-LEDs with node.js
246 lines (211 loc) • 5.8 kB
JavaScript
const bindings = require('./get-native-bindings')();
const {stripType, stripTypeIds, paramCodes} = require('./constants');
const MAX_CHANNELS = 2;
const DEFAULT_DMA = 5;
const DEFAULT_FREQ = 800000;
const DEFAULT_STRIP_TYPE = stripType.WS2812;
// for GPIO-numbers see for example https://pinout.xyz/
const CHANNEL_DEFAULTS = [
{
count: 0,
gpio: 18,
invert: 0,
brightness: 255,
stripType: DEFAULT_STRIP_TYPE
},
{
count: 0,
gpio: 13,
invert: 0,
brightness: 255,
stripType: DEFAULT_STRIP_TYPE
}
];
const channels = Array(2);
// private members for channel-instances
const _id = Symbol('_id');
const _params = Symbol('_params');
const _update = Symbol('_update');
const _init = Symbol('_init');
const _reset = Symbol('_reset');
class Channel {
/**
* @param {number} channelId
* @param {ChannelParams} params
*/
constructor(channelId, params) {
const ledCount = params.count;
// private properties
this[_id] = channelId;
this[_params] = params;
// public/readonly properties
Object.defineProperties(this, {
count: {
value: params.count,
writable: false,
enumerable: true,
configurable: false
},
stripType: {
value: params.stripType,
writable: false,
enumerable: true,
configurable: false
},
invert: {
value: params.invert,
writable: false,
enumerable: true,
configurable: false
},
gpio: {
value: params.gpio,
writable: false,
enumerable: true,
configurable: false
}
});
// public properties
this.brightness = params.brightness;
this.buffer = null;
this.array = null;
if (ledCount > 0) {
const arrayBuffer = new ArrayBuffer(ledCount * 4); // 0xWWRRGGBB
// buffer and array are different ways to access the same data
this.buffer = Buffer.from(arrayBuffer);
this.array = new Uint32Array(arrayBuffer);
}
}
/**
* Resets this channel, setting all LEDs to black.
*/
[_reset]() {
if (!this.array) {
return;
}
this.array.fill(0);
}
/**
* Sends values for all parameters to the library.
* @private
*/
[_init]() {
Object.keys(this[_params]).forEach(paramName => {
const value = this[_params][paramName];
const code = paramCodes[paramName];
bindings.setChannelParam(this[_id], code, value);
});
}
/**
* Sends the current state of the buffer and the current brightness-value
* to the library.
* @private
*/
[_update]() {
if (this.buffer === null) {
return;
}
bindings.setChannelParam(this[_id], paramCodes.brightness, this.brightness);
bindings.setChannelData(this[_id], this.buffer);
}
}
/**
* Initializes the library.
* @param {FullParams} params
* @return {Channel[]} the initialized channel-instances
*/
function init(params) {
// FIXME: validate params
const {
dma = DEFAULT_DMA,
freq = DEFAULT_FREQ,
channels: channelsConfig = []
} = params;
// send global parameter values
bindings.setParam(paramCodes.dma, dma);
bindings.setParam(paramCodes.freq, freq);
// setup channels
for (let channelId = 0; channelId < MAX_CHANNELS; channelId++) {
const userParams = channelsConfig[channelId];
const defaults = CHANNEL_DEFAULTS[channelId];
const params = Object.assign({}, defaults, userParams);
if (typeof params.stripType === 'string') {
params.stripType = stripTypeIds[params.stripType];
}
channels[channelId] = new Channel(channelId, params);
}
channels.forEach(channel => channel[_init]());
bindings.init();
return channels;
}
/**
* Submits the current state of the channel-buffers to the driver for rendering.
*/
function render() {
channels.forEach(channel => channel[_update]());
bindings.render();
}
/**
* resets all color-values of all channels and renders.
*/
function reset() {
channels.forEach(channel => channel[_reset]());
render();
}
/**
* Shuts down the library, freeing allocated memory and resources.
* This should always be called when terminating the program.
*/
function finalize() {
bindings.finalize();
}
/**
* Simple initializer for single-channel usage.
* @param {number} numLeds number of LEDs
* @param {CombinedParams} options additional options.
* @return {Channel}
*/
module.exports = function(numLeds, options = {}) {
const {
dma = DEFAULT_DMA,
freq = DEFAULT_FREQ,
gpio = 18,
invert = false,
brightness = 255,
stripType = DEFAULT_STRIP_TYPE
} = options;
const channelOptions = {count: numLeds, gpio, invert, brightness, stripType};
const [channel] = init({dma, freq, channels: [channelOptions]});
// for convenience, make methods available via the channel-instance
channel.render = render;
channel.finalize = finalize;
return channel;
};
Object.assign(module.exports, {
init,
render,
reset,
finalize,
stripType
});
/**
* @typedef {object} FullParams
* @property {number} dma the DMA-number
* @property {number} freq the PWM-frequency in Hz
* @property {ChannelParams[]} channels The channel configurations.
*/
/**
* @typedef {object} ChannelParams
* @property {number} count number of LEDs on this channel
* @property {number} gpio the GPIO port-number the LEDs connect to
* @property {boolean} invert true to invert the output-signal (if you are
* using an inverting level-shifter for example)
* @property {number} brightness initial brightness for the channel
* @property {number|string} stripType the strip-type (see ./constants.js)
*/
/**
* @typedef {object} CombinedParams
* @extends ChannelParams
* @property {number} dma
* @property {number} freq
*/