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/light.py
homeassistant/components/philips_js/media_player.py homeassistant/components/philips_js/media_player.py
homeassistant/components/philips_js/remote.py homeassistant/components/philips_js/remote.py
homeassistant/components/philips_js/switch.py
homeassistant/components/pi_hole/sensor.py homeassistant/components/pi_hole/sensor.py
homeassistant/components/pi4ioe5v9xxxx/binary_sensor.py homeassistant/components/pi4ioe5v9xxxx/binary_sensor.py
homeassistant/components/pi4ioe5v9xxxx/switch.py homeassistant/components/pi4ioe5v9xxxx/switch.py

View File

@ -2,12 +2,13 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
from collections.abc import Callable from collections.abc import Callable, Mapping
from datetime import timedelta from datetime import timedelta
import logging import logging
from typing import Any from typing import Any
from haphilipsjs import ConnectionFailure, PhilipsTV from haphilipsjs import ConnectionFailure, PhilipsTV
from haphilipsjs.typing import SystemType
from homeassistant.components.automation import AutomationActionType from homeassistant.components.automation import AutomationActionType
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@ -18,13 +19,25 @@ from homeassistant.const import (
CONF_USERNAME, CONF_USERNAME,
Platform, 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.debounce import Debouncer
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator 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__) LOGGER = logging.getLogger(__name__)
@ -71,7 +84,7 @@ class PluggableAction:
def __init__(self, update: Callable[[], None]) -> None: def __init__(self, update: Callable[[], None]) -> None:
"""Initialize.""" """Initialize."""
self._update = update self._update = update
self._actions: dict[Any, AutomationActionType] = {} self._actions: dict[Any, tuple[HassJob, dict[str, Any]]] = {}
def __bool__(self): def __bool__(self):
"""Return if we have something attached.""" """Return if we have something attached."""
@ -102,7 +115,7 @@ class PluggableAction:
class PhilipsTVDataUpdateCoordinator(DataUpdateCoordinator[None]): class PhilipsTVDataUpdateCoordinator(DataUpdateCoordinator[None]):
"""Coordinator to update data.""" """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.""" """Set up the coordinator."""
self.api = api self.api = api
self.options = options 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 @property
def _notify_wanted(self): def _notify_wanted(self):
"""Return if the notify feature should be active. """Return if the notify feature should be active.
@ -170,7 +197,7 @@ class PhilipsTVDataUpdateCoordinator(DataUpdateCoordinator[None]):
self._async_notify_stop() self._async_notify_stop()
@callback @callback
def _async_stop_refresh(self, event: asyncio.Event) -> None: def _async_stop_refresh(self, event: Event) -> None:
super()._async_stop_refresh(event) super()._async_stop_refresh(event)
self._async_notify_stop() self._async_notify_stop()

View File

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

View File

@ -1,8 +1,6 @@
"""Media Player component to integrate TVs exposing the Joint Space API.""" """Media Player component to integrate TVs exposing the Joint Space API."""
from __future__ import annotations from __future__ import annotations
from typing import Any
from haphilipsjs import ConnectionFailure from haphilipsjs import ConnectionFailure
from homeassistant import config_entries from homeassistant import config_entries
@ -40,7 +38,7 @@ from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import LOGGER as _LOGGER, PhilipsTVDataUpdateCoordinator from . import LOGGER as _LOGGER, PhilipsTVDataUpdateCoordinator
from .const import CONF_SYSTEM, DOMAIN from .const import DOMAIN
SUPPORT_PHILIPS_JS = ( SUPPORT_PHILIPS_JS = (
SUPPORT_TURN_OFF SUPPORT_TURN_OFF
@ -75,8 +73,6 @@ async def async_setup_entry(
[ [
PhilipsTVMediaPlayer( PhilipsTVMediaPlayer(
coordinator, coordinator,
config_entry.data[CONF_SYSTEM],
config_entry.unique_id,
) )
] ]
) )
@ -85,13 +81,12 @@ async def async_setup_entry(
class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity):
"""Representation of a Philips TV exposing the JointSpace API.""" """Representation of a Philips TV exposing the JointSpace API."""
_coordinator: PhilipsTVDataUpdateCoordinator
_attr_device_class = MediaPlayerDeviceClass.TV _attr_device_class = MediaPlayerDeviceClass.TV
def __init__( def __init__(
self, self,
coordinator: PhilipsTVDataUpdateCoordinator, coordinator: PhilipsTVDataUpdateCoordinator,
system: dict[str, Any],
unique_id: str,
) -> None: ) -> None:
"""Initialize the Philips TV.""" """Initialize the Philips TV."""
self._tv = coordinator.api self._tv = coordinator.api
@ -99,8 +94,18 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity):
self._sources = {} self._sources = {}
self._channels = {} self._channels = {}
self._supports = SUPPORT_PHILIPS_JS self._supports = SUPPORT_PHILIPS_JS
self._system = system self._system = coordinator.system
self._unique_id = unique_id 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._state = STATE_OFF
self._media_content_type: str | None = None self._media_content_type: str | None = None
self._media_content_id: str | None = None self._media_content_id: str | None = None
@ -115,11 +120,6 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity):
self.async_write_ha_state() self.async_write_ha_state()
await self.coordinator.async_request_refresh() await self.coordinator.async_request_refresh()
@property
def name(self):
"""Return the device name."""
return self._system["name"]
@property @property
def supported_features(self): def supported_features(self):
"""Flag media player features that are supported.""" """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): if app := self._tv.applications.get(self._tv.application_id):
return app.get("label") 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): async def async_play_media(self, media_type, media_id, **kwargs):
"""Play a piece of media.""" """Play a piece of media."""
_LOGGER.debug("Call play media type <%s>, Id <%s>", media_type, media_id) _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.""" """Remote control support for Apple TV."""
import asyncio import asyncio
from haphilipsjs.typing import SystemType
from homeassistant.components.remote import ( from homeassistant.components.remote import (
ATTR_DELAY_SECS, ATTR_DELAY_SECS,
ATTR_NUM_REPEATS, ATTR_NUM_REPEATS,
@ -13,9 +11,10 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import LOGGER, PhilipsTVDataUpdateCoordinator from . import LOGGER, PhilipsTVDataUpdateCoordinator
from .const import CONF_SYSTEM, DOMAIN from .const import DOMAIN
async def async_setup_entry( async def async_setup_entry(
@ -25,36 +24,32 @@ async def async_setup_entry(
) -> None: ) -> None:
"""Set up the configuration entry.""" """Set up the configuration entry."""
coordinator = hass.data[DOMAIN][config_entry.entry_id] coordinator = hass.data[DOMAIN][config_entry.entry_id]
async_add_entities( async_add_entities([PhilipsTVRemote(coordinator)])
[
PhilipsTVRemote(
coordinator,
config_entry.data[CONF_SYSTEM],
config_entry.unique_id,
)
]
)
class PhilipsTVRemote(RemoteEntity): class PhilipsTVRemote(CoordinatorEntity, RemoteEntity):
"""Device that sends commands.""" """Device that sends commands."""
_coordinator: PhilipsTVDataUpdateCoordinator
def __init__( def __init__(
self, self,
coordinator: PhilipsTVDataUpdateCoordinator, coordinator: PhilipsTVDataUpdateCoordinator,
system: SystemType,
unique_id: str,
) -> None: ) -> None:
"""Initialize the Philips TV.""" """Initialize the Philips TV."""
super().__init__(coordinator)
self._tv = coordinator.api self._tv = coordinator.api
self._coordinator = coordinator self._attr_name = f"{coordinator.system['name']} Remote"
self._system = system self._attr_unique_id = coordinator.unique_id
self._unique_id = unique_id self._attr_device_info = DeviceInfo(
identifiers={
@property (DOMAIN, coordinator.unique_id),
def name(self): },
"""Return the device name.""" manufacturer="Philips",
return self._system["name"] model=coordinator.system.get("model"),
name=coordinator.system["name"],
sw_version=coordinator.system.get("softwareversion"),
)
@property @property
def is_on(self): 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) 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): async def async_turn_on(self, **kwargs):
"""Turn the device on.""" """Turn the device on."""
if self._tv.on and self._tv.powerstate: 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 @fixture
async def mock_config_entry(hass): async def mock_config_entry(hass):
"""Get standard player.""" """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) config_entry.add_to_hass(hass)
return config_entry return config_entry