Skip to content

Commit

Permalink
Add recording status for Philips TV (#94691)
Browse files Browse the repository at this point in the history
  • Loading branch information
Floyer007 committed Nov 22, 2023
1 parent 1f3f073 commit 01c49ba
Show file tree
Hide file tree
Showing 5 changed files with 300 additions and 0 deletions.
1 change: 1 addition & 0 deletions homeassistant/components/philips_js/__init__.py
Expand Up @@ -36,6 +36,7 @@
Platform.LIGHT,
Platform.REMOTE,
Platform.SWITCH,
Platform.BINARY_SENSOR,
]

LOGGER = logging.getLogger(__name__)
Expand Down
105 changes: 105 additions & 0 deletions homeassistant/components/philips_js/binary_sensor.py
@@ -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()
8 changes: 8 additions & 0 deletions homeassistant/components/philips_js/strings.json
Expand Up @@ -44,6 +44,14 @@
}
},
"entity": {
"binary_sensor": {
"recording_new": {
"name": "New recording available"
},
"recording_ongoing": {
"name": "Recording ongoing"
}
},
"light": {
"ambilight": {
"name": "Ambilight"
Expand Down
126 changes: 126 additions & 0 deletions tests/components/philips_js/__init__.py
Expand Up @@ -73,3 +73,129 @@
}

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",
},
],
}
60 changes: 60 additions & 0 deletions tests/components/philips_js/test_binary_sensor.py
@@ -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

0 comments on commit 01c49ba

Please sign in to comment.