piixel
Version:
Control WS281X / NeoPixel LEDs from a Raspberry Pi using Node.js and TypeScript
143 lines (141 loc) • 5.53 kB
JavaScript
import getBindings from "bindings";
import ansiEscapes from "ansi-escapes";
import chalk from "chalk";
function colorwheel(pos) {
pos = pos - Math.floor(pos / 256) * 256;
let shift1, shift2;
pos < 85 ? (shift1 = 8, shift2 = 16) : pos < 170 ? (pos -= 85, shift1 = 0, shift2 = 8) : (pos -= 170, shift1 = 16, shift2 = 0);
let p = Math.floor(pos * 3);
return p = p < 256 ? p : 255, p << shift1 | 255 - p << shift2;
}
function rgb2hex(r, g, b) {
return r << 16 | g << 8 | b;
}
function brighten(color, amount) {
amount = amount === 0 ? 0 : amount;
const [r, g, b] = color;
return [
Math.max(0, Math.min(255, r - Math.round(255 * -amount))),
Math.max(0, Math.min(255, g - Math.round(255 * -amount))),
Math.max(0, Math.min(255, b - Math.round(255 * -amount)))
];
}
class MockBindings {
#options;
#pixels = new Uint32Array(0);
#brightness = 1;
#layout;
// todo: add support circular layout
constructor(layout = { h: 1 }) {
this.#layout = layout, layout.h !== -1 && process.stdout.write(ansiEscapes.cursorHide);
}
configure(options) {
this.#options = options;
}
render() {
const height = this.#layout.h;
if (height === -1)
return;
const width = this.#layout.w ?? Math.ceil(this.#pixels.length / this.#layout.h), pixels = new Array(height).fill(0).map(
(_, row) => new Array(width).fill(0).map((_2, col) => {
const pixel = this.#pixels[row * width + col], r = pixel >> 16 & 255, g = pixel >> 8 & 255, b = pixel & 255;
return chalk.bgRgb(...brighten([r, g, b], 1 - this.#brightness))(
" "
);
}).join("")
).join(`
`);
process.stdout.write(
ansiEscapes.cursorLeft + ansiEscapes.eraseLines(height) + pixels
);
}
reset() {
process.stdout.write(ansiEscapes.cursorShow);
}
setPixels(pixels) {
this.#pixels = pixels;
}
setBrightness(brightness) {
this.#brightness = brightness;
}
}
function createMockBindings(layout) {
return new MockBindings(layout);
}
function parseMockEnv(env) {
const [h, w] = env.split("x").map(Number);
return isNaN(h) ? { h: 1 } : { h, w };
}
function tryGetBindings() {
try {
return getBindings("addon");
} catch (error) {
if (error instanceof Error) {
if ("code" in error && error.code === "ERR_DLOPEN_FAILED") {
if (process.env.MOCK_PIIXEL)
return createMockBindings(parseMockEnv(process.env.MOCK_PIIXEL));
error.message = `WS281x native bindings could not be loaded. Set MOCK_PIIXEL=true to render pixels to terminal.
Original error: ${error.message}`;
}
"tries" in error && Array.isArray(error.tries) && (error.message = `!!This package is only works on a Raspberry PI!! ${error.message}`);
}
throw error;
}
}
const bindings = tryGetBindings();
var StripType = /* @__PURE__ */ ((StripType2) => (StripType2[StripType2.SK6812_STRIP_RGBW = 403703808] = "SK6812_STRIP_RGBW", StripType2[StripType2.SK6812_STRIP_RBGW = 403701768] = "SK6812_STRIP_RBGW", StripType2[StripType2.SK6812_STRIP_GRBW = 403181568] = "SK6812_STRIP_GRBW", StripType2[StripType2.SK6812_STRIP_GBRW = 403177488] = "SK6812_STRIP_GBRW", StripType2[StripType2.SK6812_STRIP_BRGW = 402657288] = "SK6812_STRIP_BRGW", StripType2[StripType2.SK6812_STRIP_BGRW = 402655248] = "SK6812_STRIP_BGRW", StripType2[StripType2.WS2811_STRIP_RGB = 1050624] = "WS2811_STRIP_RGB", StripType2[StripType2.WS2811_STRIP_RBG = 1048584] = "WS2811_STRIP_RBG", StripType2[StripType2.WS2811_STRIP_GRB = 528384] = "WS2811_STRIP_GRB", StripType2[StripType2.WS2811_STRIP_GBR = 524304] = "WS2811_STRIP_GBR", StripType2[StripType2.WS2811_STRIP_BRG = 4104] = "WS2811_STRIP_BRG", StripType2[StripType2.WS2811_STRIP_BGR = 2064] = "WS2811_STRIP_BGR", StripType2[
StripType2.WS2812_STRIP = 528384
/* WS2811_STRIP_GRB */
] = "WS2812_STRIP", StripType2[
StripType2.SK6812_STRIP = 528384
/* WS2811_STRIP_GRB */
] = "SK6812_STRIP", StripType2[
StripType2.SK6812W_STRIP = 403181568
/* SK6812_STRIP_GRBW */
] = "SK6812W_STRIP", StripType2))(StripType || {});
class Ws281xImpl {
#leds = void 0;
#resetOnExit = !1;
constructor() {
process.once("exit", () => {
this.#resetOnExit && this.clear();
});
}
configure({ resetOnExit, ...options }) {
if (this.#leds !== void 0)
throw new Error(
"ws281x is already configured. Call ws281x.reset() first!"
);
this.#resetOnExit = resetOnExit ?? !1, this.#leds = options.leds, bindings.configure(options);
}
reset() {
this.#leds !== void 0 && (this.clear(), bindings.reset()), this.#leds = void 0;
}
clear() {
this.#leds !== void 0 && this.render(new Uint32Array(this.#leds));
}
render(pixelsOrOpts) {
pixelsOrOpts instanceof Uint32Array ? this.#render({ pixels: pixelsOrOpts }) : this.#render(pixelsOrOpts);
}
#render(options) {
if (this.#leds === void 0)
throw new Error("Must call configure() before render()");
const { pixels, brightness } = options, ops = [];
if (typeof pixels < "u") {
if (pixels.length !== this.#leds)
throw new Error(
`Size of pixels array must match number of LEDs (expected: ${this.#leds}, got: ${pixels.length})`
);
ops.push(() => bindings.setPixels(pixels));
}
brightness !== void 0 && ops.push(() => bindings.setBrightness(brightness)), ops.length !== 0 && (ops.forEach((op) => op()), bindings.render());
}
}
const ws281x = new Ws281xImpl();
export {
StripType,
colorwheel,
rgb2hex,
ws281x
};
//# sourceMappingURL=index.js.map