Add support for philips js screen state (#62775)

pull/62597/head^2
Joakim Plate 2022-01-04 16:14:44 +01:00 committed by GitHub
parent 06f05d2302
commit 7c6297db86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 149 additions and 105 deletions

View File

@ -827,6 +827,7 @@ omit =
homeassistant/components/philips_js/light.py
homeassistant/components/philips_js/media_player.py
homeassistant/components/philips_js/remote.py
homeassistant/components/philips_js/switch.py
homeassistant/components/pi_hole/sensor.py
homeassistant/components/pi4ioe5v9xxxx/binary_sensor.py
homeassistant/components/pi4ioe5v9xxxx/switch.py

View File

@ -2,12 +2,13 @@
from __future__ import annotations
import asyncio
from collections.abc import Callable
from collections.abc import Callable, Mapping
from datetime import timedelta
import logging
from typing import Any
from haphilipsjs import ConnectionFailure, PhilipsTV
from haphilipsjs.typing import SystemType
from homeassistant.components.automation import AutomationActionType
from homeassistant.config_entries import ConfigEntry
@ -18,13 +19,25 @@ from homeassistant.const import (
CONF_USERNAME,
Platform,
)
from homeassistant.core import CALLBACK_TYPE, Context, HassJob, HomeAssistant, callback
from homeassistant.core import (
CALLBACK_TYPE,
Context,
Event,
HassJob,
HomeAssistant,
callback,
)
from homeassistant.helpers.debounce import Debouncer
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import CONF_ALLOW_NOTIFY, DOMAIN
from .const import CONF_ALLOW_NOTIFY, CONF_SYSTEM, DOMAIN
PLATFORMS = [Platform.MEDIA_PLAYER, Platform.LIGHT, Platform.REMOTE]
PLATFORMS = [
Platform.MEDIA_PLAYER,
Platform.LIGHT,
Platform.REMOTE,
Platform.SWITCH,
]
LOGGER = logging.getLogger(__name__)
@ -71,7 +84,7 @@ class PluggableAction:
def __init__(self, update: Callable[[], None]) -> None:
"""Initialize."""
self._update = update
self._actions: dict[Any, AutomationActionType] = {}
self._actions: dict[Any, tuple[HassJob, dict[str, Any]]] = {}
def __bool__(self):
"""Return if we have something attached."""
@ -102,7 +115,7 @@ class PluggableAction:
class PhilipsTVDataUpdateCoordinator(DataUpdateCoordinator[None]):
"""Coordinator to update data."""
def __init__(self, hass, api: PhilipsTV, options: dict) -> None:
def __init__(self, hass, api: PhilipsTV, options: Mapping) -> None:
"""Set up the coordinator."""
self.api = api
self.options = options
@ -125,6 +138,20 @@ class PhilipsTVDataUpdateCoordinator(DataUpdateCoordinator[None]):
),
)
@property
def system(self) -> SystemType:
"""Return the system descriptor."""
if self.api.system:
return self.api.system
return self.config_entry.data[CONF_SYSTEM]
@property
def unique_id(self) -> str:
"""Return the system descriptor."""
assert self.config_entry
assert self.config_entry.unique_id
return self.config_entry.unique_id
@property
def _notify_wanted(self):
"""Return if the notify feature should be active.
@ -170,7 +197,7 @@ class PhilipsTVDataUpdateCoordinator(DataUpdateCoordinator[None]):
self._async_notify_stop()
@callback
def _async_stop_refresh(self, event: asyncio.Event) -> None:
def _async_stop_refresh(self, event: Event) -> None:
super()._async_stop_refresh(event)
self._async_notify_stop()

View File

@ -1,8 +1,6 @@
"""Component to integrate ambilight for TVs exposing the Joint Space API."""
from __future__ import annotations
from typing import Any
from haphilipsjs import PhilipsTV
from haphilipsjs.typing import AmbilightCurrentConfiguration
@ -24,7 +22,7 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util.color import color_hsv_to_RGB, color_RGB_to_hsv
from . import PhilipsTVDataUpdateCoordinator
from .const import CONF_SYSTEM, DOMAIN
from .const import DOMAIN
EFFECT_PARTITION = ": "
EFFECT_MODE = "Mode"
@ -40,13 +38,7 @@ async def async_setup_entry(
):
"""Set up the configuration entry."""
coordinator = hass.data[DOMAIN][config_entry.entry_id]
async_add_entities(
[
PhilipsTVLightEntity(
coordinator, config_entry.data[CONF_SYSTEM], config_entry.unique_id
)
]
)
async_add_entities([PhilipsTVLightEntity(coordinator)])
def _get_settings(style: AmbilightCurrentConfiguration):
@ -136,15 +128,11 @@ class PhilipsTVLightEntity(CoordinatorEntity, LightEntity):
def __init__(
self,
coordinator: PhilipsTVDataUpdateCoordinator,
system: dict[str, Any],
unique_id: str,
) -> None:
"""Initialize light."""
self._tv = coordinator.api
self._hs = None
self._brightness = None
self._system = system
self._coordinator = coordinator
self._cache_keys = None
super().__init__(coordinator)
@ -152,17 +140,17 @@ class PhilipsTVLightEntity(CoordinatorEntity, LightEntity):
self._attr_supported_features = (
SUPPORT_EFFECT | SUPPORT_COLOR | SUPPORT_BRIGHTNESS
)
self._attr_name = self._system["name"]
self._attr_unique_id = unique_id
self._attr_name = f"{coordinator.system['name']} Ambilight"
self._attr_unique_id = coordinator.unique_id
self._attr_icon = "mdi:television-ambient-light"
self._attr_device_info = DeviceInfo(
identifiers={
(DOMAIN, self._attr_unique_id),
},
manufacturer="Philips",
model=self._system.get("model"),
name=self._system["name"],
sw_version=self._system.get("softwareversion"),
model=coordinator.system.get("model"),
name=coordinator.system["name"],
sw_version=coordinator.system.get("softwareversion"),
)
self._update_from_coordinator()

View File

@ -1,8 +1,6 @@
"""Media Player component to integrate TVs exposing the Joint Space API."""
from __future__ import annotations
from typing import Any
from haphilipsjs import ConnectionFailure
from homeassistant import config_entries
@ -40,7 +38,7 @@ from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import LOGGER as _LOGGER, PhilipsTVDataUpdateCoordinator
from .const import CONF_SYSTEM, DOMAIN
from .const import DOMAIN
SUPPORT_PHILIPS_JS = (
SUPPORT_TURN_OFF
@ -75,8 +73,6 @@ async def async_setup_entry(
[
PhilipsTVMediaPlayer(
coordinator,
config_entry.data[CONF_SYSTEM],
config_entry.unique_id,
)
]
)
@ -85,13 +81,12 @@ async def async_setup_entry(
class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity):
"""Representation of a Philips TV exposing the JointSpace API."""
_coordinator: PhilipsTVDataUpdateCoordinator
_attr_device_class = MediaPlayerDeviceClass.TV
def __init__(
self,
coordinator: PhilipsTVDataUpdateCoordinator,
system: dict[str, Any],
unique_id: str,
) -> None:
"""Initialize the Philips TV."""
self._tv = coordinator.api
@ -99,8 +94,18 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity):
self._sources = {}
self._channels = {}
self._supports = SUPPORT_PHILIPS_JS
self._system = system
self._unique_id = unique_id
self._system = coordinator.system
self._attr_name = coordinator.system["name"]
self._attr_unique_id = coordinator.unique_id
self._attr_device_info = DeviceInfo(
identifiers={
(DOMAIN, coordinator.unique_id),
},
manufacturer="Philips",
model=coordinator.system.get("model"),
sw_version=coordinator.system.get("softwareversion"),
name=coordinator.system["name"],
)
self._state = STATE_OFF
self._media_content_type: str | None = None
self._media_content_id: str | None = None
@ -115,11 +120,6 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity):
self.async_write_ha_state()
await self.coordinator.async_request_refresh()
@property
def name(self):
"""Return the device name."""
return self._system["name"]
@property
def supported_features(self):
"""Flag media player features that are supported."""
@ -280,24 +280,6 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity):
if app := self._tv.applications.get(self._tv.application_id):
return app.get("label")
@property
def unique_id(self):
"""Return unique identifier if known."""
return self._unique_id
@property
def device_info(self) -> DeviceInfo:
"""Return a device description for device registry."""
return DeviceInfo(
identifiers={
(DOMAIN, self._unique_id),
},
manufacturer="Philips",
model=self._system.get("model"),
sw_version=self._system.get("softwareversion"),
name=self._system["name"],
)
async def async_play_media(self, media_type, media_id, **kwargs):
"""Play a piece of media."""
_LOGGER.debug("Call play media type <%s>, Id <%s>", media_type, media_id)

View File

@ -1,8 +1,6 @@
"""Remote control support for Apple TV."""
import asyncio
from haphilipsjs.typing import SystemType
from homeassistant.components.remote import (
ATTR_DELAY_SECS,
ATTR_NUM_REPEATS,
@ -13,9 +11,10 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import LOGGER, PhilipsTVDataUpdateCoordinator
from .const import CONF_SYSTEM, DOMAIN
from .const import DOMAIN
async def async_setup_entry(
@ -25,36 +24,32 @@ async def async_setup_entry(
) -> None:
"""Set up the configuration entry."""
coordinator = hass.data[DOMAIN][config_entry.entry_id]
async_add_entities(
[
PhilipsTVRemote(
coordinator,
config_entry.data[CONF_SYSTEM],
config_entry.unique_id,
)
]
)
async_add_entities([PhilipsTVRemote(coordinator)])
class PhilipsTVRemote(RemoteEntity):
class PhilipsTVRemote(CoordinatorEntity, RemoteEntity):
"""Device that sends commands."""
_coordinator: PhilipsTVDataUpdateCoordinator
def __init__(
self,
coordinator: PhilipsTVDataUpdateCoordinator,
system: SystemType,
unique_id: str,
) -> None:
"""Initialize the Philips TV."""
super().__init__(coordinator)
self._tv = coordinator.api
self._coordinator = coordinator
self._system = system
self._unique_id = unique_id
@property
def name(self):
"""Return the device name."""
return self._system["name"]
self._attr_name = f"{coordinator.system['name']} Remote"
self._attr_unique_id = coordinator.unique_id
self._attr_device_info = DeviceInfo(
identifiers={
(DOMAIN, coordinator.unique_id),
},
manufacturer="Philips",
model=coordinator.system.get("model"),
name=coordinator.system["name"],
sw_version=coordinator.system.get("softwareversion"),
)
@property
def is_on(self):
@ -63,29 +58,6 @@ class PhilipsTVRemote(RemoteEntity):
self._tv.on and (self._tv.powerstate == "On" or self._tv.powerstate is None)
)
@property
def should_poll(self):
"""No polling needed for Apple TV."""
return False
@property
def unique_id(self):
"""Return unique identifier if known."""
return self._unique_id
@property
def device_info(self) -> DeviceInfo:
"""Return a device description for device registry."""
return DeviceInfo(
identifiers={
(DOMAIN, self._unique_id),
},
manufacturer="Philips",
model=self._system.get("model"),
name=self._system["name"],
sw_version=self._system.get("softwareversion"),
)
async def async_turn_on(self, **kwargs):
"""Turn the device on."""
if self._tv.on and self._tv.powerstate:

View File

@ -0,0 +1,72 @@
"""Philips TV menu switches."""
from __future__ import annotations
from typing import Any
from homeassistant import config_entries
from homeassistant.components.switch import SwitchEntity
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import PhilipsTVDataUpdateCoordinator
from .const import DOMAIN
async def async_setup_entry(
hass: HomeAssistant,
config_entry: config_entries.ConfigEntry,
async_add_entities: AddEntitiesCallback,
):
"""Set up the configuration entry."""
coordinator: PhilipsTVDataUpdateCoordinator = hass.data[DOMAIN][
config_entry.entry_id
]
async_add_entities([PhilipsTVScreenSwitch(coordinator)])
class PhilipsTVScreenSwitch(CoordinatorEntity, SwitchEntity):
"""A Philips TV screen state switch."""
coordinator: PhilipsTVDataUpdateCoordinator
def __init__(
self,
coordinator: PhilipsTVDataUpdateCoordinator,
) -> None:
"""Initialize entity."""
super().__init__(coordinator)
self._attr_name = f"{coordinator.system['name']} Screen State"
self._attr_icon = "mdi:television-shimmer"
self._attr_unique_id = f"{coordinator.unique_id}_screenstate"
self._attr_device_info = DeviceInfo(
identifiers={
(DOMAIN, coordinator.unique_id),
}
)
@property
def available(self) -> bool:
"""Return true if entity is available."""
if not super().available:
return False
if not self.coordinator.api.on:
return False
return self.coordinator.api.powerstate == "On"
@property
def is_on(self) -> bool:
"""Return True if entity is on."""
return self.coordinator.api.screenstate == "On"
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the entity on."""
await self.coordinator.api.setScreenState("On")
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the entity off."""
await self.coordinator.api.setScreenState("Off")

View File

@ -41,7 +41,9 @@ def mock_tv():
@fixture
async def mock_config_entry(hass):
"""Get standard player."""
config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, title=MOCK_NAME)
config_entry = MockConfigEntry(
domain=DOMAIN, data=MOCK_CONFIG, title=MOCK_NAME, unique_id="ABCDEFGHIJKLF"
)
config_entry.add_to_hass(hass)
return config_entry