Add recording status for Philips TV (#94691)
parent
1f3f073df9
commit
01c49ba0e4
|
@ -36,6 +36,7 @@ PLATFORMS = [
|
|||
Platform.LIGHT,
|
||||
Platform.REMOTE,
|
||||
Platform.SWITCH,
|
||||
Platform.BINARY_SENSOR,
|
||||
]
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
"""Philips TV binary sensors."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from haphilipsjs import PhilipsTV
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorEntity,
|
||||
BinarySensorEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import PhilipsTVDataUpdateCoordinator
|
||||
from .const import DOMAIN
|
||||
from .entity import PhilipsJsEntity
|
||||
|
||||
|
||||
@dataclass
|
||||
class PhilipsTVBinarySensorEntityDescription(BinarySensorEntityDescription):
|
||||
"""A entity description for Philips TV binary sensor."""
|
||||
|
||||
def __init__(self, recording_value, *args, **kwargs) -> None:
|
||||
"""Set up a binary sensor entity description and add additional attributes."""
|
||||
super().__init__(*args, **kwargs)
|
||||
self.recording_value: str = recording_value
|
||||
|
||||
|
||||
DESCRIPTIONS = (
|
||||
PhilipsTVBinarySensorEntityDescription(
|
||||
key="recording_ongoing",
|
||||
translation_key="recording_ongoing",
|
||||
icon="mdi:record-rec",
|
||||
recording_value="RECORDING_ONGOING",
|
||||
),
|
||||
PhilipsTVBinarySensorEntityDescription(
|
||||
key="recording_new",
|
||||
translation_key="recording_new",
|
||||
icon="mdi:new-box",
|
||||
recording_value="RECORDING_NEW",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the configuration entry."""
|
||||
coordinator: PhilipsTVDataUpdateCoordinator = hass.data[DOMAIN][
|
||||
config_entry.entry_id
|
||||
]
|
||||
|
||||
if (
|
||||
coordinator.api.json_feature_supported("recordings", "List")
|
||||
and coordinator.api.api_version == 6
|
||||
):
|
||||
async_add_entities(
|
||||
PhilipsTVBinarySensorEntityRecordingType(coordinator, description)
|
||||
for description in DESCRIPTIONS
|
||||
)
|
||||
|
||||
|
||||
def _check_for_recording_entry(api: PhilipsTV, entry: str, value: str) -> bool:
|
||||
"""Return True if at least one specified value is available within entry of list."""
|
||||
for rec in api.recordings_list["recordings"]:
|
||||
if rec.get(entry) == value:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class PhilipsTVBinarySensorEntityRecordingType(PhilipsJsEntity, BinarySensorEntity):
|
||||
"""A Philips TV binary sensor class, which allows multiple entities given by a BinarySensorEntityDescription."""
|
||||
|
||||
entity_description: PhilipsTVBinarySensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: PhilipsTVDataUpdateCoordinator,
|
||||
description: PhilipsTVBinarySensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize entity class."""
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{coordinator.unique_id}_{description.key}"
|
||||
self._attr_device_info = coordinator.device_info
|
||||
self._attr_is_on = _check_for_recording_entry(
|
||||
coordinator.api,
|
||||
"RecordingType",
|
||||
description.recording_value,
|
||||
)
|
||||
|
||||
super().__init__(coordinator)
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle updated data from the coordinator and set is_on true if one specified value is available within given entry of list."""
|
||||
self._attr_is_on = _check_for_recording_entry(
|
||||
self.coordinator.api,
|
||||
"RecordingType",
|
||||
self.entity_description.recording_value,
|
||||
)
|
||||
super()._handle_coordinator_update()
|
|
@ -44,6 +44,14 @@
|
|||
}
|
||||
},
|
||||
"entity": {
|
||||
"binary_sensor": {
|
||||
"recording_new": {
|
||||
"name": "New recording available"
|
||||
},
|
||||
"recording_ongoing": {
|
||||
"name": "Recording ongoing"
|
||||
}
|
||||
},
|
||||
"light": {
|
||||
"ambilight": {
|
||||
"name": "Ambilight"
|
||||
|
|
|
@ -73,3 +73,129 @@ MOCK_CONFIG_PAIRED = {
|
|||
}
|
||||
|
||||
MOCK_ENTITY_ID = "media_player.philips_tv"
|
||||
|
||||
MOCK_RECORDINGS_LIST = {
|
||||
"version": "253.91",
|
||||
"recordings": [
|
||||
{
|
||||
"RecordingId": 36,
|
||||
"RecordingType": "RECORDING_ONGOING",
|
||||
"IsIpEpgRec": False,
|
||||
"ccid": 2091,
|
||||
"StartTime": 1676833531,
|
||||
"Duration": 569,
|
||||
"MarginStart": 0,
|
||||
"MarginEnd": 0,
|
||||
"EventId": 47369,
|
||||
"EITVersion": 0,
|
||||
"RetentionInfo": 0,
|
||||
"EventInfo": "This is a event info which is not rejected by codespell.",
|
||||
"EventExtendedInfo": "",
|
||||
"EventGenre": "8",
|
||||
"RecName": "Terra X",
|
||||
"SeriesID": "None",
|
||||
"SeasonNo": 0,
|
||||
"EpisodeNo": 0,
|
||||
"EpisodeCount": 72300,
|
||||
"ProgramNumber": 11110,
|
||||
"EventRating": 0,
|
||||
"hasDot": True,
|
||||
"isFTARecording": False,
|
||||
"LastPinChangedTime": 0,
|
||||
"Version": 344,
|
||||
"HasCicamPin": False,
|
||||
"HasLicenseFile": False,
|
||||
"Size": 0,
|
||||
"ResumeInfo": 0,
|
||||
"IsPartial": False,
|
||||
"AutoMarginStart": 0,
|
||||
"AutoMarginEnd": 0,
|
||||
"ServerRecordingId": -1,
|
||||
"ActualStartTime": 1676833531,
|
||||
"ProgramDuration": 0,
|
||||
"IsRadio": False,
|
||||
"EITSource": "EIT_SOURCE_PF",
|
||||
"RecError": "REC_ERROR_NONE",
|
||||
},
|
||||
{
|
||||
"RecordingId": 35,
|
||||
"RecordingType": "RECORDING_NEW",
|
||||
"IsIpEpgRec": False,
|
||||
"ccid": 2091,
|
||||
"StartTime": 1676832212,
|
||||
"Duration": 22,
|
||||
"MarginStart": 0,
|
||||
"MarginEnd": 0,
|
||||
"EventId": 47369,
|
||||
"EITVersion": 0,
|
||||
"RetentionInfo": -1,
|
||||
"EventInfo": "This is another event info which is not rejected by codespell.",
|
||||
"EventExtendedInfo": "",
|
||||
"EventGenre": "8",
|
||||
"RecName": "Terra X",
|
||||
"SeriesID": "None",
|
||||
"SeasonNo": 0,
|
||||
"EpisodeNo": 0,
|
||||
"EpisodeCount": 70980,
|
||||
"ProgramNumber": 11110,
|
||||
"EventRating": 0,
|
||||
"hasDot": True,
|
||||
"isFTARecording": False,
|
||||
"LastPinChangedTime": 0,
|
||||
"Version": 339,
|
||||
"HasCicamPin": False,
|
||||
"HasLicenseFile": False,
|
||||
"Size": 0,
|
||||
"ResumeInfo": 0,
|
||||
"IsPartial": False,
|
||||
"AutoMarginStart": 0,
|
||||
"AutoMarginEnd": 0,
|
||||
"ServerRecordingId": -1,
|
||||
"ActualStartTime": 1676832212,
|
||||
"ProgramDuration": 0,
|
||||
"IsRadio": False,
|
||||
"EITSource": "EIT_SOURCE_PF",
|
||||
"RecError": "REC_ERROR_NONE",
|
||||
},
|
||||
{
|
||||
"RecordingId": 34,
|
||||
"RecordingType": "RECORDING_PARTIALLY_VIEWED",
|
||||
"IsIpEpgRec": False,
|
||||
"ccid": 2091,
|
||||
"StartTime": 1676677580,
|
||||
"Duration": 484,
|
||||
"MarginStart": 0,
|
||||
"MarginEnd": 0,
|
||||
"EventId": -1,
|
||||
"EITVersion": 0,
|
||||
"RetentionInfo": -1,
|
||||
"EventInfo": "\n\nAlpine Ski-WM: Parallel-Event, Übertragung aus Méribel/Frankreich\n\n14:10: Biathlon-WM (AD): 20 km Einzel Männer, Übertragung aus Oberhof\nHD-Produktion",
|
||||
"EventExtendedInfo": "",
|
||||
"EventGenre": "4",
|
||||
"RecName": "ZDF HD 2023-02-18 00:46",
|
||||
"SeriesID": "None",
|
||||
"SeasonNo": 0,
|
||||
"EpisodeNo": 0,
|
||||
"EpisodeCount": 2760,
|
||||
"ProgramNumber": 11110,
|
||||
"EventRating": 0,
|
||||
"hasDot": True,
|
||||
"isFTARecording": False,
|
||||
"LastPinChangedTime": 0,
|
||||
"Version": 328,
|
||||
"HasCicamPin": False,
|
||||
"HasLicenseFile": False,
|
||||
"Size": 0,
|
||||
"ResumeInfo": 56,
|
||||
"IsPartial": False,
|
||||
"AutoMarginStart": 0,
|
||||
"AutoMarginEnd": 0,
|
||||
"ServerRecordingId": -1,
|
||||
"ActualStartTime": 1676677581,
|
||||
"ProgramDuration": 0,
|
||||
"IsRadio": False,
|
||||
"EITSource": "EIT_SOURCE_PF",
|
||||
"RecError": "REC_ERROR_NONE",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
"""The tests for philips_js binary_sensor."""
|
||||
import pytest
|
||||
|
||||
from homeassistant.const import STATE_ON
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import MOCK_NAME, MOCK_RECORDINGS_LIST
|
||||
|
||||
ID_RECORDING_AVAILABLE = (
|
||||
"binary_sensor." + MOCK_NAME.replace(" ", "_").lower() + "_new_recording_available"
|
||||
)
|
||||
ID_RECORDING_ONGOING = (
|
||||
"binary_sensor." + MOCK_NAME.replace(" ", "_").lower() + "_recording_ongoing"
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def mock_tv_api_invalid(mock_tv):
|
||||
"""Set up a invalid mock_tv with should not create sensors."""
|
||||
mock_tv.secured_transport = True
|
||||
mock_tv.api_version = 1
|
||||
mock_tv.recordings_list = None
|
||||
return mock_tv
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def mock_tv_api_valid(mock_tv):
|
||||
"""Set up a valid mock_tv with should create sensors."""
|
||||
mock_tv.secured_transport = True
|
||||
mock_tv.api_version = 6
|
||||
mock_tv.recordings_list = MOCK_RECORDINGS_LIST
|
||||
return mock_tv
|
||||
|
||||
|
||||
async def test_recordings_list_invalid(
|
||||
mock_tv_api_invalid, mock_config_entry, hass: HomeAssistant
|
||||
) -> None:
|
||||
"""Test if sensors are not created if mock_tv is invalid."""
|
||||
|
||||
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
|
||||
state = hass.states.get(ID_RECORDING_AVAILABLE)
|
||||
assert state is None
|
||||
|
||||
state = hass.states.get(ID_RECORDING_ONGOING)
|
||||
assert state is None
|
||||
|
||||
|
||||
async def test_recordings_list_valid(
|
||||
mock_tv_api_valid, mock_config_entry, hass: HomeAssistant
|
||||
) -> None:
|
||||
"""Test if sensors are created correctly."""
|
||||
|
||||
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
|
||||
state = hass.states.get(ID_RECORDING_AVAILABLE)
|
||||
assert state.state is STATE_ON
|
||||
|
||||
state = hass.states.get(ID_RECORDING_ONGOING)
|
||||
assert state.state is STATE_ON
|
Loading…
Reference in New Issue