Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions custom_components/opendisplay/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
ATTR_REFRESH_MODE = "refresh_mode"
ATTR_FIT_MODE = "fit_mode"
ATTR_TONE_COMPRESSION = "tone_compression"
ATTR_USE_MEASURED_PALETTES = "measured_palette"


def _str_to_int_enum(enum_class: type[IntEnum]) -> Callable[[str], Any]:
Expand Down Expand Up @@ -123,6 +124,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,
}
)

Expand All @@ -135,11 +137,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, ...)
Expand Down Expand Up @@ -281,6 +284,7 @@ async def _async_connect_and_run(
hass: HomeAssistant,
entry: "OpenDisplayConfigEntry",
action: Callable[[OpenDisplayDevice], Awaitable[None]],
use_measured_palettes: bool = False,
) -> None:
"""Resolve BLE device, open a connection, run action, handle auth errors."""
address = entry.unique_id
Expand Down Expand Up @@ -325,6 +329,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)
Expand All @@ -351,6 +356,7 @@ async def _async_send_image(
fit: FitMode = FitMode.CONTAIN,
tone: float | str = "auto",
rotate: Rotation = Rotation.ROTATE_0,
use_measured_palettes: bool = False,
) -> None:
"""Upload a PIL image to the device."""
# Split the upload into its heavy CPU half and its BLE-I/O half. The CPU
Expand Down Expand Up @@ -396,7 +402,9 @@ async def _async_send_image(
async def _upload(device: OpenDisplayDevice) -> None:
await device.upload_prepared_image(prepared, refresh_mode=refresh_mode, state=state)

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)

Expand All @@ -414,6 +422,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.
Expand Down Expand Up @@ -463,6 +472,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
Expand Down Expand Up @@ -654,6 +664,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,
Expand All @@ -663,6 +674,7 @@ async def _drawcustom_for_device(
refresh_mode=refresh_mode,
tone=tone_compression,
rotate=Rotation(rotate),
use_measured_palettes=use_measured_palettes,
)


Expand Down
14 changes: 13 additions & 1 deletion custom_components/opendisplay/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ upload_image:
step: 1
mode: slider
unit_of_measurement: "%"
measured_palette:
required: false
default: false
selector:
boolean:

activate_led:
fields:
Expand Down Expand Up @@ -284,7 +289,7 @@ drawcustom:
name: Dither mode
description: Dithering algorithm
required: true
default: "ordered"
default: "burkes"
selector:
select:
translation_key: dither_mode
Expand Down Expand Up @@ -321,6 +326,13 @@ drawcustom:
step: 1
mode: slider
unit_of_measurement: "%"
measured_palette:
name: Use measured palette
description: Use the panel's measured color palette 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
Expand Down
8 changes: 8 additions & 0 deletions custom_components/opendisplay/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@
"tone_compression": {
"description": "Dynamic range compression strength. Leave empty for automatic.",
"name": "Tone compression"
},
"measured_palette": {
"description": "Use the panel's measured color palette when available. Turn off to dither against the theoretical color scheme. No effect on panels without measured data.",
"name": "Use measured palette"
}
},
"name": "Upload image",
Expand Down Expand Up @@ -322,6 +326,10 @@
"description": "Display refresh mode. Partial updates only the changed region without flashing; falls back to a full refresh automatically when not possible.",
"name": "Refresh type"
},
"measured_palette": {
"description": "Use the panel's measured color palette when available. Turn off to dither against the theoretical color scheme. No effect on panels without measured data.",
"name": "Use measured palette"
},
"dry-run": {
"description": "Generate image without uploading to the device.",
"name": "Dry run"
Expand Down
8 changes: 8 additions & 0 deletions custom_components/opendisplay/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@
"tone_compression": {
"description": "Dynamic range compression strength. Leave empty for automatic.",
"name": "Tone compression"
},
"measured_palette": {
"description": "Use the panel's measured color palette when available. Turn off to dither against the theoretical color scheme. No effect on panels without measured data.",
"name": "Use measured palette"
}
},
"name": "Upload image",
Expand Down Expand Up @@ -319,6 +323,10 @@
"description": "Display refresh mode. Partial updates only the changed region without flashing; falls back to a full refresh automatically when not possible.",
"name": "Refresh type"
},
"measured_palette": {
"description": "Use the panel's measured color palette when available. Turn off to dither against the theoretical color scheme. No effect on panels without measured data.",
"name": "Use measured palette"
},
"dry-run": {
"description": "Generate image without uploading to the device.",
"name": "Dry run"
Expand Down
Loading