koryk/inky-impression-server

Server for displaying images on inky impression eink screen

6

stars

3

commits

Python

primary language

May 16, 2026

updated

README

inky-impression-server

A tiny HTTP server that lets you upload an image and have it displayed on a Pimoroni Inky Impression e-ink panel attached to a Raspberry Pi.

An Inky Impression panel displaying an uploaded dashboard image

POST an image, it's fitted to the panel and shown. Built for the slow reality of e-ink: one refresh at a time, bursts collapse to the most recent image, and a configurable cooldown protects panel life.

No authentication. Intended for a trusted LAN / internal network. Put it behind a reverse proxy or firewall if you need access control.

Features

  • Single POST /image endpoint — multipart file field or a raw image body.
  • Latest-wins: uploads during a refresh collapse to the newest one; no multi-minute backlog of stale renders.
  • Serialized rendering: one worker thread owns the panel; overlapping SPI writes (which corrupt e-ink) are impossible by construction.
  • Minimum refresh interval for panel longevity.
  • Auto-detects the panel size/model via the inky library (works on any Inky Impression that inky.auto supports).
  • EXIF-orientation, letterboxing onto a white canvas, saturation boost (e-ink looks washed out otherwise), upload size cap, and image validation.

Requirements

  • Raspberry Pi with a Pimoroni Inky Impression connected.

  • Raspberry Pi OS with I2C and SPI enabled (sudo raspi-configInterface Options, or sudo raspi-config nonint do_i2c 0 && sudo raspi-config nonint do_spi 0, then reboot).

  • Python 3.9+.

  • The Pimoroni inky library and its system dependencies. The easiest path is Pimoroni's installer, which also creates a virtualenv:

    git clone https://github.com/pimoroni/inky
    cd inky && ./install.sh
    

    This creates ~/.virtualenvs/pimoroni. Use that environment below.

Install

git clone https://github.com/koryk/inky-impression-server
cd inky-impression-server

# Use the Pimoroni virtualenv (or any venv where `inky` works):
source ~/.virtualenvs/pimoroni/bin/activate

pip install .

Run

inky-impression-server

or without installing:

python -m inky_impression_server

It listens on 0.0.0.0:8080 by default.

Usage

Upload an image (replace the host with your Pi's address):

curl -X POST -F "file=@photo.jpg" http://raspberrypi.local:8080/image

Raw body also works:

curl -X POST --data-binary @photo.jpg http://raspberrypi.local:8080/image

Response is immediate (202); the e-ink refresh then takes ~25–40s:

{"status":"accepted","seq":4,"note":"rendering now"}

Endpoints

MethodPathPurpose
POST/imageUpload an image (multipart file or raw body). Returns 202 with a seq.
GET/statusWorker/render state as JSON.
GET/Plain-text usage.

GET /status:

{"busy":true,"pending":false,"pending_seq":4,"rendered_seq":3,
 "last_error":null,"panel_size":[1600,1200],"min_interval_seconds":30}
  • busy — a refresh is physically happening now.
  • pending — an image is waiting to render next.
  • rendered_seqseq currently on the panel; when it reaches your upload's seq, your image is showing.
  • last_error — string if the last render failed, else null.

Configuration

All via environment variables:

VariableDefaultMeaning
INKY_HOST0.0.0.0Bind address.
INKY_PORT8080Listen port.
INKY_MIN_INTERVAL30Minimum seconds between the end of one refresh and the start of the next.
INKY_SATURATION0.5Dithering saturation, 0.01.0.
INKY_MAX_UPLOAD_MB16Reject uploads larger than this.

Example:

INKY_PORT=9000 INKY_MIN_INTERVAL=60 inky-impression-server

Run as a service (systemd)

A unit template is in packaging/inky-impression-server.service. Edit User= and the venv path in ExecStart=, then:

sudo cp packaging/inky-impression-server.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now inky-impression-server

systemctl status inky-impression-server
journalctl -u inky-impression-server -f

How it works

The HTTP handler never touches the display. It validates and fits the upload, then drops it into a single "pending" slot and returns 202. One worker thread loops: wait for a pending image → render (~30s, blocking) → enforce the minimum interval → repeat. Because the slot is single-valued, a burst of uploads during a render collapses to "show the most recent one once" — earlier pending uploads are dropped, not queued.

Contributing

Issues and PRs welcome. Keep the concurrency model intact: only the worker thread may touch the Inky device, and the pending slot stays latest-wins.

License

MIT

Contributors

koryk

3 commits

koryk/inky-impression-server

Server for displaying images on inky impression eink screen

6

stars

3

commits

Python

primary language

May 16, 2026

updated

README

inky-impression-server

A tiny HTTP server that lets you upload an image and have it displayed on a Pimoroni Inky Impression e-ink panel attached to a Raspberry Pi.

An Inky Impression panel displaying an uploaded dashboard image

POST an image, it's fitted to the panel and shown. Built for the slow reality of e-ink: one refresh at a time, bursts collapse to the most recent image, and a configurable cooldown protects panel life.

No authentication. Intended for a trusted LAN / internal network. Put it behind a reverse proxy or firewall if you need access control.

Features

  • Single POST /image endpoint — multipart file field or a raw image body.
  • Latest-wins: uploads during a refresh collapse to the newest one; no multi-minute backlog of stale renders.
  • Serialized rendering: one worker thread owns the panel; overlapping SPI writes (which corrupt e-ink) are impossible by construction.
  • Minimum refresh interval for panel longevity.
  • Auto-detects the panel size/model via the inky library (works on any Inky Impression that inky.auto supports).
  • EXIF-orientation, letterboxing onto a white canvas, saturation boost (e-ink looks washed out otherwise), upload size cap, and image validation.

Requirements

  • Raspberry Pi with a Pimoroni Inky Impression connected.

  • Raspberry Pi OS with I2C and SPI enabled (sudo raspi-configInterface Options, or sudo raspi-config nonint do_i2c 0 && sudo raspi-config nonint do_spi 0, then reboot).

  • Python 3.9+.

  • The Pimoroni inky library and its system dependencies. The easiest path is Pimoroni's installer, which also creates a virtualenv:

    git clone https://github.com/pimoroni/inky
    cd inky && ./install.sh
    

    This creates ~/.virtualenvs/pimoroni. Use that environment below.

Install

git clone https://github.com/koryk/inky-impression-server
cd inky-impression-server

# Use the Pimoroni virtualenv (or any venv where `inky` works):
source ~/.virtualenvs/pimoroni/bin/activate

pip install .

Run

inky-impression-server

or without installing:

python -m inky_impression_server

It listens on 0.0.0.0:8080 by default.

Usage

Upload an image (replace the host with your Pi's address):

curl -X POST -F "file=@photo.jpg" http://raspberrypi.local:8080/image

Raw body also works:

curl -X POST --data-binary @photo.jpg http://raspberrypi.local:8080/image

Response is immediate (202); the e-ink refresh then takes ~25–40s:

{"status":"accepted","seq":4,"note":"rendering now"}

Endpoints

MethodPathPurpose
POST/imageUpload an image (multipart file or raw body). Returns 202 with a seq.
GET/statusWorker/render state as JSON.
GET/Plain-text usage.

GET /status:

{"busy":true,"pending":false,"pending_seq":4,"rendered_seq":3,
 "last_error":null,"panel_size":[1600,1200],"min_interval_seconds":30}
  • busy — a refresh is physically happening now.
  • pending — an image is waiting to render next.
  • rendered_seqseq currently on the panel; when it reaches your upload's seq, your image is showing.
  • last_error — string if the last render failed, else null.

Configuration

All via environment variables:

VariableDefaultMeaning
INKY_HOST0.0.0.0Bind address.
INKY_PORT8080Listen port.
INKY_MIN_INTERVAL30Minimum seconds between the end of one refresh and the start of the next.
INKY_SATURATION0.5Dithering saturation, 0.01.0.
INKY_MAX_UPLOAD_MB16Reject uploads larger than this.

Example:

INKY_PORT=9000 INKY_MIN_INTERVAL=60 inky-impression-server

Run as a service (systemd)

A unit template is in packaging/inky-impression-server.service. Edit User= and the venv path in ExecStart=, then:

sudo cp packaging/inky-impression-server.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now inky-impression-server

systemctl status inky-impression-server
journalctl -u inky-impression-server -f

How it works

The HTTP handler never touches the display. It validates and fits the upload, then drops it into a single "pending" slot and returns 202. One worker thread loops: wait for a pending image → render (~30s, blocking) → enforce the minimum interval → repeat. Because the slot is single-valued, a burst of uploads during a render collapses to "show the most recent one once" — earlier pending uploads are dropped, not queued.

Contributing

Issues and PRs welcome. Keep the concurrency model intact: only the worker thread may touch the Inky device, and the pending slot stays latest-wins.

License

MIT

Contributors

koryk

3 commits

Languages

Python

100.0%