feat(displays): per-display devices + DDC/CI capability entities
Restructure how displays are exposed in Home Assistant:
Each physical monitor is now its own HA device linked to the media-server
hub via `via_device`. The hub keeps the media_player + script buttons; per-
display devices hold the power switch, brightness slider, and the new
capability entities. This lets users place displays in their own area/room
and keeps related entities grouped together in the UI.
New platforms:
- sensor: DisplayResolutionSensor (diagnostic, from EDID)
- binary_sensor: DisplayPrimaryBinarySensor + DisplayPowerControlBinarySensor
(both diagnostic; help users see why a power switch is or isn't created)
- select: DisplayInputSourceSelect (HDMI1/DP1/...), DisplayColorPresetSelect
(color temperature), DisplayPictureModeSelect (VCP 0xDC scene modes)
- number: added DisplayContrastNumber alongside brightness
Other changes:
- display_device helper centralises the per-display DeviceInfo; pulls real
manufacturer/model from EDID; device name no longer prepends the hub
title since via_device already shows the hierarchy.
- api_client gains set_display_{contrast,input_source,color_preset,picture_mode}
and stops forcing `?refresh=true` on every poll so HA can ride the
server's TTL cache instead of triggering full DDC/CI probes per entity.
- select / number entities now check the server's `success` flag and re-
sync from the actual monitor state when a write was silently rejected
(some monitors honor reads but ignore writes for certain DDC/CI codes).
Bumps manifest.json to 0.3.0 - the device topology change is user-visible
and existing brightness/power entities migrate to per-display devices on
first reload (unique_ids are preserved).
This commit is contained in:
@@ -33,6 +33,10 @@ from .const import (
|
||||
API_DISPLAY_MONITORS,
|
||||
API_DISPLAY_BRIGHTNESS,
|
||||
API_DISPLAY_POWER,
|
||||
API_DISPLAY_CONTRAST,
|
||||
API_DISPLAY_INPUT_SOURCE,
|
||||
API_DISPLAY_COLOR_PRESET,
|
||||
API_DISPLAY_PICTURE_MODE,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -348,12 +352,12 @@ class MediaServerClient:
|
||||
return await self._request("POST", API_BROWSER_PLAY, {"path": file_path})
|
||||
|
||||
async def get_display_monitors(self) -> list[dict[str, Any]]:
|
||||
"""Get list of connected monitors with brightness and power info.
|
||||
"""Get list of connected monitors with brightness, power, DDC/CI state.
|
||||
|
||||
Returns:
|
||||
List of monitor dicts with id, name, brightness, power_supported, power_on, resolution
|
||||
Uses the server's short TTL cache so per-entity polling does not pay
|
||||
the full DDC/CI probe cost on every call.
|
||||
"""
|
||||
return await self._request("GET", f"{API_DISPLAY_MONITORS}?refresh=true")
|
||||
return await self._request("GET", API_DISPLAY_MONITORS)
|
||||
|
||||
async def set_display_brightness(self, monitor_id: int, brightness: int) -> dict[str, Any]:
|
||||
"""Set brightness for a specific monitor.
|
||||
@@ -383,6 +387,30 @@ class MediaServerClient:
|
||||
"POST", f"{API_DISPLAY_POWER}/{monitor_id}", {"on": on}
|
||||
)
|
||||
|
||||
async def set_display_contrast(self, monitor_id: int, contrast: int) -> dict[str, Any]:
|
||||
"""Set DDC/CI contrast for a specific monitor (0-100)."""
|
||||
return await self._request(
|
||||
"POST", f"{API_DISPLAY_CONTRAST}/{monitor_id}", {"contrast": contrast}
|
||||
)
|
||||
|
||||
async def set_display_input_source(self, monitor_id: int, source: str) -> dict[str, Any]:
|
||||
"""Switch a monitor's DDC/CI input source by enum name (e.g. 'HDMI1')."""
|
||||
return await self._request(
|
||||
"POST", f"{API_DISPLAY_INPUT_SOURCE}/{monitor_id}", {"source": source}
|
||||
)
|
||||
|
||||
async def set_display_color_preset(self, monitor_id: int, preset: str) -> dict[str, Any]:
|
||||
"""Apply a DDC/CI color preset by enum name (e.g. 'COLOR_TEMP_6500K')."""
|
||||
return await self._request(
|
||||
"POST", f"{API_DISPLAY_COLOR_PRESET}/{monitor_id}", {"preset": preset}
|
||||
)
|
||||
|
||||
async def set_display_picture_mode(self, monitor_id: int, code: int) -> dict[str, Any]:
|
||||
"""Apply a DDC/CI picture/scene mode (VCP 0xDC) by raw code."""
|
||||
return await self._request(
|
||||
"POST", f"{API_DISPLAY_PICTURE_MODE}/{monitor_id}", {"code": code}
|
||||
)
|
||||
|
||||
|
||||
class MediaServerWebSocket:
|
||||
"""WebSocket client for real-time media status updates."""
|
||||
|
||||
Reference in New Issue
Block a user