Files
alexei.dolgolyov 4156dedf5e 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).
2026-05-15 14:46:50 +03:00

57 lines
2.1 KiB
Python

"""Helpers for building per-display DeviceInfo.
Each physical monitor is exposed as its own HA device (linked back to the
media-server hub via `via_device`) so that per-display entities (power
switch, brightness, future per-display sensors) cluster together, can be
placed in their own area/room, and participate in device-based automations.
"""
from __future__ import annotations
from typing import Any
from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.entity import DeviceInfo
from .const import DOMAIN
def display_label(monitor: dict[str, Any]) -> str:
"""Return a user-friendly label for a display monitor.
Resolution is appended when available so that two monitors sharing a
name (e.g. two "Generic PnP Monitor" entries) remain distinguishable.
"""
name = monitor.get("name") or f"Monitor {monitor['id']}"
resolution = monitor.get("resolution")
if resolution:
return f"{name} ({resolution})"
return name
def display_device_identifier(entry: ConfigEntry, monitor_id: int) -> tuple[str, str]:
"""Return the stable identifier tuple for a per-display device."""
return (DOMAIN, f"{entry.entry_id}_display_{monitor_id}")
def display_device_info(entry: ConfigEntry, monitor: dict[str, Any]) -> DeviceInfo:
"""Build DeviceInfo for a per-display device linked to the hub.
Prefers the manufacturer/model reported by the monitor's EDID; falls back
to integration-level defaults so devices still appear sensibly even when
EDID parsing returns blanks.
"""
manufacturer = (monitor.get("manufacturer") or "").strip() or "Remote Media Player"
model = (monitor.get("model") or "").strip() or "Display"
return DeviceInfo(
identifiers={display_device_identifier(entry, monitor["id"])},
via_device=(DOMAIN, entry.entry_id),
# HA's device tree already shows the parent hub above its children
# via `via_device`, so re-stating the entry title here would just
# duplicate the hub name on every child row.
name=display_label(monitor),
manufacturer=manufacturer,
model=model,
)