npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

rvl-node

v9.0.0

Published

A Node.js implementation of the RVL* library family

Readme

RVL Node

A Node.js implementation of rvl's sending side. It joins the fleet's network, follows the fleet clock, and schedules scenes for the boards to show, so a program on a laptop or a Pi can drive the lights the way a controller board does.

For working examples, see the two demos in this repository: demo/waves.ts, which cycles through the built-in animations, and demo/scenes.ts, which schedules palette shifts on a bar grid at a fixed tempo.

Requirements

  • One RVL Node per host. It binds UDP port 4979 and sends on every channel, so a second instance has nothing to add, and init reports an error if one is already running.

Installation

Install using npm:

npm install rvl-node

Usage

The code below connects to the fleet and shows solid red on every board listening on channel 1, dissolving in over half a second.

import {
  createAnimationParameters,
  createSolidColorAnimation,
  init,
  on,
  setAnimationParameters,
} from 'rvl-node';

init();
on('connected', () => {
  setAnimationParameters(
    1, // Channel 1
    createAnimationParameters(
      createSolidColorAnimation(0, 255, 255) // Hue 0, full saturation, full alpha: solid red
    )
  );
});

How it works

Scenes

Everything the fleet shows is a scene: an animation, or off, with a start frame and a fade. When a scene starts, every board dissolves from whatever it is showing into the new scene over the fade, so a change lands on all of them at the same instant and looks the same everywhere. Every scene fades, and the shortest fade is 16 frames, about half a second.

setAnimationParameters and setOff each schedule a scene on a channel. Without a startFrame, RVL Node defaults to five frames ahead if the channel's last scene has finished fading, and otherwise it holds the request until that fade ends. A newer request replaces a held one and whatever the boards end up showing is always the latest thing you asked for.

The fleet clock and frames

The fleet shares an animation clock, a millisecond count the boards agree on through the coordinator's reference broadcasts, and RVL Node follows it. A frame is 32 ms long (31.25 frames per second) and scene starts and fades are counted in frames. Frame numbers wrap every 2^27 frames, so compare them with subtractFrames rather than <.

The fleet clock is available a few seconds after init, and connected waits for it as well as for a device ID. Until then the clock accessors throw, scenes without a start are held, and scenes with a start can't be placed, so setAnimationParameters and setOff throw for those.

Configuring boards

A board's channel, brightness, pixel count, color order, status LED brightness and nickname can be set over the wire, and the board stores them, so they survive a reboot. getNodes asks every board to report itself and returns what came back: each board's settings and which of the six properties it honors, since a board without a status LED ignores that one and a board with physical controls reads channel and brightness from them. configureNode sets one property on one board and resolves with the board's report once it has applied it. identifyNode makes a board flash white for five seconds, strip and status LED, so you can tell which physical board an ID is.

Boards answer with a broadcast addressed to this node, which is lost a few percent of the time like any broadcast, so a listing asks twice and a targeted request asks once more before rejecting.

API

Setup

init

Joins the fleet: binds the socket, starts requesting a device ID from the coordinator, and starts following the fleet clock. It returns at once and the rest happens in the background; connected says when the fleet can be driven.

Signature:

interface RVLInitOptions {
  networkInterface?: string;
}

function init(options?: RVLInitOptions): void;

Arguments:

  • options.networkInterface (optional): the network interface to send and receive RVL packets on, e.g. "wlan0". Without it, RVL Node uses the first interface getAvailableInterfaces lists, and throws if there is none. A named interface doesn't need an address yet, so name it if you're starting before the network is up.

Returns: nothing. It throws if it has already been called, or if no interface is usable. The bind finishes in the background: if port 4979 is already in use, most likely by another RVL Node on the host, an error event says so, and init may be called again after it.

Every other function below, except on, the interface helpers, subtractFrames and the animation builders, throws until init has been called.

on

Subscribes to an event.

Signature:

function on(event: 'connected' | 'disconnected', listener: () => void): void;
function on(event: 'error', listener: (error: Error) => void): void;

connected is emitted once this node has a device ID from the coordinator and the fleet clock, in whichever order they arrive, and so can drive the fleet. disconnected is emitted when the ID is lost, which happens when the network interface loses its address; connected is emitted again when it's reassigned, since the clock stays synced. error carries a failed bind. With no listener for it, the error surfaces as an uncaught exception, since Node's emitters throw an unhandled error.

getDeviceId

Signature:

function getDeviceId(): number | undefined;

Returns: the device ID this node appears as to the fleet, or undefined if the coordinator hasn't assigned one yet. The ID is dropped when the interface loses its address, and may be different when it's reassigned.

isConnected

Signature:

function isConnected(): boolean;

Returns: whether this node has a device ID and the fleet clock, and so can drive the fleet.

getNetworkInterface

Signature:

function getNetworkInterface(): string;

Returns: the name of the interface RVL Node is sending on.

getAvailableInterfaces

Gets the list of available network interfaces that RVL Node can use. To be considered valid, an interface must have an IPv4 address that isn't self-assigned (169.254.x.x), and its name must start with en, eth, wlan, Wi-Fi, or Ethernet.

Signature:

function getAvailableInterfaces(): string[];

Returns: a list of interface names that can be used with RVL Node.

getDefaultInterface

Gets the network interface init will use when none is named.

Signature:

function getDefaultInterface(): string | undefined;

Returns: the name of the interface that will be used, or undefined if there is no suitable interface.

Scenes

setAnimationParameters

Schedules an animation on a channel. You can craft animation parameters by hand, but it's recommended to use the helper functions exported by this package, such as createAnimationParameters and createSimpleWaveParameters, instead. Crafting parameters by hand is a pain.

Signature:

function setAnimationParameters(
  channel: number,
  parameters: AnimationParameters,
  options?: SceneOptions
): Promise<void>;

Arguments:

  • channel: the channel to schedule the animation on, from 0 to 7.
  • parameters: the animation. Throws if there are more than 4 layers, or if a value is out of range for its field: timePeriod and distancePeriod are 1 to 255, a and b are 0 to 255, and w_t, w_x and phi are -128 to 127.
  • options.startFrame (optional): the fleet frame the scene starts on, an integer from 0 to 2^32 - 1, at or after getEarliestStartFrame. The check allows one frame boundary to pass between that read and this call, so a start read just before is still taken, four frames ahead at the least; a second boundary and it throws. Also throws if getEarliestStartFrame is undefined, or before the first clock sync. Without it the scene is scheduled as described under Scenes.
  • options.fadeFrames (optional): how many frames the boards take to dissolve from what they are showing into this scene, an integer from 16 to 255 (about 0.5 to 8 s). Defaults to 32, a second.

Returns: a promise that resolves once the channel has settled: no request is being held for it, and the last scene scheduled on it has been sent twice, at once and again four frames later as insurance against a lost packet. That can be some seconds after the call when the scene was held behind a running fade. Before the first clock sync it resolves at once, with the request held. The promise never rejects: without a device ID the sends are skipped but still count, and the scene goes out once the coordinator assigns one.

setOff

Turns off the lights on a channel. Off is a scene like any other: it fades in the same way and takes the same options, and setAnimationParameters turns the channel back on.

Signature:

function setOff(channel: number, options?: SceneOptions): Promise<void>;

Arguments:

  • channel: the channel to turn off, from 0 to 7.
  • options (optional): the same options as setAnimationParameters.

Returns: a promise that resolves once the channel has settled, as setAnimationParameters's does.

getEarliestStartFrame

Gets the earliest frame an explicit startFrame can take on a channel right now: five frames ahead, or the end of the running fade if that is later.

Signature:

function getEarliestStartFrame(channel: number): number | undefined;

Returns: that frame, or undefined while a scene you scheduled is still ahead or a request is being held, in which case a call with a startFrame would throw. Read it again a frame later. A program scheduling on a grid should read it rather than keep its own copy of the channel's state, which goes stale when the fleet clock steps. Throws before the first clock sync.

The fleet clock

getAnimationClock

Signature:

function getAnimationClock(): number;

Returns: the fleet's animation clock now, in milliseconds, which wraps at 2^32. Throws before the first clock sync.

toAnimationClock

Signature:

function toAnimationClock(localTime: number): number;

Returns: the animation clock at a local time, as performance.now() gives it. Throws before the first clock sync.

toLocalTime

Signature:

function toLocalTime(animationClock: number): number;

Returns: the local time, as performance.now() gives it, nearest to now that maps to the animation clock value; subtract performance.now() from it for a setTimeout delay. Throws before the first clock sync.

FRAME_PERIOD

The length of a frame in animation clock milliseconds, 32. A frame number is the animation clock divided by it, rounded down.

subtractFrames

Signature:

function subtractFrames(a: number, b: number): number;

Returns: the signed number of frames from b to a, correct across the 2^27 frame wrap for differences of up to 2^26 frames, about 25 days.

Configuration

getNodes

Asks every board on the network to report itself.

Signature:

function getNodes(): Promise<NodeStatus[]>;

Returns: a promise for the boards that answered within a second, sorted by device ID. A board that is off or not on the network is absent, and so is one whose reply was lost twice, so a missing board is worth a second call. Throws before connected.

configureNode

Sets one property on one board.

Signature:

function configureNode<P extends keyof NodeConfiguration>(
  deviceId: number,
  property: P,
  value: NodeConfiguration[P]
): Promise<NodeStatus>;

Arguments:

  • deviceId: the board, 0 to 239, as getNodes reports it.
  • property and value: channel 0 to 7; brightness 0 to 255; pixelCount 1 to MAX_PIXEL_COUNT, the number of pixels the board renders and drives; colorOrder, the order the strip reads its three bytes in, GRB for a WS2812B; statusLedBrightness 0 to 255, for boards that have one; nickname, up to 16 printable ASCII characters. Throws for a value out of range.

Returns: a promise for the board's status carrying the new value, so a status the board sent for some other reason in the meantime isn't mistaken for the answer. A property the board doesn't honor, per capabilities in its status, is ignored, and the first status shows it unchanged. The board stores the change a second later. Rejects if the board hasn't answered after two attempts, about two seconds. Throws before connected.

identifyNode

Flashes a board white for five seconds, its strip and, on a board that has one, its status LED, so you can pick it out.

Signature:

function identifyNode(deviceId: number): Promise<NodeStatus>;

Returns: a promise for the board's status, which confirms it heard. Rejects if the board hasn't answered after two attempts. Throws before connected.

MAX_PIXEL_COUNT

The most pixels a board drives, 300, which is the size of the firmware's strip buffers. A pixelCount past it throws.

NUM_CHANNELS

The number of channels, 8. A channel is 0 to NUM_CHANNELS - 1.

NICKNAME_LENGTH

The most characters a nickname holds, 16.

Animation builders

createAnimationParameters

Builds a complete parameter set from up to four layers. The layers are composited from the fourth up to the first, each blended over the result by its alpha.

Signature:

function createAnimationParameters(
  animation1?: AnimationLayer,
  animation2?: AnimationLayer,
  animation3?: AnimationLayer,
  animation4?: AnimationLayer
): AnimationParameters;

Returns: parameters with the layers given and empty layers for the rest.

createEmptyAnimation

Signature:

function createEmptyAnimation(): AnimationLayer;

Returns: a layer with every field 0, which renders as transparent black.

createSolidColorAnimation

Signature:

function createSolidColorAnimation(
  h: number,
  s: number,
  a: number
): AnimationLayer;

Arguments: hue, saturation and alpha, each 0 to 255.

createColorCycleAnimation

A layer whose hue cycles through the spectrum, the same color along the whole strip.

Signature:

function createColorCycleAnimation(rate: number, a: number): AnimationLayer;

Arguments: a rate from 1 to 32 and an alpha from 0 to 255.

createMovingAnimation

A solid color whose alpha sweeps along the strip.

Signature:

function createMovingAnimation(
  h: number,
  s: number,
  rate: number,
  spacing: number
): AnimationLayer;

Arguments: hue and saturation from 0 to 255, a rate from 0 to 32, and a spacing from 1 to 16.

createPulsingAnimation

A solid color whose alpha pulses, the same along the whole strip.

Signature:

function createPulsingAnimation(
  h: number,
  s: number,
  rate: number
): AnimationLayer;

Arguments: hue and saturation from 0 to 255 and a rate from 1 to 32.

createRainbowAnimation

A rainbow that moves along the strip.

Signature:

function createRainbowAnimation(a: number, rate: number): AnimationLayer;

Arguments: an alpha from 0 to 255 and a rate from 1 to 32.

createSimpleWaveParameters

The controller boards' Wave preset without its foreground layer: a wave in one hue moving over a background in another. The strip's average color holds still while the pattern moves, so a scene change reads as a clean color shift.

Signature:

function createSimpleWaveParameters(options: {
  rate: number;
  waveHue: number;
  backgroundHue: number;
  brightness: number;
}): AnimationParameters;

Arguments: a rate from 0 to 32, the two hues from 0 to 255, and a brightness from 0 to 255 applied to both layers. With one hue on both layers nothing moves, so keep them apart.

Returns: a complete parameter set, the wave in the first layer and the background in the third, as the preset lays them out.

Types

interface AnimationColorParameters {
  a: number; // Amplitude, 0 to 255
  b: number; // Offset, 0 to 255
  w_t: number; // Rate over time, -128 to 127
  w_x: number; // Rate along the strip, -128 to 127
  phi: number; // Phase, -128 to 127
}

interface AnimationLayer {
  h: AnimationColorParameters;
  s: AnimationColorParameters;
  v: AnimationColorParameters;
  a: AnimationColorParameters;
}

interface AnimationParameters {
  timePeriod?: number; // 1 to 255, default 255
  distancePeriod?: number; // 1 to 255, default 32
  animations: AnimationLayer[];
}

interface SceneOptions {
  startFrame?: number;
  fadeFrames?: number;
}

type ColorOrder = 'RGB' | 'RBG' | 'GRB' | 'GBR' | 'BRG' | 'BGR';

interface NodeConfiguration {
  channel: number; // 0 to 7
  brightness: number; // 0 to 255
  pixelCount: number; // 1 to MAX_PIXEL_COUNT
  colorOrder: ColorOrder;
  statusLedBrightness: number; // 0 to 255
  nickname: string; // up to 16 printable ASCII characters
}

// Which of the properties a board honors
type NodeCapabilities = { [P in keyof NodeConfiguration]: boolean };

interface NodeStatus extends NodeConfiguration {
  deviceId: number;
  capabilities: NodeCapabilities;
}

Each color component of a layer is b + a × sin(w_t·t + w_x·x + phi) with the sine scaled 0 to 1, so it runs from b to a + b. t runs over timePeriod tenths of a second and x over distancePeriod pixels, so w_t and w_x count cycles per period. The layer's h, s and v make its color, and a is its alpha over the layers beneath it.

License

Copyright (c) Bryan Hughes [email protected]

This file is part of RVL Node.

RVL Node is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

RVL Node is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with RVL Node. If not, see http://www.gnu.org/licenses/.