Skip to content

feat: runtime waveform override api - #12

Open
markomitranic wants to merge 1 commit into
protolux-electronics:refactor-driverfrom
markomitranic:waveform-override-api
Open

feat: runtime waveform override api#12
markomitranic wants to merge 1 commit into
protolux-electronics:refactor-driverfrom
markomitranic:waveform-override-api

Conversation

@markomitranic

@markomitranic markomitranic commented Jul 15, 2026

Copy link
Copy Markdown

Why

let apps tune the grayscale look live from app space — a
settings panel with a −/+ "brightness" slider over the mid grays, plus a grayscale on/off
toggle. Today the LUT is baked into Settings at compile time, so calibrating a new panel
means editing the library and reflashing for every tweak. This adds a runtime channel: an
app pushes its own init/LUT registers and sees the result in ~1s, no rebuild.

It also retires the ad-hoc gc4_tune calibration scaffolding — the way you calibrate the
next panel is now just set_waveform + a normal draw.

Screenshot 2026-07-15 at 15 32 09 * The test photo is Kodak "kodim17" from the [Kodak Lossless True Color Image Suite](https://r0k.us/graphics/kodak/)

What changed

Two layers plus a semantic builder.

1. A generic override channel on EInk — the public API:

  set_waveform(:grayscale, lut: […])
        │  stores the override in GenServer state, keyed by mode
        │  calls driver.reset  ──▶  active_state: nil   (chip forgets its mode)
        ▼
  next EInk.draw(img, mode: :grayscale)
        │  GenServer merges the mode's override into the draw opts
        ▼  driver re-inits and uses opts[:lut] instead of Settings.get_lut
  • EInk.set_waveform(mode, init: […], lut: […]) — both keys optional, stored per mode.
  • EInk.clear_waveform(mode) — drops it, back to the packaged defaults.
  • Cost: set/clear force one full re-init (~1s) on the next draw. By design — the LUT lives
    in chip registers that only a fresh init reloads reliably.

2. Driver plumbing. All three drivers (uc8276, uc8179, uc8253c) now prefer
opts[:init] / opts[:lut] over Settings.get_init / get_lut everywhere Settings is
consulted
— including uc8179's post-draw data-interval read and uc8253c's active_lut_reg
ping-pong. Nothing else in the drivers changed.

3. A semantic builder UC8276.Settings.grayscale_lut([black, dark, light, white], reset)
— builds the five LUT registers from four whiten-frame counts instead of five opaque byte
literals. get_lut(:grayscale, _) now calls grayscale_lut([0, 5, 10, 54], 63), verified
byte-identical to the previous hand-tuned literals, so on-device behavior is unchanged.

The slider flow it enables

  slider k  ──▶  set_waveform(:grayscale, lut: grayscale_lut([0, 5+k, 10+k, 54]))
                 keep black/white anchors fixed, nudge only the mids

eink stays deliberately dumb here: it exposes the raw frame counts and nothing else. The
mapping from a friendly slider to counts — and clamping to the panel's usable frame window —
is the app's policy, not the library's.

Stacking

This sits on top of #11 (the grayscale LUT data). Until that merges, the diff below includes
#11's commit (add 4-level grayscale init and LUT…). The actual new work here is the single
add runtime waveform-override api commit.

@gworkman
gworkman force-pushed the waveform-override-api branch from 57dad35 to 9ac7d5b Compare July 19, 2026 21:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant