feat: rewrite integration (3.0.0)#31
Conversation
… odl-renderer, bump py-opendisplay to 7.2.5)
Pass HA font search directories (/config/www/fonts, /config/media/fonts, /media/fonts) to generate_image() so users can reference custom fonts by name without absolute paths. Requires odl-renderer 0.5.8 which adds the font_dirs parameter to FontManager and generate_image().
- SCHEMA_DRAWCUSTOM validates payload, background, rotate, dither, refresh_type and dry-run at the service boundary rather than at runtime inside the renderer - _async_send_image centralises BLE connection, encryption key parsing and error handling; used by both upload_image and drawcustom
ImageGen was replaced by odl-renderer; rendering logic is now tested in the odl-renderer package itself.
Delete empty tests/ dir, requirements_test.txt, tests workflow, and pytest config now that all drawcustom tests have been removed.
Remove .claude/, .run/, ha_data_provider.md, and uv.lock from version control; add them to .gitignore.
…acker, recorder dependency)
The upload_error exception swallowed the original OpenDisplayError message, making it hard to diagnose failures. Pass str(err) as a translation placeholder so the underlying cause is shown in the HA notification.
Both sensors are diagnostic and disabled by default. All devices now load the sensor platform (moved Platform.SENSOR to _BASE_PLATFORMS), so non-flex devices get temperature, RSSI, and last-seen; battery sensors remain gated on power mode as before. The sensor value_fn signature is widened from AdvertisementData to OpenDisplayUpdate to give access to coordinator-level fields (RSSI, last_seen) alongside advertisement payload fields. Also syncs en.json with strings.json (adds translations for the new sensors, update entity, no_leds/no_buzzers exceptions, and the activate_led/activate_buzzer/drawcustom services that were missing).
- Replace single flat color/flash_count/loop_delay/inter_delay fields with three step groups (color1-3, flash_count1-3, loop_delay1-3, inter_delay1-3); steps 2 and 3 are skipped when flash_count is 0 - Use RGB color picker selector; voluptuous converts [R,G,B] to the firmware's packed 3R-3G-2B byte at validation time - Expose loop_delay and inter_delay in milliseconds (×100ms units) with slider selectors; voluptuous converts to firmware units - Brightness and repeats now use sliders
Adds a Display content image entity that updates via dispatcher signal after every successful upload_image or drawcustom call (including dry-run).
| name: Draw Custom Image | ||
| description: Draws a custom image on one or more E-Paper displays | ||
| target: | ||
| upload_image: |
There was a problem hiding this comment.
Would it make sense for upload_image to (also) take a url?
Or perhaps thats a new service.
My use case is that I have puppet set up with the ability to snapshot HA pages, so I have a URL that can produce the image I want. I want to push that URL.
My current approach is an ODL with a single image entity that covers the whole screen.. which is also still an option.
There was a problem hiding this comment.
Should be implemented now in (ef91895)
action: opendisplay.upload_image
data:
device_id: <your device>
image: "http://homeassistant.local:10000/home?viewport=1000x1000&format=jpeg"One requirement: the URL host has to be in allowlist_external_urls (this keeps the service from being tricked into fetching arbitrary/internal targets). Since puppet serves on a direct port (10000, no ingress) and handles its own HA auth, a one-time allowlist entry is all it needs:
homeassistant:
allowlist_external_urls:
- "http://homeassistant.local:10000"Add tone compression to drawcustom
Bump odl-renderer to 0.5.10, which adds generic rotation, mirror and pivot fields on every element, plus lenient string coercion for `visible` (a templated "false" or empty string now hides an element). Restructure docs/drawcustom/supported_types.md to link the element/field reference to the odl-renderer README as the single source of truth instead of duplicating it (the stale tables predated the icon-anchor rewrite, font dirs, and the new transforms). Only integration-specific content remains: the service call, colors, fonts, entity-backed plots, and templating examples.
Replace leftover OpenEPaperLink-era wording (OEPL AP, ESL, tag) with OpenDisplay device terminology, matching the 3.0 rewrite which targets OpenDisplay BLE devices directly.
Sync the Service Options table with SCHEMA_DRAWCUSTOM / services.yaml: add refresh_type, tone_compression and the target selector; correct dither to the named modes (default "ordered"); document rotate's allowed values; drop the removed ttl option (legacy keys are still accepted but ignored).
Rewrite the Font support section to match 3.0: the search dirs are /config/www/fonts, /config/media/fonts and /media/fonts (per _font_search_dirs); default fonts are bundled with odl-renderer. Remove the 'configure custom font directories' section and the imagegen assets path, both dropped in the rewrite. Also correct the halftone-color note to use the named dither modes instead of the legacy dither=2.
Remove the misplaced color-variants sentence from Basic Usage, normalize the icon example to the mdi: prefix, and fix two section headings that were H1 instead of H2.
Add dkgray/ltgray gray levels and the corrected half_white/hw (light gray) to the color list and the per-element values table. Document the #RGBA and #RRGGBBAA hex forms as accepted, while noting the alpha component is not blended during rendering and has no visible effect. Add an IMPORTANT note that hex colors must be quoted in YAML: an unquoted '#' starts a comment, so 'fill: #FF0000' parses as null and the color is silently dropped.
…ing for draw_Custom to burkes Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Aligns the send_image action defaults with the UI-facing services.yaml default.
BLE access to a tag was not serialized across operations. Only
_async_upload_image self-serialized (cancelling a prior upload_task);
drawcustom/send_image, LED, buzzer and the OTA flow each opened their own
OpenDisplayDevice connection with no coordination, and the library has no
per-address lock. Two automations hitting the same tag raced for the single
BLE link and surfaced a confusing HomeAssistantError("upload_error"). The
reboot-reload path also waited only on upload_task, so it could tear the
connection out from under an in-flight drawcustom/LED/buzzer/OTA.
Add an asyncio.Lock to OpenDisplayRuntimeData (one config entry == one MAC,
so this is a per-MAC lock) and hold it for the full connection lifetime of
every BLE operation: _async_connect_and_run (drawcustom/send_image, upload,
LED, buzzer) and the OTA install flow. Different MACs are not serialized
against each other. The upload_image latest-wins cancel is kept but its
cancel happens before the lock is taken, so it composes without deadlock.
_async_reload_after_reboot and async_unload_entry now drain the same lock
(acquire/release without holding across the reload) to wait for in-flight
BLE ops, replacing the upload_task-only wait.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012g2e8mr132vcizx92WsgiR
_async_send_image awaited device.upload_image(), which runs the library's _prepare_image synchronously on the event loop: rotate + fit + dither + encode + zlib on a full frame. Every upload_image/drawcustom call blocked the HA event loop for the whole CPU stage before any BLE I/O started. py-opendisplay 7.9.0 exposes the split deliberately: prepare_image() is the heavy CPU work and needs only the device config (no BLE), and device.upload_prepared_image() does the BLE transfer. Run prepare_image() in the executor via async_add_executor_job and pass the same dither/tone/fit/rotate options; then upload_prepared_image() on the loop. compress is gated on display.supports_zip to mirror upload_image() (which passes `compress and supports_compression` into _prepare_image); when the panel has no zip support prepare_image() returns compressed_data=None and upload_prepared_image() falls back to the uncompressed protocol, unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012g2e8mr132vcizx92WsgiR
- NEW-3: default _attr_release_notes at class level so async_release_notes() can't raise AttributeError before/without a successful GitHub fetch. - NEW-4: import ColorScheme from opendisplay (re-exported) instead of the undeclared direct epaper_dithering dependency. - NEW-5: add explicit aiohttp.ClientTimeout to all external HTTP calls (GitHub metadata 30s, firmware download 120s, media download 30s). - NEW-6: run blocking os.path.isdir font-dir checks in the executor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012g2e8mr132vcizx92WsgiR
fix: Serialize BLE access per tag with a per-MAC lock
fix: Offload image prepare to executor, keep BLE transfer on the loop
Fix: minor issues in update and services (NEW-3..NEW-6)
|
@g4bri3lDev whats the timeline on 3.0.0 ? I'd like to start using it sometime this week to use a battery powered esp-OD display, which occasionally comes online. So the queueing of images is essential. Should I run with this branch as a custom component (and possibly do fixes), or is it about done and ready so I should just wait a bit? :) |
Adds "partial" as a refresh mode for upload_image and drawcustom. A PartialState per config entry tracks the last uploaded frame + etag; partial refreshes diff against it and update flicker-free via the 0x76 protocol, while full/fast refreshes re-baseline the state so the next partial diffs against the frame actually on the panel. All panel handling lives in the library: automatic fallback to a full upload when partial is not possible, automatic full-frame region expansion on panels that require it (partial_update_support=2, OpenDisplay/Firmware#80), and identical frames skip the transfer entirely. Legacy numeric refresh_type 2/3 map to partial. Also fixes the compression gate for streaming-decompression-only configs (bit 0x01 without ZIP): such panels uploaded every frame uncompressed. Requires py-opendisplay 7.11.1 (partial support, compression gates, and the epaper-dithering 5.0.8 tone=auto NaN fix without which bilevel drawcustom frames render all-black).
feat: partial refresh support via py-opendisplay 7.11.1
Expose use_measured_palettes to Home Assistant; default drawcustom dithering to Burkes
py-opendisplay 7.11.2: epaper-dithering 5.0.9 (FFI validation, tone-map NaN fix line), partial support, compression gate fixes. odl-renderer 0.5.12: plot crash/hang fixes, text color/anchor + O(n) fitting, color coercion tolerance, transform/font/QR perf.
chore: bump py-opendisplay to 7.11.2 and odl-renderer to 0.5.12
Summary
3.0.0 is a ground-up rewrite. 2.x was built around the OpenEPaperLink access-point/tag and ATC protocols with the internal
imagegenengine; 3.0 talks directly to OpenDisplay BLE devices viapy-opendisplayand renders withodl-renderer. Many 2.x services carry over (reworked onto the new stack); OEPL and ATC support is removed. Breaking changes below.New in 3.0
EFR32BG22devices are flashed over a direct BLE connection. nRF / ESP32 devices still show the available version but have no Install button (proxy-unreliable / no BLE OTA path)upload_imageservice: uploads a media-source image or a direct image URLactivate_buzzerservice (Flex)Ported from 2.x (reworked onto the new stack)
drawcustom: now renders viaodl-rendererwith voluptuous schema validation at the service boundaryactivate_led(wassetled): redesigned with up to 3 independent RGB color steps, each with its own flash count and timing (Flex)upload_image/drawcustom(including dry-run).ttffiles in/config/www/fonts,/config/media/fonts, or/media/fonts, referenced by name indrawcustomFixes
drawcustomfield values from pre-3.0 configsBreaking changes
drawcustomelement types and field names follow theodl-rendererschema. See the odl-renderer docsRequirements
py-opendisplay[silabs-ota]7.9.0,odl-renderer0.5.10Closes
Closes #27
Closes #28
Closes #29
Closes #40
Closes #43
Changelog by pre-release
3.0.0-beta.1
imagegenwithodl-rendererdrawcustomservice + service-boundary validation;font_dirswired upupload_imageservice3.0.0-beta.2
activate_ledandactivate_buzzerservices (Flex)3.0.0-beta.3
activate_ledredesigned with 3 independent RGB color steps3.0.0-beta.4
EFR32BG22, direct-connection only; nRF intentionally not offered over a Bluetooth proxy)upload_imageaccepts a direct image URLdrawcustomfield values from pre-3.0 configs3.0.0-beta.5
drawcustom90/270 rotation now transposes the canvas instead of scaling/centering it; regression from theimagegen→odl-rendererrewrite (in drawcustom, the rotate function does not work properly. Image is "scaled" and centered instead of just transposed on the x/y axis #43)3.0.0-beta.6
tone_compressionoption fordrawcustom, mirroringupload_image(Add tone compression to drawcustom #46)3.0.0-beta.7
drawcustomgainsrotation(degrees, positive = clockwise),mirror(h/v/hv) and an optionalpivoton every element, plus lenient string handling forvisible— a templated"false"or empty string now hides the element (odl-renderer 0.5.10)supported_types.mdlinks the element/field reference to the odl-renderer README instead of duplicating it; only integration-specific sections remain (service call, colors, fonts, entity-backed plots, templating)