From 9bfefe48e50c810b5352cf3582158178f963937a Mon Sep 17 00:00:00 2001 From: David Lee <247393336+davelee98@users.noreply.github.com> Date: Fri, 3 Jul 2026 19:27:48 -0400 Subject: [PATCH 1/2] expose use_measured_palette to home assistant. changed default dithering for draw_Custom to burkes Co-Authored-By: Claude Opus 4.8 --- custom_components/opendisplay/services.py | 16 ++++++++++++++-- custom_components/opendisplay/services.yaml | 14 +++++++++++++- custom_components/opendisplay/strings.json | 8 ++++++++ .../opendisplay/translations/en.json | 8 ++++++++ 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/custom_components/opendisplay/services.py b/custom_components/opendisplay/services.py index 6ca5651..3821e4f 100644 --- a/custom_components/opendisplay/services.py +++ b/custom_components/opendisplay/services.py @@ -58,6 +58,7 @@ ATTR_REFRESH_MODE = "refresh_mode" ATTR_FIT_MODE = "fit_mode" ATTR_TONE_COMPRESSION = "tone_compression" +ATTR_USE_MEASURED_PALETTES = "use_measured_palettes" def _str_to_int_enum(enum_class: type[IntEnum]) -> Callable[[str], Any]: @@ -122,6 +123,7 @@ def _refresh_type_value(value: Any) -> RefreshMode: vol.Optional(ATTR_TONE_COMPRESSION): vol.All( vol.Coerce(float), vol.Range(min=0.0, max=100.0) ), + vol.Optional(ATTR_USE_MEASURED_PALETTES, default=True): cv.boolean, } ) @@ -134,11 +136,12 @@ def _refresh_type_value(value: Any) -> RefreshMode: vol.Required("payload"): list, vol.Optional("background", default="white"): cv.string, vol.Optional("rotate", default=0): vol.All(vol.Coerce(int), vol.In([0, 90, 180, 270])), - vol.Optional("dither", default="ordered"): _dither_value, + vol.Optional("dither", default="burkes"): _dither_value, vol.Optional("refresh_type", default="full"): _refresh_type_value, vol.Optional(ATTR_TONE_COMPRESSION): vol.All( vol.Coerce(float), vol.Range(min=0.0, max=100.0) ), + vol.Optional(ATTR_USE_MEASURED_PALETTES, default=False): cv.boolean, vol.Optional("dry-run", default=False): cv.boolean, }, extra=vol.REMOVE_EXTRA, # silently drop legacy keys (ttl, preload_type, preload_lut, ...) @@ -278,6 +281,7 @@ async def _async_connect_and_run( hass: HomeAssistant, entry: "OpenDisplayConfigEntry", action: Callable[[OpenDisplayDevice], Awaitable[None]], + use_measured_palettes: bool = True, ) -> None: """Resolve BLE device, open a connection, run action, handle auth errors.""" address = entry.unique_id @@ -316,6 +320,7 @@ async def _async_connect_and_run( mac_address=address, ble_device=ble_device, config=entry.runtime_data.device_config, + use_measured_palettes=use_measured_palettes, encryption_key=encryption_key, ) as device: await action(device) @@ -342,6 +347,7 @@ async def _async_send_image( fit: FitMode = FitMode.CONTAIN, tone: float | str = "auto", rotate: Rotation = Rotation.ROTATE_0, + use_measured_palettes: bool = True, ) -> None: """Upload a PIL image to the device.""" async def _upload(device: OpenDisplayDevice) -> None: @@ -353,7 +359,9 @@ async def _upload(device: OpenDisplayDevice) -> None: fit=fit, rotate=rotate, ) - await _async_connect_and_run(hass, entry, _upload) + await _async_connect_and_run( + hass, entry, _upload, use_measured_palettes=use_measured_palettes + ) jpeg = await hass.async_add_executor_job(_pil_to_jpeg, img) async_dispatcher_send(hass, f"{SIGNAL_IMAGE_UPDATED}_{entry.unique_id}", jpeg) @@ -371,6 +379,7 @@ async def _async_upload_image(call: ServiceCall) -> None: tone_compression: float | str = ( tone_compression_pct / 100.0 if tone_compression_pct is not None else "auto" ) + use_measured_palettes: bool = call.data[ATTR_USE_MEASURED_PALETTES] # A plain URL (e.g. an automation pushing a rendered snapshot) must be # explicitly allowlisted; media-source items are already trusted. @@ -414,6 +423,7 @@ async def _async_upload_image(call: ServiceCall) -> None: fit=fit_mode, tone=tone_compression, rotate=rotation, + use_measured_palettes=use_measured_palettes, ) except asyncio.CancelledError: return @@ -605,6 +615,7 @@ async def _drawcustom_for_device( tone_compression: float | str = ( tone_compression_pct / 100.0 if tone_compression_pct is not None else "auto" ) + use_measured_palettes: bool = call.data[ATTR_USE_MEASURED_PALETTES] await _async_send_image( hass, @@ -614,6 +625,7 @@ async def _drawcustom_for_device( refresh_mode=refresh_mode, tone=tone_compression, rotate=Rotation(rotate), + use_measured_palettes=use_measured_palettes, ) diff --git a/custom_components/opendisplay/services.yaml b/custom_components/opendisplay/services.yaml index de88a43..df1cc0c 100644 --- a/custom_components/opendisplay/services.yaml +++ b/custom_components/opendisplay/services.yaml @@ -68,6 +68,11 @@ upload_image: step: 1 mode: slider unit_of_measurement: "%" + use_measured_palettes: + required: false + default: true + selector: + boolean: activate_led: fields: @@ -283,7 +288,7 @@ drawcustom: name: Dither mode description: Dithering algorithm required: true - default: "ordered" + default: "burkes" selector: select: translation_key: dither_mode @@ -319,6 +324,13 @@ drawcustom: step: 1 mode: slider unit_of_measurement: "%" + use_measured_palettes: + name: Use measured palettes + description: Use measured, per-panel color palettes when available. Turn off to dither against the theoretical color scheme. No effect on panels without measured data. + required: false + default: false + selector: + boolean: dry-run: name: Dry run description: Generate image without sending to device diff --git a/custom_components/opendisplay/strings.json b/custom_components/opendisplay/strings.json index 350d82b..2a864ba 100644 --- a/custom_components/opendisplay/strings.json +++ b/custom_components/opendisplay/strings.json @@ -193,6 +193,10 @@ "tone_compression": { "description": "Dynamic range compression strength. Leave empty for automatic.", "name": "Tone compression" + }, + "use_measured_palettes": { + "description": "Use measured, per-panel color palettes when available. Turn off to dither against the theoretical color scheme. No effect on panels without measured data.", + "name": "Use measured palettes" } }, "name": "Upload image", @@ -321,6 +325,10 @@ "description": "Display refresh mode.", "name": "Refresh type" }, + "use_measured_palettes": { + "description": "Use measured, per-panel color palettes when available. Turn off to dither against the theoretical color scheme. No effect on panels without measured data.", + "name": "Use measured palettes" + }, "dry-run": { "description": "Generate image without uploading to the device.", "name": "Dry run" diff --git a/custom_components/opendisplay/translations/en.json b/custom_components/opendisplay/translations/en.json index ca55005..f91f499 100644 --- a/custom_components/opendisplay/translations/en.json +++ b/custom_components/opendisplay/translations/en.json @@ -190,6 +190,10 @@ "tone_compression": { "description": "Dynamic range compression strength. Leave empty for automatic.", "name": "Tone compression" + }, + "use_measured_palettes": { + "description": "Use measured, per-panel color palettes when available. Turn off to dither against the theoretical color scheme. No effect on panels without measured data.", + "name": "Use measured palettes" } }, "name": "Upload image", @@ -318,6 +322,10 @@ "description": "Display refresh mode.", "name": "Refresh type" }, + "use_measured_palettes": { + "description": "Use measured, per-panel color palettes when available. Turn off to dither against the theoretical color scheme. No effect on panels without measured data.", + "name": "Use measured palettes" + }, "dry-run": { "description": "Generate image without uploading to the device.", "name": "Dry run" From c02068624e6a3e80c330de6bcff8fc1f36dbd2b3 Mon Sep 17 00:00:00 2001 From: David Lee <247393336+davelee98@users.noreply.github.com> Date: Fri, 3 Jul 2026 21:09:52 -0400 Subject: [PATCH 2/2] Default use_measured_palettes to False for service calls Aligns the send_image action defaults with the UI-facing services.yaml default. --- custom_components/opendisplay/services.py | 4 ++-- custom_components/opendisplay/services.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/custom_components/opendisplay/services.py b/custom_components/opendisplay/services.py index 3821e4f..d9b9aa8 100644 --- a/custom_components/opendisplay/services.py +++ b/custom_components/opendisplay/services.py @@ -281,7 +281,7 @@ async def _async_connect_and_run( hass: HomeAssistant, entry: "OpenDisplayConfigEntry", action: Callable[[OpenDisplayDevice], Awaitable[None]], - use_measured_palettes: bool = True, + use_measured_palettes: bool = False, ) -> None: """Resolve BLE device, open a connection, run action, handle auth errors.""" address = entry.unique_id @@ -347,7 +347,7 @@ async def _async_send_image( fit: FitMode = FitMode.CONTAIN, tone: float | str = "auto", rotate: Rotation = Rotation.ROTATE_0, - use_measured_palettes: bool = True, + use_measured_palettes: bool = False, ) -> None: """Upload a PIL image to the device.""" async def _upload(device: OpenDisplayDevice) -> None: diff --git a/custom_components/opendisplay/services.yaml b/custom_components/opendisplay/services.yaml index df1cc0c..8d98dad 100644 --- a/custom_components/opendisplay/services.yaml +++ b/custom_components/opendisplay/services.yaml @@ -70,7 +70,7 @@ upload_image: unit_of_measurement: "%" use_measured_palettes: required: false - default: true + default: false selector: boolean: