A dedicated Raspberry Pi notification center is the ultimate desk companion. Unlike a power-hungry monitor or an OLED prone to burn-in, an e-ink dashboard pulls milliamps during updates and zero power while holding an image. You can mount it on a wall, stick it to your fridge, or wedge it next to your keyboard to track weather, Home Assistant states, or GitHub PRs without the glare of a backlight.
This guide cuts through the guesswork. We are building an always-on dashboard using the Raspberry Pi Zero 2 W running Raspberry Pi OS (Bookworm, 64-bit) and the Pimoroni Inky Impression 4" (7-Color) e-ink display. Below is the exact hardware decision path, the pin mapping, the Python rendering engine, and the specific debugging steps for when the SPI bus inevitably throws a fit.
The Hardware Decision Tree: Pick Your Display
Before buying parts, you need to choose the right display technology for an always-on notification hub. Here is the decision matrix to terminate your search:
| Display Tech | Power Draw (Idle) | Burn-in Risk | Readability (Glare) | Verdict |
|---|---|---|---|---|
| HDMI Monitor | ~15W - 30W | Low | Poor (Reflective) | Overkill and wastes power for static data. |
| TFT LCD (SPI) | ~150mA (Backlight) | High (Static UI) | Fair | Good for video, terrible for 24/7 static dashboards. |
| OLED (I2C/SPI) | ~20mA | Very High | Good | Great for small widgets, but static text will burn in within weeks. |
| 7-Color E-Ink (SPI) | 0mA (Idle) | None | Excellent (Matte) | DEFAULT PICK: Pimoroni Inky Impression 4" (640x400). |
Parts List and Pin Mapping
Here is the exact bill of materials (BOM) and the BCM (Broadcom) pin mapping required for the code below. Prices reflect typical 2026 retail availability.
Bill of Materials
- Compute: Raspberry Pi Zero 2 W (with pre-soldered 40-pin header) — ~$15 USD
- Display: Pimoroni Inky Impression 4" (7-Color, UC8159 controller, SKU: PIM544) — ~$65 USD
- Storage: 32GB MicroSD Card (SanDisk Extreme or Samsung EVO Select) — ~$12 USD
- Power: 5V 2.5A USB-C Power Supply (Official Raspberry Pi or equivalent UL-listed) — ~$10 USD
- Stand: Pimoroni Inky Impression Desk Stand or a 3D-printed wedge.
SPI Pin Mapping (BCM Numbering)
The Inky Impression connects directly to the Pi's GPIO header. If you are using a ribbon cable or custom PCB, wire it exactly as follows:
| Display Pin | Pi Zero 2 W BCM Pin | Physical Pin # | Function |
|---|---|---|---|
| GND | GND | 6, 9, 14, 20, 25, 30, 34, 39 | Ground Reference |
| VCC (3.3V) | 3.3V | 1, 17 | Logic Power |
| SCK | GPIO 11 (SCLK) | 23 | SPI Clock |
| MOSI | GPIO 10 (MOSI) | 19 | SPI Master Out Slave In |
| CS | GPIO 8 (CE0) | 24 | SPI Chip Select 0 |
| DC | GPIO 22 | 15 | Data / Command Selector |
| RST | GPIO 27 | 13 | Hardware Reset |
| BUSY | GPIO 17 | 11 | Controller Busy Flag |
Wiring and Assembly Steps
- Flash the OS: Use Raspberry Pi Imager to flash Raspberry Pi OS (64-bit, Bookworm) to your SD card. In the OS Customization settings, enable SSH, set your WiFi credentials, and crucially, enable the SPI interface under the Services tab.
- Seat the Display: Push the Pi Zero 2 W's GPIO header through the back of the Inky Impression's PCB. Ensure the pins are perfectly aligned before applying pressure. Do not force it at an angle; you will bend the header.
- Secure the HAT: Use the included M2.5 standoffs to secure the display to the Pi. This prevents mechanical stress on the GPIO solder joints.
- Boot and Verify: Insert the SD card, connect power via the Pi's USB-C port (not the display's micro-USB if present), and SSH into the Pi.
- Install Dependencies: Run the following commands to install the required Python libraries and system dependencies:
sudo apt update sudo apt install python3-pip python3-venv libopenjp2-7 libatlas-base-dev mkdir ~/notification-center && cd ~/notification-center python3 -m venv venv source venv/bin/activate pip install pillow requests inky rpi-lgpio
RPi.GPIO library. The inky library now relies on rpi-lgpio as a drop-in replacement for GPIO control. Ensure you install it in your virtual environment as shown above, or the display driver will fail to initialize the DC and RST pins.
The Python Notification Engine
This script targets the Raspberry Pi Zero 2 W. It fetches mock weather and system data, renders a 640x400 buffer using Pillow, and pushes it to the UC8159 e-ink controller. It includes explicit pin definitions and robust error handling for common SPI and network failures.
import sys
import time
import requests
from PIL import Image, ImageDraw, ImageFont
# Explicit Pin Definitions (BCM Numbering) for Pi Zero 2 W
# These map directly to the physical header and the UC8159 controller
PIN_MAP = {
"CS": 8, # SPI CE0
"DC": 22, # Data/Command
"RST": 27, # Hardware Reset
"BUSY": 17 # Busy Indicator
}
try:
# Import the specific driver for the 4" 7-color Impression
from inky.inky_uc8159 import InkyUC8159
from inky.auto import auto
except ImportError:
print("ERROR: 'inky' library not found. Run: pip install inky")
sys.exit(1)
def fetch_dashboard_data():
"""Fetch data from APIs. Using mock data for offline reliability."""
try:
# Replace with your actual OpenWeatherMap or Home Assistant API endpoints
# response = requests.get("https://api.yourservice.com/data", timeout=5)
# return response.json()
return {
"weather": "22°C, Partly Cloudy",
"alerts": "No severe weather alerts.",
"system": "Pi Zero 2 W | Uptime: 14d 4h"
}
except requests.exceptions.RequestException as e:
print(f"Network Error: {e}")
return {"weather": "Offline", "alerts": "Check WiFi", "system": "Error"}
def render_dashboard(data):
"""Draw the notification center UI to a Pillow Image buffer."""
# 640x400 is the native resolution of the Inky Impression 4"
img = Image.new("P", (640, 400), color=0) # 0 = Black background
draw = ImageDraw.Draw(img)
# Load fonts (fallback to default if custom fonts aren't installed)
try:
font_large = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 48)
font_med = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 32)
font_small = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 20)
except IOError:
font_large = font_med = font_small = ImageFont.load_default()
# Draw Header
draw.text((20, 20), "Flux Command Center", fill=3, font=font_large) # 3 = Yellow
draw.line([(20, 80), (620, 80)], fill=1, width=2) # 1 = White
# Draw Weather Module
draw.text((20, 110), "Environment", fill=4, font=font_med) # 4 = Blue
draw.text((20, 160), data["weather"], fill=1, font=font_med)
draw.text((20, 210), data["alerts"], fill=2, font=font_small) # 2 = Green
# Draw System Status
draw.line([(20, 280), (620, 280)], fill=1, width=2)
draw.text((20, 310), "System Status", fill=4, font=font_med)
draw.text((20, 360), data["system"], fill=1, font=font_small)
return img
def main():
print("Initializing Raspberry Pi Notification Center...")
try:
# Initialize display with explicit saturation and pin mapping context
# The Inky library handles the SPI bus routing based on BCM pins
display = InkyUC8159()
display.set_saturation(0.5) # 0.5 provides a good balance of color vs contrast
except FileNotFoundError as e:
if "/dev/spidev" in str(e):
print(f"FATAL SPI ERROR: {e}")
print("SPI is not enabled. Run 'sudo raspi-config' -> Interface Options -> SPI.")
else:
print(f"FATAL FILE ERROR: {e}")
sys.exit(1)
except RuntimeError as e:
print(f"FATAL GPIO ERROR: {e}")
print("Ensure you are running on a Pi and rpi-lgpio is installed.")
sys.exit(1)
while True:
try:
data = fetch_dashboard_data()
img = render_dashboard(data)
print("Pushing buffer to e-ink controller (takes ~15s)...")
display.set_image(img)
display.show()
print("Update complete. Sleeping for 10 minutes.")
# E-ink screens degrade if refreshed too frequently.
# 10 minutes (600s) is a safe interval for 7-color displays.
time.sleep(600)
except KeyboardInterrupt:
print("\nShutdown requested. Clearing screen...")
# Clear to white (color 1) to prevent ghosting when powered off
clear_img = Image.new("P", (640, 400), color=1)
display.set_image(clear_img)
display.show()
sys.exit(0)
except Exception as e:
print(f"Unexpected rendering error: {e}")
time.sleep(60) # Wait 1 min before retrying on unknown errors
if __name__ == "__main__":
main()
Debugging: When the Screen Stays Blank
E-ink displays are unforgiving if the SPI handshake fails. If your screen remains blank, flashes erratically, or throws an exception, follow this decision path.
The First Three Things to Check
- Verify SPI is actually enabled: Bookworm moved the config file. Run
cat /boot/firmware/config.txt | grep spi. You must seedtparam=spi=on(without a#comment in front of it). - Check the FPC Connector: The ribbon cable connecting the glass to the PCB on the Inky board is fragile. Ensure it is fully seated and the black locking latch is pushed down flush.
- Verify User Permissions: If you aren't running as root, ensure your user is in the
spiandgpiogroups:sudo usermod -aG spi,gpio $USER, then reboot.
Ranked Causes for Common Error Strings
| Exact Error String | Most Likely Cause | The Fix |
|---|---|---|
FileNotFoundError: [Errno 2] No such file or directory: '/dev/spidev0.0' |
SPI interface is disabled in the OS, or the spi-bcm2835 kernel module failed to load. |
Run sudo raspi-config, enable SPI, and reboot. Verify with ls /dev/spi*. |
RuntimeError: Cannot determine SOC peripheral base address |
You are using the legacy RPi.GPIO library on a Pi 5 or Bookworm OS without the rpi-lgpio compatibility shim. |
Uninstall RPi.GPIO and install rpi-lgpio via pip in your virtual environment. |
TimeoutError: Display timed out waiting for BUSY pin |
The UC8159 controller crashed during a previous partial refresh, or the physical BUSY pin (BCM 17) is not making contact. | Power cycle the Pi completely (don't just reboot; cut the 5V USB-C power for 10 seconds to reset the e-ink controller logic). |
Extending or Simplifying the Build
Once the baseline dashboard is rendering, you will inevitably want to tweak the footprint or the data pipeline. Here is how to pivot based on your constraints.
How to Simplify (Lower Cost / Faster Refresh)
If the 15-second refresh time of the 7-color display is too slow, or the $65 price tag is too high, downgrade to a 2.13" Monochrome E-Ink (V4) display.
The Pivot: Swap the InkyUC8159 class for the InkyPHAT (or Waveshare equivalent) driver. Monochrome e-ink refreshes in under 2 seconds using partial updates, allowing you to build a live-updating stock ticker or server CPU monitor rather than a static 10-minute dashboard. You will need to change the Pillow image mode from "P" (Palette) to "1" (1-bit black and white).
How to Extend (Smart Home Integration)
Polling HTTP APIs every 10 minutes wastes CPU cycles and battery if you ever move to a LiFePO4 backup.
The Pivot: Replace the while True polling loop with an MQTT subscriber. Install paho-mqtt, connect to your Home Assistant Mosquitto broker, and bind the display update function to an MQTT topic (e.g., homeassistant/notify/desk_dashboard). When Home Assistant detects a state change (like a door opening or a severe weather alert triggering), it publishes a payload, and the Pi wakes the SPI bus to redraw only the changed elements. This drops your active CPU time to near zero and turns the notification center into a true event-driven IoT node.
Start with the 4" 7-color build to validate your physical mounting and API parsing logic. Once the SPI bus is stable and the Pillow rendering is dialed in, migrating to an MQTT-driven event loop is a straightforward software swap that requires zero hardware changes.






