A modular Node.js driver for Waveshare E-Paper displays that supports multiple display models with different resolutions and color modes.
- Modular architecture: Easy to extend with new display models
- Multiple display support: Currently supports 8+ display models with more easily added
- Color mode support: Monochrome, grayscale and color modes where supported by hardware
- Common API: Unified interface across all display models
- Hardware abstraction: Base class handles common SPI/GPIO operations
- PNG image loading: Built-in support for loading and displaying PNG images
Not all devices have been tested in the field. Please create a GitHub issue if you have confirmed one of the untested platforms working.
Model | Resolution | Color Modes | Description | Status |
---|---|---|---|---|
2in13 | 122 × 250 | Monochrome | 2.13" black/white | Untested |
2in7 | 176 × 264 | Mono, 4-grayscale | 2.7" with grayscale support | Untested |
2in7b | 176 × 264 | 3-color | 2.7" black/white/red or yellow | Untested |
7in5 | 640 × 384 | Monochrome | 7.5" black/white | Confirmed working |
7in3f | 800 × 480 | 7-color | 7.3" full color (7 colors) | Untested |
13in3k | 960 × 680 | Mono, 4-grayscale | 13.3" with grayscale support | Confirmed working |
13in3b | 960 × 680 | 3-color | 13.3" black/white/red or yellow | Untested |
13in3gray | 1600 × 1200 | 16-grayscale | 13.3" 16-level grayscale (IT8951) | Untested |
npm install waveshare-epaper
For security, add your user to the gpio
group instead of running as root:
# Add current user to gpio group
sudo usermod -a -G gpio $USER
# Log out and back in, then verify group membership
groups
After logging back in, you can run GPIO programs without sudo
.
const { createDisplay } = require('waveshare-epaper');
(async () => {
// Create display instance (13.3" 4-grayscale)
const epd = createDisplay('13in3k', '4gray', {
rstPin: 17,
dcPin: 25,
busyPin: 24,
pwrPin: 18
});
// Initialize and use
await epd.init();
await epd.clear();
// Draw with different gray levels (0=black, 1=dark gray, 2=light gray, 3=white)
epd.drawRect(10, 10, 100, 50, 0, true); // Black filled rectangle
epd.drawRect(120, 10, 100, 50, 1, true); // Dark gray rectangle
epd.drawRect(230, 10, 100, 50, 2, true); // Light gray rectangle
epd.drawLine(10, 80, 330, 80, 0); // Black line
// Update display
await epd.display();
await epd.sleep();
})();
const { createDisplay } = require('waveshare-epaper');
(async () => {
const epd = createDisplay('13in3gray', '16gray', {
rstPin: 17,
dcPin: 25,
busyPin: 24,
pwrPin: 18,
vcom: -2.30 // Adjust according to your display
});
await epd.init();
await epd.clear();
// Draw with 16 different gray levels (0=black, 15=white)
const cellWidth = 100;
for (let i = 0; i < 16; i++) {
const x = (i % 8) * cellWidth;
const y = Math.floor(i / 8) * 60;
epd.drawRect(x, y, cellWidth - 2, 58, i, true);
}
// Display with high quality GC16 mode
await epd.display('GC16');
await epd.sleep();
})();
Requires: npm install canvas
const { createDisplay } = require('waveshare-epaper');
(async () => {
const { createCanvas } = require('canvas');
const epd = createDisplay('13in3k', '4gray', {
rstPin: 17, dcPin: 25, busyPin: 24, pwrPin: 18
});
await epd.init();
// Create a simple canvas
const canvas = createCanvas(400, 200);
const ctx = canvas.getContext('2d');
// White background
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw text and shapes
ctx.fillStyle = 'black';
ctx.font = '36px Arial';
ctx.fillText('Canvas Demo', 50, 100);
// Draw a rectangle around the text
ctx.strokeStyle = 'gray';
ctx.lineWidth = 2;
ctx.strokeRect(10, 10, 380, 180);
// Draw to display
await epd.drawCanvas(canvas, 100, 100);
await epd.display();
await epd.sleep();
})();
Creates a display instance for the specified model.
model
(string): Display model ('2in13', '2in7', '2in7b', '7in5', '7in3f', '13in3k', '13in3b', '13in3gray')colorMode
(string): Color mode ('mono', '4gray', '16gray', '3color', '7color') - must be supported by the displayoptions
(object): Configuration options
Options:
rstPin
(number): Reset GPIO pin (default: 17)dcPin
(number): Data/Command GPIO pin (default: 25)busyPin
(number): Busy GPIO pin (default: 24)csPin
(number): Chip Select GPIO pin (default: 22)pwrPin
(number): Power control GPIO pin (default: 18)gpioChip
(string): GPIO chip name (default: 'gpiochip0')busNumber
(number): SPI bus number (default: 0)deviceNumber
(number): SPI device number (default: 0)maxSpeedHz
(number): SPI max speed (default: 4000000)accentColor
(string): For 3-color displays, specify 'red' or 'yellow' accent colorvcom
(number): VCOM voltage for IT8951 displays (default: -2.30)
Returns array of supported models with their specifications.
await epd.init()
- Initialize the displayawait epd.clear()
- Clear display to background colorawait epd.display()
- Update the display with current bufferawait epd.sleep()
- Put display into low power modeawait epd.cleanup()
- Clean up resources
await epd.powerOn()
- Turn on display power (automatically called during init)await epd.powerOff()
- Turn off display power (automatically called during cleanup)
epd.setPixel(x, y, color)
- Set individual pixelepd.drawLine(x0, y0, x1, y1, color)
- Draw lineepd.drawRect(x, y, width, height, color, filled)
- Draw rectangleawait epd.drawPNG(filePath, x, y)
- Load and draw PNG imageawait epd.drawCanvas(canvas, x, y)
- Draw HTML5 Canvas object
7-color displays:
epd.setPixelColor(x, y, colorName)
- Set pixel using color nameepd.drawColorLine(x0, y0, x1, y1, colorName)
- Draw colored lineepd.drawColorRect(x, y, w, h, colorName, filled)
- Draw colored rectangleawait epd.show7Block()
- Display all 7 colors in blocks
3-color displays:
epd.drawBlackRect(x, y, w, h, filled)
- Draw black rectangleepd.drawRedRect(x, y, w, h, filled)
- Draw accent color rectangleawait epd.show3ColorTest()
- Display 3-color test pattern
- Monochrome mode:
0
= black,1
= white - 4-grayscale mode:
0
= black,1
= dark gray,2
= light gray,3
= white - 16-grayscale mode:
0
= black,1-14
= varying gray levels,15
= white - 3-color mode:
0
= black,1
= white,2
= accent color (red or yellow, configurable) - 7-color mode:
0
= black,1
= white,2
= green,3
= blue,4
= red,5
= yellow,6
= orange
For 7-color displays, you can use color names:
epd.setPixelColor(x, y, 'RED');
epd.drawColorRect(10, 10, 100, 50, 'BLUE', true);
epd.drawColorLine(0, 0, 100, 100, 'GREEN');
Available colors: BLACK
, WHITE
, RED
, GREEN
, BLUE
, YELLOW
, ORANGE
├── index.js # Main entry point and factory functions
├── EPDBase.js # Base class with common functionality
├── displays/
│ ├── index.js # Display module exports
│ ├── EPD2in13.js # 2.13" monochrome display driver
│ ├── EPD2in7.js # 2.7" mono/4-grayscale display driver
│ ├── EPD2in7b.js # 2.7" 3-color display driver
│ ├── EPD7in5.js # 7.5" monochrome display driver
│ ├── EPD7in3f.js # 7.3" 7-color display driver
│ ├── EPD13in3k.js # 13.3" mono/4-grayscale display driver
│ ├── EPD13in3b.js # 13.3" 3-color display driver
│ └── EPD13in3Gray.js # 13.3" 16-grayscale display driver (IT8951)
├── examples/ # Usage examples for each feature
└── README.md # This file
To add support for a new display model:
-
Create a new file in
displays/
(e.g.,EPDNewModel.js
) -
Extend
EPDBase
class:const EPDBase = require('../EPDBase'); class EPDNewModel extends EPDBase { constructor(options = {}) { super(options); this.width = 200; // Set display width this.height = 200; // Set display height this.colorMode = options.colorMode || 'mono'; this.bitsPerPixel = this.colorMode === '4gray' ? 2 : 1; this.initializeBuffer(); } async initDisplay() { // Implement display-specific initialization } async displayImage() { // Implement display-specific image update } }
-
Add to
displays/index.js
exports -
Add case to
createDisplay()
function
- Raspberry Pi with SPI enabled
gpiod
tools installed (apt install gpiod
)- Node.js 14+ (recommended: Node.js 18+)
Model | GPIO Chip | Pin Layout | SPI Interface | Initialization Notes |
---|---|---|---|---|
Pi 1 Model A/B | gpiochip0 |
26-pin (original) | /dev/spidev0.0 |
Limited pins, use adapted wiring |
Pi 1 Model A+/B+ | gpiochip0 |
40-pin | /dev/spidev0.0 |
Full compatibility |
Pi 2 Model B | gpiochip0 |
40-pin | /dev/spidev0.0 |
Full compatibility |
Pi 3 Model A+/B+ | gpiochip0 |
40-pin | /dev/spidev0.0 |
Full compatibility |
Pi 4 Model B | gpiochip0 |
40-pin | /dev/spidev0.0 |
Full compatibility |
Pi 5 | gpiochip4 |
40-pin | /dev/spidev0.0 |
Requires gpioChip option |
Critical: Pi 5 uses gpiochip4
instead of gpiochip0
. You must specify this in your configuration or initialization will fail:
const epd = createDisplay('13in3k', 'mono', {
rstPin: 17,
dcPin: 25,
busyPin: 24,
pwrPin: 18,
gpioChip: 'gpiochip4' // Required for Pi 5
});
Original Pi 1 Model A/B have only 26 GPIO pins. Use this pin mapping:
- RST: GPIO 17 ✓ (available)
- DC: GPIO 25 ✓ (available)
- CS: GPIO 22 ✓ (available)
- BUSY: GPIO 24 ✓ (available)
- PWR: GPIO 18 ✓ (available)
The following GPIO pins are required for proper operation:
Function | Default Pin | Description |
---|---|---|
RST | GPIO 17 | Reset signal (output) |
DC | GPIO 25 | Data/Command signal (output) |
CS | GPIO 22 | SPI Chip Select (output) |
BUSY | GPIO 24 | Busy status signal (input) |
PWR | GPIO 18 | Power control (output) - Critical for cold boot operation |
Important: The power pin (PWR) is essential for reliable operation, especially when cold-booting the Raspberry Pi. Many Waveshare e-paper displays require explicit power control to function properly. Without this pin, the display may:
- Not respond to commands after a cold boot
- Work only after running other display programs first
- Show inconsistent initialization behavior
The driver uses SPI interface with these default settings:
- SPI Bus:
/dev/spidev0.0
(bus 0, device 0) - Speed: 4 MHz (configurable)
- Mode: SPI Mode 0 (CPOL=0, CPHA=0)
sudo raspi-config
# Navigate to: Interfacing Options → SPI → Enable
# Or add to /boot/config.txt:
echo "dtparam=spi=on" | sudo tee -a /boot/config.txt
sudo reboot
Symptoms: Display appears to initialize (no errors) but screen doesn't update, or works only after running manufacturer's example programs.
Cause: Missing power pin configuration. Many Waveshare displays have internal power management that requires explicit control.
Solution: Always specify the power pin in your configuration:
const epd = createDisplay('13in3k', 'mono', {
rstPin: 17,
dcPin: 25,
busyPin: 24,
pwrPin: 18
});
Permission errors: Make sure you're running with appropriate permissions or add your user to the gpio
group:
sudo usermod -a -G gpio $USER
# Then log out and back in
GPIO already in use: If you see "Device or resource busy" errors, check what's using the GPIO pins:
# Check what's using GPIO pins
sudo lsof /dev/gpiomem
# Or check specific pins
gpioinfo | grep -E "(17|18|24|25)"
If you're unsure which GPIO chip your Pi uses, run:
gpioinfo | head -n 1
For Pi 5, you'll see: gpiochip4 - 54 lines
For older Pi models: gpiochip0 - 54 lines
(or similar)
You can also set the GPIO chip via environment variable:
export GPIO_CHIP=gpiochip4 # For Pi 5
node your-epd-program.js
Then check for this variable in your code:
const epd = createDisplay('13in3k', 'mono', {
gpioChip: process.env.GPIO_CHIP || 'gpiochip0'
});
If while loading canvas you get this error:
Error: libcairo.so.2: ELF load command address/offset not page-aligned
Tru removing canvas and re-installing by compiling from source.
rm -fr node_modules/canvas/
npm install canvas --build-from-source
This can happen if you compile Node.js from source because the precompiled ciaro library that comes with canvas might not be compatible with what you have.
See the examples/ directory for working examples of each feature. Each example demonstrates a specific capability and can be copied directly into your projects.
const epd = createDisplay('7in3f', '7color');
await epd.init();
// Show all 7 colors in blocks
await epd.show7Block();
// Draw using color names
epd.drawColorRect(50, 50, 100, 80, 'RED', true);
epd.drawColorLine(0, 100, 800, 100, 'BLUE');
epd.setPixelColor(10, 10, 'GREEN');
await epd.display();
// Black/White/Red display
const epd = createDisplay('13in3b', 'red');
// Or Black/White/Yellow display
const epd = createDisplay('13in3b', 'yellow');
await epd.init();
// Draw in different colors
epd.drawBlackRect(50, 50, 200, 100, true); // Black rectangle
epd.drawRedRect(300, 50, 200, 100, true); // Accent color rectangle
// White is the background color
// Show test pattern
await epd.show3ColorTest();
The drawCanvas()
method allows rendering HTML5 Canvas objects directly to the display. This enables dynamic text rendering with TrueType fonts, graphics drawing, and complex layouts:
const { createCanvas, registerFont } = require('canvas');
// Register a TrueType font
registerFont('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', { family: 'MyFont' });
// Create and configure canvas
const canvas = createCanvas(800, 400);
const ctx = canvas.getContext('2d');
// White background
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw text with custom font
ctx.fillStyle = 'black';
ctx.font = '48px MyFont';
ctx.textAlign = 'center';
ctx.fillText('Hello E-Paper!', canvas.width / 2, canvas.height / 2);
// Draw graphics
ctx.strokeStyle = 'black';
ctx.lineWidth = 3;
ctx.strokeRect(10, 10, canvas.width - 20, canvas.height - 20);
// Render to display
await epd.drawCanvas(canvas, 0, 0);
Canvas Features:
- TrueType font rendering with
canvas
package - Text, shapes, gradients, and complex graphics
- Automatic color conversion for all display modes
- Transparency support (transparent pixels become background color)
- Full HTML5 Canvas API compatibility
Requirements:
Install the canvas
package for Canvas support:
npm install canvas
See canvas-example.js
for a complete working example.
The driver automatically converts PNG images to the appropriate color format:
// 7-color display will convert RGB to nearest of 7 colors
await epd.drawPNG('colorful-image.png', 0, 0);
// 3-color display will detect red/yellow regions and convert others to black/white
await epd.drawPNG('mixed-color-image.png', 0, 0);
The original display classes are still available for backward compatibility:
const { EPD13in3k } = require('waveshare-epaper');
const epd = new EPD13in3k();
However, using the new factory function is recommended:
const { createDisplay } = require('waveshare-epaper');
const epd = createDisplay('13in3k', 'mono');
To publish a new version to npm:
npm version patch # or minor/major
npm publish
MIT License - see LICENSE file for details.
Based on Waveshare example code.