Implement partial refreshing protocol#42
Conversation
Welcome to Codecov 🎉Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests. ℹ️ You can also turn on project coverage checks and project coverage reporting on Pull Request comment Thanks for integrating Codecov - We've got you covered ☂️ |
# Conflicts: # src/opendisplay/cli.py # src/opendisplay/device.py
| send_ms = (send_end[0] - t_upload_start) * 1000 | ||
| refresh_ms = (t_upload_end - send_end[0]) * 1000 | ||
| kb = bytes_transferred[0] / 1024 | ||
| stats = f"prep {prep_ms:.0f}ms · send {send_ms:.0f}ms ({kb:.1f} KB) · refresh {refresh_ms:.0f}ms" |
There was a problem hiding this comment.
| stats = f"prep {prep_ms:.0f}ms · send {send_ms:.0f}ms ({kb:.1f} KB) · refresh {refresh_ms:.0f}ms" | |
| stats = ( | |
| f"prep {prep_ms:.0f}ms · send {send_ms:.0f}ms " | |
| f"({kb:.1f} KB) · refresh {refresh_ms:.0f}ms" | |
| ) |
|
The pylint failures in
Move
|
|
I believe all was handled. |
Adds client-side support for partial image uploads to OpenDisplay. The protocol-level specifics are covered in the firmware PR; this PR is about making the Python client able to track display state, compute a partial region, send it, and recover back to full uploads when needed. The code has been written by a mix of Claude Code and OpenAI Codex, at my direction.
TODOs
refresh_modeshould remain a normal user-facing option for partial uploads. Right now the client acceptsRefreshMode.PARTIAL, but when partial state is present the fallback path forces a full refresh to re-establish known state. In my opinion, the CLI should not expose this to the caller, except possibly to choose between "fast" and "full" - but not "partial". The library should possibly not accept "PARTIAL" - doing so will lead to invalid states if the "old" image is incorrect. Maybe we need to split this to two api methods, where one (the normal) can take a mode like FULL or FAST, but the other (partial) does not take a mode.. or, it takes a mode, but its afallback_mode, in case partial wasn't possible, and still only FULL or FAST.--state-file, and the library takes a caller-ownedPartialState, but applications may want a higher-level helper around this.The process
The normal upload path is still the same from a caller's perspective: prepare or pass an image, then upload it. If no partial state is supplied, the client uses the existing full-image upload behavior.
Partial uploads are opt-in by passing a
PartialStatetoupload_imageorupload_prepared_image. The first upload with an empty state falls back to a full update, assigns a new etag, and stores the processed palette image in the state. Later uploads compare the new processed image against the stored one, compute a bounding rectangle for changed pixels, align it to the packed-byte boundary, and send only that rectangle through the partial path.If nothing changed, the upload is skipped. If the device rejects the old etag, or if the display/configuration cannot support partial updates, the client clears the stale state and performs a full upload once to recover to a known good image.
The state
PartialStateis caller-owned on purpose. The library mutates it after successful full or partial uploads, but the application decides whether and where to persist it.The state contains the last accepted etag, the processed palette bytes for the image believed to be on the panel, and the image dimensions. It can be serialized with
to_bytes()and restored withfrom_bytes(). The CLI uses this through--state-file, writing the file atomically after each successful upload.Client shape
The main library additions are:
RefreshMode.PARTIALfor requesting true partial updates from the client APIPartialStateplus helpers for serialization, etag generation, NACK parsing, bounding-box diffing, byte-boundary alignment, and old/new rectangle stream constructionupload_imageandupload_prepared_imageflows that try partial first and fall back to full uploads when state is missing, stale, or unsupportedupload --state-filesupport for persistent partial-rendering stateexamples/animate.pyhelper that sends the first frame as a full update and subsequent frames through the partial pathCaveats
Changes
This PR adds the client side for the firmware partial-rendering work:
References: