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

@opendisplay/opendisplay

v1.2.0

Published

TypeScript library for OpenDisplay BLE e-paper displays (Web Bluetooth)

Readme

@opendisplay/opendisplay

TypeScript library for OpenDisplay BLE e-paper displays using Web Bluetooth API. Control your OpenDisplay devices directly from the browser.

npm version

Features

  • Web Bluetooth Integration: Control OpenDisplay devices directly from the browser
  • Complete Protocol Support: Image upload, device configuration, firmware management
  • Automatic Image Processing: Built-in dithering, encoding, and compression
  • Type-Safe: Full TypeScript support with exported types
  • Device Discovery: Browser-native device picker

Browser Compatibility

Web Bluetooth API is required. Supported browsers:

  • ✅ Chrome/Edge 56+ (Desktop & Android)
  • ✅ Opera 43+ (Desktop & Android)
  • ✅ Samsung Internet 6.0+
  • ❌ Firefox (no Web Bluetooth support)
  • ❌ Safari (no Web Bluetooth support)

Note: HTTPS or localhost is required for Web Bluetooth API.

Installation

NPM/Bun/Yarn

npm install @opendisplay/opendisplay
# or
bun add @opendisplay/opendisplay
# or
yarn add @opendisplay/opendisplay

CDN (for quick prototyping)

<script type="module">
  import { OpenDisplayDevice } from 'https://esm.sh/@opendisplay/[email protected]';
  // Your code here
</script>

Quick Start

import { OpenDisplayDevice, DitherMode, RefreshMode } from '@opendisplay/opendisplay';

// Create device instance
const device = new OpenDisplayDevice();

// Connect to device (shows browser picker)
await device.connect();

// Device is auto-interrogated on first connect
console.log(`Connected to ${device.width}x${device.height} display`);

// Load image from canvas
const canvas = document.getElementById('myCanvas') as HTMLCanvasElement;
const ctx = canvas.getContext('2d')!;
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

// Upload image
await device.uploadImage(imageData, {
  refreshMode: RefreshMode.FULL,
  ditherMode: DitherMode.FLOYD_STEINBERG,
  compress: true
});

// Disconnect when done
await device.disconnect();

API Documentation

OpenDisplayDevice

Main class for interacting with OpenDisplay devices.

Constructor

new OpenDisplayDevice(options?: {
  config?: GlobalConfig;
  capabilities?: DeviceCapabilities;
  device?: BluetoothDevice;
  namePrefix?: string;
})

Options:

  • config: Cached device configuration to skip interrogation
  • capabilities: Minimal device info (width, height, color scheme) to skip interrogation
  • device: Pre-selected BluetoothDevice instance
  • namePrefix: Device name filter for picker (e.g., "OpenDisplay")

Methods

connect(options?: BLEConnectionOptions): Promise<void>

Connect to an OpenDisplay device. Shows browser's Bluetooth device picker if no device was provided in constructor.

await device.connect();
// or with name filter
await device.connect({ namePrefix: 'OpenDisplay' });

Automatically interrogates device on first connect unless config/capabilities were provided.

disconnect(): Promise<void>

Disconnect from the device.

await device.disconnect();
uploadImage(imageData: ImageData, options?): Promise<void>

Upload image to device display. Handles resizing, dithering, encoding, and compression automatically.

Parameters:

  • imageData: Image as ImageData from canvas
  • options.refreshMode: Display refresh mode (default: RefreshMode.FULL)
  • options.ditherMode: Dithering algorithm (default: DitherMode.BURKES)
  • options.compress: Enable zlib compression (default: true)
// From canvas
const canvas = document.getElementById('canvas') as HTMLCanvasElement;
const ctx = canvas.getContext('2d')!;
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

await device.uploadImage(imageData, {
  refreshMode: RefreshMode.FULL,
  ditherMode: DitherMode.FLOYD_STEINBERG,
  compress: true
});

Upload Progress & Callbacks

The uploadImage() method supports several optional callbacks for tracking progress and timing:

await device.uploadImage(imageData, {
  refreshMode: RefreshMode.FULL,
  ditherMode: DitherMode.BURKES,
  compress: true,

  // Called during data transfer with progress updates
  onProgress: (current, total, stage) => {
    const percent = Math.floor((current / total) * 100);
    console.log(`${stage}: ${percent}% (${current}/${total} bytes)`);
  },

  // Called at various stages with status messages
  onStatusChange: (message) => {
    console.log(`Status: ${message}`);
    // Example messages:
    // - "Preparing image..."
    // - "Compressing..."
    // - "Uploading..."
    // - "Upload complete (X.Xs), refreshing display..."
    // - "Refresh complete (Y.Ys)"
  },

  // Called when upload is complete (before display refresh)
  onUploadComplete: (uploadTimeSeconds) => {
    console.log(`Upload took ${uploadTimeSeconds.toFixed(1)}s`);
  },

  // Called when entire operation completes (upload + refresh)
  onComplete: (uploadTime, refreshTime, totalTime) => {
    console.log(`Complete! Upload: ${uploadTime.toFixed(1)}s, Refresh: ${refreshTime.toFixed(1)}s, Total: ${totalTime.toFixed(1)}s`);
  }
});

Callback Reference:

| Callback | Parameters | When Called | Use Case | |----------|-----------|-------------|----------| | onProgress | (current, total, stage) | After each data chunk sent | Progress bars, upload percentage | | onStatusChange | (message) | At each operation stage | Status messages, user feedback | | onUploadComplete | (uploadTimeSeconds) | Upload done, before refresh | Track upload performance | | onComplete | (uploadTime, refreshTime, totalTime) | After refresh completes | Final timing summary |

Supported Refresh Modes:

  • RefreshMode.FULL - Full refresh (recommended, ~15s)
  • RefreshMode.FAST - Fast refresh if supported (~2s, may have ghosting)
  • RefreshMode.PARTIAL - Partial refresh if supported

Supported Dither Modes (from @opendisplay/epaper-dithering):

  • DitherMode.FLOYD_STEINBERG - Classic error diffusion (recommended)
  • DitherMode.BURKES - Burkes error diffusion
  • DitherMode.SIERRA - Sierra error diffusion
  • DitherMode.ATKINSON - Atkinson dithering (HyperCard style)
  • DitherMode.STUCKI - Stucki error diffusion
  • DitherMode.JARVIS - Jarvis-Judice-Ninke
  • DitherMode.SIMPLE_2D - Fast 2D error diffusion
  • DitherMode.ORDERED_BAYER_2 - 2x2 Bayer ordered dithering
  • DitherMode.ORDERED_BAYER_4 - 4x4 Bayer ordered dithering
interrogate(): Promise<GlobalConfig>

Read complete device configuration from device. Automatically called on first connect unless config/capabilities were provided.

const config = await device.interrogate();
console.log(`Device has ${config.displays.length} display(s)`);
readFirmwareVersion(): Promise<FirmwareVersion>

Read firmware version from device.

const fw = await device.readFirmwareVersion();
console.log(`Firmware v${fw.major}.${fw.minor} (${fw.sha})`);
writeConfig(config: GlobalConfig): Promise<void>

Write configuration to device. Device must be rebooted for changes to take effect.

// Read current config
const config = device.config!;

// Modify config
config.displays[0].rotation = 1;

// Write back to device
await device.writeConfig(config);

// Reboot to apply changes
await device.reboot();
reboot(): Promise<void>

Reboot the device. Connection will drop as device resets.

await device.reboot();
// Device will disconnect automatically

Properties

isConnected: boolean

Check if currently connected to a device.

width: number

Display width in pixels (throws if not interrogated).

height: number

Display height in pixels (throws if not interrogated).

colorScheme: ColorScheme

Display color scheme (throws if not interrogated).

Possible values:

  • ColorScheme.MONO - Black and white
  • ColorScheme.BWR - Black, white, red
  • ColorScheme.BWY - Black, white, yellow
  • ColorScheme.BWRY - Black, white, red, yellow (4-color)
  • ColorScheme.BWGBRY - Black, white, green, blue, red, yellow (6-color Spectra)
  • ColorScheme.GRAYSCALE_4 - 4-level grayscale
rotation: number

Display rotation steps (by 90 degrees) (0, 1, 2, 3).

config: GlobalConfig | null

Full device configuration (null if not interrogated).

capabilities: DeviceCapabilities | null

Minimal device info (width, height, colorScheme, rotation).

Discovery

import { discoverDevices } from '@opendisplay/opendisplay';

// Show device picker
const device = await discoverDevices();
// or with name filter
const device = await discoverDevices('OD');

Types

All types are exported for TypeScript users:

import type {
  GlobalConfig,
  DisplayConfig,
  DeviceCapabilities,
  FirmwareVersion,
  AdvertisementData
} from '@opendisplay/opendisplay';

Usage Examples

Basic Image Upload

import { OpenDisplayDevice } from '@opendisplay/opendisplay';

const device = new OpenDisplayDevice();
await device.connect();

// Create canvas with image
const canvas = document.createElement('canvas');
canvas.width = device.width;
canvas.height = device.height;
const ctx = canvas.getContext('2d')!;

// Draw something
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'black';
ctx.font = '48px Arial';
ctx.fillText('Hello OpenDisplay!', 50, 100);

// Upload to device
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
await device.uploadImage(imageData);

await device.disconnect();

Upload Image from File

// HTML: <input type="file" id="imageInput" accept="image/*">

const input = document.getElementById('imageInput') as HTMLInputElement;
input.addEventListener('change', async (e) => {
  const file = (e.target as HTMLInputElement).files?.[0];
  if (!file) return;

  // Load image
  const img = new Image();
  img.src = URL.createObjectURL(file);
  await img.decode();

  // Convert to ImageData
  const canvas = document.createElement('canvas');
  canvas.width = img.width;
  canvas.height = img.height;
  const ctx = canvas.getContext('2d')!;
  ctx.drawImage(img, 0, 0);
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

  // Upload
  await device.uploadImage(imageData);
});

Skip Interrogation with Cached Config

import { OpenDisplayDevice } from '@opendisplay/opendisplay';

// First connection - interrogate and cache
const device = new OpenDisplayDevice();
await device.connect();
const cachedConfig = device.config!;

// Store config in localStorage
localStorage.setItem('deviceConfig', JSON.stringify(cachedConfig));

// Later - reuse cached config
const savedConfig = JSON.parse(localStorage.getItem('deviceConfig')!);
const fastDevice = new OpenDisplayDevice({ config: savedConfig });
await fastDevice.connect();
// No interrogation needed!

Skip Interrogation with Minimal Capabilities

import { OpenDisplayDevice, ColorScheme } from '@opendisplay/opendisplay';

// If you know your device specs
const device = new OpenDisplayDevice({
  capabilities: {
    width: 296,
    height: 128,
    colorScheme: ColorScheme.BWR,
    rotation: 0
  }
});

await device.connect();
// Fast connection - no interrogation!

Read and Modify Device Configuration

const device = new OpenDisplayDevice();
await device.connect();

// Read config
const config = await device.interrogate();
console.log(`Display: ${config.displays[0].pixelWidth}x${config.displays[0].pixelHeight}`);
console.log(`Battery: ${config.power?.batteryCapacityMah}mAh`);

// Modify rotation
config.displays[0].rotation = 180;

// Write back
await device.writeConfig(config);
await device.reboot(); // Reboot to apply

Error Handling

import {
  OpenDisplayDevice,
  BLEConnectionError,
  BLETimeoutError,
  ProtocolError
} from '@opendisplay/opendisplay';

try {
  const device = new OpenDisplayDevice();
  await device.connect();
  await device.uploadImage(imageData);
} catch (error) {
  if (error instanceof BLEConnectionError) {
    console.error('Failed to connect:', error.message);
  } else if (error instanceof BLETimeoutError) {
    console.error('Operation timed out:', error.message);
  } else if (error instanceof ProtocolError) {
    console.error('Protocol error:', error.message);
  } else {
    console.error('Unexpected error:', error);
  }
}

Architecture

This library mirrors the architecture of py-opendisplay:

  • Protocol Layer: Command builders, response parsers, TLV config handling
  • Transport Layer: Web Bluetooth wrapper with notification queue
  • Encoding Layer: Image encoding, compression, bitplane handling
  • Models Layer: TypeScript interfaces for all data structures
  • Public API: OpenDisplayDevice class and helper functions

Development

# Install dependencies
npm install

# Build library
npm run build

# Type check
npm run type-check

# Lint
npm run lint

Related Packages