Panel-wear tracking
What this gives you. Brightness-weighted on-hours per display, visible in
the web dashboard and via GET /api/wear, with an advisory after no long
standby window has occurred in 96 h.
When to use it. When you want to know whether your panel is getting the
long dim it needs. Tracking is on by default; turn it off with wear.enabled = false if on-hours accounting is not useful for your setup.
Quick setup. Tracking is on by default — the dashboard panel-exposure card
and dormantctl status are the readout:
dormantctl status
dormant records brightness-weighted panel on-time and shows it in the web dashboard. v1 measures and advises; it does not alter blank/wake timing.
What it records
For displays with readable brightness (ddcci and samsung-tizen), the tracker
samples panel state every wear.sample_interval (default 60s) and records:
total_on_hours— brightness-weighted on-time. One hour at 50% brightness adds 0.5 hours. Blanked, blanking, and waking time adds zero.seeded_usage_hours— a DDC/CI panel's lifetime VCP0xC0counter, read once when a new ledger is created and available.last_long_dwell_epoch_s— the last blanked dwell lasting at leastwear.short_cycle_dwell(default10m).
render_screensaver attributes each loaded source frame from its luma grid and
local wear heat when wear-even metadata is available. The black overlay
attributes zero. Missing luma, heat, or journal data fails open to uniform
attribution and logs wear_screensaver_luma_fallback; scan and journal failures
also emit screensaver_luma_scan_failed and
screensaver_item_journal_overflow. Playback and blanking continue.
The ledger has a wear.grid_rows × wear.grid_cols grid for future spatial
attribution. v1 writes the same value to every cell. It does not know which
region or content was shown.
displays.<id>.panel_type accepts "woled", "qd-oled", or "unknown" and is
stored in the ledger. v1 does not change its wear formula by panel type and does
not auto-detect the value.
Advisory
The panel-exposure card reports how long the display has gone without a blanked
dwell of at least wear.short_cycle_dwell. After wear.advisory_after (default
96h), it shows an advisory such as:
no long standby window in 5 days
The day count is also returned by GET /api/wear. The advisory never forces a
rest window, blank, or state-machine transition.
Ledger files
Each display gets one JSON ledger under:
$XDG_STATE_HOME/dormant/wear/ # when XDG_STATE_HOME is set
~/.local/state/dormant/wear/ # fallback
The filename uses the stable display identity where available: DDC/CI EDID
manufacturer/model/serial, samsung:<host>, or the config display id. Writes are
atomic and mode 0644; the ledger contains no credentials.
dormant persists every wear.persist_interval (default 5m), on shutdown, and
when tracking is disabled at runtime. A crash can lose at most one persistence
interval. Orphaned ledgers are not pruned automatically.
Delete the wear/ directory to erase the history. New ledgers are created on
the next sample.
Configuration
Tracking is enabled by default. While wear.enabled = false, the tracker takes
no samples and performs no ongoing ledger I/O.
[wear]
enabled = true
sample_interval = "60s"
persist_interval = "5m"
read_timeout = "2s"
grid_rows = 9
grid_cols = 16
fallback_brightness = 0.5
screensaver_factor = 0.35
short_cycle_dwell = "10m"
advisory_after = "96h"
[displays.monitor]
panel_type = "qd-oled"
| Key | Default | Description |
|---|---|---|
wear.enabled | true | Enable tracking and ledger I/O |
wear.sample_interval | "60s" | Panel-state sample interval |
wear.persist_interval | "5m" | Ledger write interval |
wear.read_timeout | "2s" | Read budget for one panel sample |
wear.grid_rows | 9 | Logical grid rows; uniform in v1 |
wear.grid_cols | 16 | Logical grid columns; uniform in v1 |
wear.fallback_brightness | 0.5 | Brightness fraction when readback fails |
wear.screensaver_factor | 0.35 | Fixed factor during render_screensaver |
wear.short_cycle_dwell | "10m" | Blanked dwell counted as a long rest window |
wear.advisory_after | "96h" | Time without a long rest window before advising |
Screensaver ordering is host-side estimation, not panel telemetry. order = "wear-even" uses luma and local heat; wear_temperature defaults to 0.05
and shift_heat_bias to 0.25. Flat luma buckets mean dark = .15, medium = .45, and bright = .75. Missing or overflowing grids fall back with a WARN.
This milestone adds no schema-version bump, panel-type weighting, or RGB
weighting.
The tracker is local-only. Its sole network surface is the loopback web API; there is no telemetry, analytics, cloud sync, or phone-home path.