Raspberry Pi example
A Raspberry Pi (Zero 2 W or newer) with a Pimoroni Inky display: Inky Impression (Spectra 6 or 7-colour, 4″ to 13.3″), Inky wHAT or pHAT. It's a ~100-line Python script.
The code is in examples/rpi-inky.
Since a Pi is usually plugged in, this example also uses the nudge: it tells the hub a notify_url, and the hub pokes it whenever you send a photo, so it shows up within seconds.
Set up
- Enable SPI and I2C (
sudo raspi-config→ Interface options) and install the Inky library:
bash
mkdir -p ~/openframe && cd ~/openframe
python3 -m venv .venv
.venv/bin/pip install "inky[rpi]" pillow requests
curl -O https://gitlab.com/yayitazale/eink-hub/-/raw/main/examples/rpi-inky/openframe.py- In the hub: Add Canvas → Add manually → DIY frame with the display's resolution (e.g. 800×480). Copy the token from Connection.
- Try it:
bash
HUB_URL=http://192.168.1.10:8080 FRAME_TOKEN=… .venv/bin/python openframe.py- Run it at boot with systemd:
ini
# systemd unit: copy to /etc/systemd/system/openframe.service, edit the two variables, then
# sudo systemctl daemon-reload && sudo systemctl enable --now openframe
[Unit]
Description=E-Ink Hub Open Frame (Pimoroni Inky)
After=network-online.target
Wants=network-online.target
[Service]
Environment=HUB_URL=http://192.168.1.10:8080
Environment=FRAME_TOKEN=paste-the-token-here
ExecStart=/home/pi/openframe/.venv/bin/python /home/pi/openframe/openframe.py
WorkingDirectory=/home/pi/openframe
User=pi
Restart=always
RestartSec=30
[Install]
WantedBy=multi-user.targetbash
sudo cp openframe.service /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now openframeHow the code works
- It asks for a full-colour PNG at the display's size and lets the
inkylibrary dither it, because the library's palettes are tuned for each panel's real inks. - It waits
next_poll_sseconds, or less if the hub's nudge arrives onNOTIFY_PORT(8765 by default). SetNOTIFY_PORT=0to turn the listener off. - On a battery-powered Pi you'd rather shut down between polls: replace the loop with a single
poll()and a wake-up timer (e.g. a Witty Pi or PiSugar) set tonext_poll_s.
python
#!/usr/bin/env python3
"""Open Frame example: Raspberry Pi + Pimoroni Inky (Impression, wHAT, pHAT…).
Polls E-Ink Hub, shows new pictures and sleeps for as long as the hub says. Because a Pi is
usually on mains power it also listens for the hub's nudge (notify_url), so photos you send
by hand appear within seconds instead of at the next poll.
Protocol: https://eink-hub-7ba3a3.gitlab.io/diy/protocol
"""
import io
import os
import socket
import threading
from http.server import BaseHTTPRequestHandler, HTTPServer
import requests
from inky.auto import auto
from PIL import Image
HUB_URL = os.environ["HUB_URL"].rstrip("/") # e.g. http://192.168.1.10:8080
TOKEN = os.environ["FRAME_TOKEN"]
NOTIFY_PORT = int(os.environ.get("NOTIFY_PORT", "8765")) # 0 disables the nudge listener
SATURATION = float(os.environ.get("INKY_SATURATION", "0.5"))
RETRY_S = 300
AUTH = {"Authorization": f"Bearer {TOKEN}"}
wake = threading.Event()
inky = auto()
current = None # id of the picture on screen
def lan_ip() -> str:
# The address the hub can reach us at: the one used to route towards it.
host = HUB_URL.split("//", 1)[1].split("/")[0].split(":")[0]
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
s.connect((host, 80))
return s.getsockname()[0]
class Nudge(BaseHTTPRequestHandler):
"""The hub POSTs here (with our token) when there is something new."""
def do_POST(self):
ok = self.headers.get("Authorization") == AUTH["Authorization"]
self.send_response(204 if ok else 401)
self.end_headers()
if ok:
wake.set()
def log_message(self, *args):
pass
def poll() -> int:
global current
params = {
"w": inky.width,
"h": inky.height,
# Full-colour PNG: the inky library dithers it with its own tuned palette.
"format": "png",
"fw": "rpi-inky-1.0",
}
if current:
params["current"] = current
if NOTIFY_PORT:
params["notify_url"] = f"http://{lan_ip()}:{NOTIFY_PORT}/of/v1/notify"
reply = requests.get(f"{HUB_URL}/of/v1/display", params=params, headers=AUTH, timeout=20).json()
if reply["action"] == "show":
png = requests.get(reply["image_url"], timeout=60).content
image = Image.open(io.BytesIO(png)).convert("RGB")
try:
inky.set_image(image, saturation=SATURATION)
except TypeError: # black/white and red/yellow Inkys take no saturation
inky.set_image(image)
inky.show()
current = reply["id"]
print(f"showing {current}", flush=True)
return int(reply["next_poll_s"])
def main():
if NOTIFY_PORT:
server = HTTPServer(("0.0.0.0", NOTIFY_PORT), Nudge)
threading.Thread(target=server.serve_forever, daemon=True).start()
while True:
try:
delay = poll()
except Exception as err: # network down, hub restarting…
print(f"poll failed: {err}", flush=True)
delay = RETRY_S
wake.wait(timeout=delay)
wake.clear()
if __name__ == "__main__":
main()