Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Mirror

A calm e-ink display that shows one thought per hour — an idea, a question, or a poem — on a Waveshare 7.5" panel driven by a Raspberry Pi.

Mirror is deliberately not a dashboard. No news, no metrics, no notifications. Every card is built around a single piece of curated content, rendered in big type with a lot of whitespace, and replaced once an hour. The design goal is a surface you glance at while walking past, that occasionally hands you something worth turning over for the rest of the day.

Mirror showing an evening card

┌─────────────────────────────────────────────┐
│  THIS MORNING                               │
│                                             │
│  What would you attempt if the              │
│  attempt itself were the reward?            │
│                                             │
│  ───────────────────────────────            │
│  Clear, 61F now · high 78, low 55           │
│  Mirror · hourly rotation                   │
└─────────────────────────────────────────────┘

Background on how the project came to be: huanyu.io.

How it works

Two content paths share one render pipeline:

  • Editions (3×/day — 07:00, 12:00, 18:00): an LLM agent receives an offer of 2–3 candidate items assembled by a script, picks exactly one, and writes the card around it. The agent never invents content and never blends items.
  • Interludes (every other hour, 08:00–23:00): a pure script composes the entire payload from the curated pools. No model runs at all. Most of what the panel shows involves no LLM.

Three daypart layouts: a morning card (question / aphorism / observation), a midday card (a mental model, a small vignette, or a word history), and an evening card (a poem, presented plainly — no interpretation, no framing).

Content comes from committed pools

Everything shown is drawn from three JSONL files curated into the repo — ~130 poems (public domain, incl. classical Chinese kept in the original), ~213 ideas, ~102 morning fragments — plus a live weather line (Open-Meteo). A validator (pools.py --validate) enforces schema, length caps, and no-emoji rules (emoji render as tofu boxes on the panel's fonts).

The telemetry log is the state

Every render appends one row of telemetry, and all anti-repetition windows are derived from that log rather than stored: a poem can't reappear within 14 days, a topic within 21, an idea domain within 5, a fragment within 14. There is no separate state file to corrupt or drift — replaying the log reproduces the system's memory exactly. A 30-day simulation (--simulate) replays the whole scheme against the real pools and is the launch gate in the test suite.

The resonance lane (rare, personal, never explained)

About twice a week, the evening card may quote one verbatim line the owner wrote at least 30 days ago (mined from their notes vault), placed beside an unrelated poem or idea at moderate conceptual distance — with the connection deliberately never stated. The research behind this (reflective photo displays, technology-mediated reflection, serendipity in recommenders) says asserted connections read as horoscope material, while unstated juxtaposition lets the viewer complete the link themselves. Personal material is otherwise absent by design: the default card knows nothing about its owner.

A weekly job may also distill "threads" (sustained life patterns) from calendar/notes evidence, behind a hard evidence gate — at least 3 distinct days, 2 evidence kinds, 1 non-agent source. Zero threads passing in a given week is the expected outcome. This exists because v1 of this project taught the opposite lesson: mining an agent's operational logs for "signals" produces repetitive, over-indexed, forced-feeling cards.

Layout & rendering

  • 800×480, 1-bit monochrome. Fixed type scale (title 44px / hero 34px / body 23px / small 16px).
  • Poems are never re-wrapped — the poet's line breaks are the layout. The whole block shrinks (32px → 18px) until the widest line fits.
  • Icons are drawn as PIL vector primitives (four of them, total). No glyph fonts, no emoji.
  • Payloads are sanitized against per-layout character caps with a deterministic fallback chain (payload → last-good state → static default), then pushed over SSH and atomically swapped before render.

Repo tour

Path What it is
scripts/next_context.py Hour routing, offers, interlude payload composition, --simulate
scripts/ledger.py Telemetry-derived repetition windows
scripts/pools.py Pool loading + validation CLI
scripts/resonance.py The personal lane: artifact eligibility, seeded scheduling
scripts/build_artifacts.py Notes-vault → verbatim artifact registry (weekly)
scripts/distill_threads.py Evidence gate for proposed personal threads (weekly)
scripts/sanitize_payload.py Limits, fallbacks, telemetry identity
scripts/render_mirror.py Pillow renderer (runs on the Pi; --dry-run --png-out locally)
scripts/push_payload.sh Sanitize → SCP → atomic swap → render → telemetry
data/*.jsonl The curated content pools
references/ Architecture notes, runbook, cron policy
tests/ Plain-python suites, no framework; test_simulation.py is the launch gate

Running it

The renderer and content pipeline are portable; the scheduling assumes an agent workspace with a cron registry and an agent that can follow SKILL.md (this install uses OpenClaw). What runs standalone today:

# build a context/payload for the current hour
python3 scripts/next_context.py --emit-payload --json-path /tmp/ctx.json --payload-path /tmp/payload.json

# preview the payload as a PNG, no hardware needed
python3 scripts/render_mirror.py --payload /tmp/payload.json --dry-run --png-out /tmp/p.png

# replay 30 days of scheduling without touching state
python3 scripts/next_context.py --simulate 30 --start-date 2026-08-01

# validate the pools
python3 scripts/pools.py --validate poems --path data/poems.jsonl

Requirements: Python 3.11+ (stdlib only for the pipeline), Pillow for the renderer, a Raspberry Pi + Waveshare epd7in5_V2 for the panel itself. Local previews need DejaVu fonts — point MIRROR_FONT_DIR at a directory containing them, or the preview falls back to a bitmap font.

Personal configuration (coordinates for weather, SSH host, vault path) lives in a gitignored .env.local — copy .env.local.example and fill it in. The tracked tree contains no location, identity, or host information by design.

Tests

for f in tests/test_*.py; do python3 "$f" || break; done

Each suite is a standalone script that exits non-zero on failure. test_simulation.py replays 30 days against the real pools and asserts no repetition window was violated and no pool ran dry.

Design principles

  1. One thought per screen
  2. Big type, low ornament
  3. Ideas by default; personal material is rare and earned
  4. Monochrome iconography — never emoji glyphs
  5. Respect e-ink rhythm: slow, deliberate, stable
  6. Deterministic fallbacks over ad hoc failure handling
  7. Telemetry for every render — it is the ledger, not a log

About

A calm e-ink display that shows one thought per hour — an idea, a question, or a poem. Raspberry Pi + Waveshare 7.5", agent-curated, script-composed.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages