WORKBENCH / DISPATCH 004 / 5

The Quiet Press: a newspaper that turns itself off

A Raspberry Pi, an e-paper panel, and a battery that lasts because the device spends almost all its life switched off. The architecture, the design, and what the schedule taught me.

  • raspberry-pi
  • fastapi
  • playwright
  • home-assistant
  • mcp
  • e-paper
STATUS · SHIPPED
An e-paper newspaper display on a pale wooden shelf in raking morning light. A portrait Waveshare panel mounted on a Raspberry Pi board shows a grayscale broadsheet with a blackletter masthead reading The Quiet Press, a large date, two columns of text, and a weather line at the foot; a sprig of eucalyptus in a stoneware cup sits to the right, against a soft paper-bone wall.

There is a newspaper on my shelf that prints three times a day, runs on a battery, and spends roughly 99 percent of its life switched off. Not asleep: off. This is the story of how it got there.

The hardware

The press itself is modest: a Raspberry Pi 4, a Waveshare 7.5 inch e-paper panel (800 by 480, the V2), and a PiSugar 3 battery hat. The panel is nominally one-bit, black or white, but the driver has a quietly documented four-level grayscale mode. Each pixel becomes one of four luminance bytes:

--ink          #000     →  0x00   (black)
--gray         #7a7a7a  →  0x80
--gray-light   #c9c9c9  →  0xc0
--paper        #fff     →  0xff   (white)

Four tones turn out to be exactly enough for a newspaper: ink, paper, a secondary text gray, and a hairline gray for rules that should whisper.

The whole thing stands in portrait on a shelf. E-paper keeps its image at zero power, which is the trick the entire architecture leans on: the display does not need the computer once the page is printed.

The power model

The Pi boots, asks a server for the current edition, writes 384,000 bytes to the panel (one byte per pixel), tells the PiSugar when to wake it next, and shuts itself down. The full cycle takes about two minutes. Between cycles the only thing drawing power is the PiSugar’s real-time clock, waiting for its alarm.

All intelligence lives in the backend, a FastAPI service in a container on my home server. The device is deliberately dumb: it obeys a single field in the API response and nothing else.

{ "scene": "night", "color_mode": "gray4", "next_wake_minutes": 480 }

Scheduling logic, content, rendering, even “should I wake less often because the battery is low”: all server-side, all changeable without touching the Pi.

A browser as typesetter

I did not want to draw pixels with a graphics library. The editions are Jinja2 templates rendered by headless Chromium (Playwright) at 480 by 800, with bundled woff2 fonts so renders are deterministic and offline-safe. Real typography, flexbox, proper kerning. The screenshot then gets quantized to the panel’s four levels: templates use exact tones so they snap cleanly, photos get error-diffusion dithering instead. A server-side render on a cache miss takes about a second; the worker pre-renders each edition before its window opens, so the device usually hits warm cache.

Design before code

Before any backend existed, the editions were designed as artboards on a Paper canvas, at exact panel resolution. We argued about them like a tiny editorial board: a broadsheet with a blackletter masthead won the morning slot, a Swiss-typography date sheet lost, a calendar took the office hours, a single quote took the night.

The artboards stayed the source of truth all the way through: when the templates drifted, we compared against the canvas, not against memory.

The losing Swiss design got a second life, which I will come back to.

The schedule follows the sun

The morning edition (the broadsheet: an on-this-day lead story from Wikipedia, three year-lookback briefs, weather from the station on my roof via Home Assistant) prints at sunrise, clamped between 05:30 and 07:45. The day edition is my calendar, refreshed hourly during office hours. The night edition is a single quote from my Readwise library, printed at sunset, clamped between 18:00 and 22:30, and it holds the glass all night at zero power. Sun times come from Home Assistant’s sun.sun; if that is ever unreachable, fixed hours take over and nothing breaks.

On weekends the calendar stays home. The resurrected Swiss design (the date sheet that lost the morning slot) prints at sunrise as the weekend edition and holds until sunset: a huge date, the history briefs, the weather, one line about today’s appointments. Three wakes a day instead of twelve, which the battery appreciates.

Last night I watched the first sunset-anchored cycle land: the night edition printed at 21:27:59, sunset to the minute, and the device was told to sleep 480 minutes. That wake-up lands at 05:30, the sunrise clamp, on a Saturday. The schedule knew before I did.

The weekend edition running for real: a framed e-paper panel standing on a desk in daylight, showing a heavy sans-serif Saturday 13, three dated history briefs, a weather line reading partly cloudy, 12 degrees, rain from 14:00, a line noting one appointment at 20:00, and sun times 04:40 to 21:27. A monitor and curtained window sit softly out of focus behind it.
The Swiss design that lost the morning slot, alive on the shelf as the weekend edition. The same Saturday the schedule called the night before, holding the glass at zero power until sunset.

A sub-editor on retainer

The weakest part of the paper was the prose. Rule-based curation cut Wikipedia text to fit character budgets, and it showed: headlines like “Air India Flight 171” (an article title, not a headline) and briefs that ended mid-phrase. So the backend now passes the curated lead and briefs through one Claude API call a day. Haiku, structured output validated against a Pydantic schema, a system prompt that carries the house voice and forbids touching facts, years, or names:

class EditedEdition(BaseModel):
    lead: EditedLead          # headline, deck
    briefs: list[EditedBrief]  # year stays the original; only text is rewritten

edition = client.messages.parse(
    model="claude-haiku-4-5",
    max_tokens=2048,
    system=PRESS_VOICE,
    messages=[{"role": "user", "content": curated}],
    output_format=EditedEdition,
).parsed_output

“Air India Flight 171” came back as “Air India flight crashes near Ahmedabad”. The deck went from a Wikipedia first sentence to an actual deck.

The call is never load-bearing. No API key, an outage, a timeout, a malformed response: every failure path returns the rule-based copy and the edition prints anyway. The result is cached per date, so it costs one call and about a tenth of a cent per day. Character budgets are re-enforced mechanically after editing; the model is asked to write short, the trimmer guarantees it.

Honest telemetry

The PiSugar reports charge by measuring cell voltage, and every reading happens mid-boot under load. The result is a sawtooth: 93.5, 92.2, 87.5, 90.9, 91.5 over five hours, on a battery that was actually draining about 0.7 percent per hour. The fix is boring and effective. Keep the last five readings and report the median, not the latest sample:

self._history: deque[float] = deque(maxlen=5)

def battery_display(self) -> float:
    return round(statistics.median(self._history), 1)

The median goes to Home Assistant and to the printed page; the raw value stays available as a sensor attribute for anyone who misses the chaos. When the battery runs low, the paper says so the way a paper would: a black banner across the top, “low ink, please charge”.

What I keep from this one

The architecture that made everything easy was the dumbest one: a device that only knows how to obey one number, and a server that owns every decision. Every feature since launch (grayscale, solar scheduling, the weekend edition, the Claude editor, battery smoothing) shipped as a backend deploy while the Pi kept waking, printing, and switching itself off, none the wiser.

Build the brain where you can reach it. Let the thing on the shelf just be a newspaper.

The e-paper press on the shelf in soft morning light, its screen showing the masthead over a single line, 'Walks, not notifications', and a short note: a small newspaper, distributed by hand and e-paper, designed to be read slow and shared well. A sprig of eucalyptus in a stoneware cup stands beside it.

BUILT.