Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion homeassistant/components/lyric/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)

PLATFORMS = [Platform.CLIMATE, Platform.SELECT, Platform.SENSOR]
PLATFORMS = [Platform.BINARY_SENSOR, Platform.CLIMATE, Platform.SELECT, Platform.SENSOR]


async def async_setup_entry(hass: HomeAssistant, entry: LyricConfigEntry) -> bool:
Expand Down
88 changes: 88 additions & 0 deletions homeassistant/components/lyric/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""Support for Honeywell Lyric binary sensor platform."""

from collections.abc import Callable
from dataclasses import dataclass
from typing import override

from aiolyric.objects.device import LyricDevice
from aiolyric.objects.location import LyricLocation

from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .coordinator import LyricConfigEntry, LyricDataUpdateCoordinator
from .entity import LyricDeviceEntity


@dataclass(frozen=True, kw_only=True)
class LyricBinarySensorEntityDescription(BinarySensorEntityDescription):
"""Class describing Honeywell Lyric binary sensor entities."""

value_fn: Callable[[LyricDevice], bool]
suitable_fn: Callable[[LyricDevice], bool]


DEVICE_BINARY_SENSORS: list[LyricBinarySensorEntityDescription] = [
LyricBinarySensorEntityDescription(
key="device_pairing_enabled",
translation_key="device_pairing_enabled",
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda device: device.settings.device_pairing_enabled,
suitable_fn=lambda device: True,
),
]


async def async_setup_entry(
hass: HomeAssistant,
entry: LyricConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
Comment on lines +41 to +45
"""Set up the Honeywell Lyric binary sensor platform based on a config entry."""
coordinator = entry.runtime_data

async_add_entities(
LyricBinarySensor(
coordinator,
device_binary_sensor,
location,
device,
)
for location in coordinator.data.locations
for device in location.devices
for device_binary_sensor in DEVICE_BINARY_SENSORS
if device_binary_sensor.suitable_fn(device)
)


class LyricBinarySensor(LyricDeviceEntity, BinarySensorEntity):
"""Define a Honeywell Lyric binary sensor."""

entity_description: LyricBinarySensorEntityDescription

def __init__(
self,
coordinator: LyricDataUpdateCoordinator,
description: LyricBinarySensorEntityDescription,
location: LyricLocation,
device: LyricDevice,
) -> None:
"""Initialize."""
super().__init__(
coordinator,
location,
device,
f"{device.mac_id}_{description.key}",
)
self.entity_description = description

@property
@override
def is_on(self) -> bool:
"""Return true if the condition is met."""
return self.entity_description.value_fn(self.device)
5 changes: 5 additions & 0 deletions homeassistant/components/lyric/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@
}
},
"entity": {
"binary_sensor": {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Link to documentation pull request: home-assistant/home-assistant.io#46986

"device_pairing_enabled": {
"name": "Device pairing enabled"
}
},
"select": {
"room_priority": {
"name": "Room priority",
Expand Down
12 changes: 12 additions & 0 deletions tests/components/lyric/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
"""Tests for the Honeywell Lyric integration."""

from homeassistant.core import HomeAssistant

from tests.common import MockConfigEntry


async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
"""Set up the Lyric integration for tests."""
config_entry.add_to_hass(hass)

await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
71 changes: 71 additions & 0 deletions tests/components/lyric/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""Fixtures for the Honeywell Lyric integration tests."""

from collections.abc import Generator
from time import time
from unittest.mock import MagicMock, patch

from aiolyric.objects.location import LyricLocation
import pytest

from homeassistant.components.application_credentials import (
DOMAIN as APPLICATION_CREDENTIALS_DOMAIN,
ClientCredential,
async_import_client_credential,
)
from homeassistant.components.lyric.const import DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component

from tests.common import MockConfigEntry, load_json_array_fixture

CLIENT_ID = "1234"
CLIENT_SECRET = "5678"

# Matches the values baked into fixtures/locations.json.
LOCATION_ID = 35202000168931
DEVICE_ID = "LCC-7f86b153-8480-f111-b78f-6045bdb25006"
MAC_ID = "5CFCE1B67035"


@pytest.fixture
async def setup_credentials(hass: HomeAssistant) -> None:
"""Register lyric application credentials."""
assert await async_setup_component(hass, APPLICATION_CREDENTIALS_DOMAIN, {})

await async_import_client_credential(
hass, DOMAIN, ClientCredential(CLIENT_ID, CLIENT_SECRET)
)


@pytest.fixture
def mock_config_entry() -> MockConfigEntry:
"""Return an already-authenticated Lyric config entry."""
return MockConfigEntry(
domain=DOMAIN,
data={
"auth_implementation": DOMAIN,
"token": {
"access_token": "mock-access-token",
"refresh_token": "mock-refresh-token",
"expires_at": time() + 3600,
"token_type": "Bearer",
},
},
)


@pytest.fixture
def mock_lyric_api() -> Generator[MagicMock]:
"""Mock the aiolyric client, backed by a real Location parsed from a live-shaped fixture."""
with patch("homeassistant.components.lyric.Lyric", autospec=True) as mock_lyric_cls:
lyric = mock_lyric_cls.return_value

locations_json = load_json_array_fixture("locations.json", DOMAIN)
lyric.locations = [
LyricLocation(MagicMock(), location) for location in locations_json
]
lyric.locations_dict = {
location.location_id: location for location in lyric.locations
}

yield lyric
22 changes: 22 additions & 0 deletions tests/components/lyric/fixtures/locations.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[
{
"locationID": 35202000168931,
"name": "Ocala P01",
"devices": [
{
"vacationHold": { "Enabled": true },
"scheduleStatus": "Resume",
"settings": { "devicePairingEnabled": true },
"deviceClass": "Thermostat",
"deviceType": "Thermostat",
"deviceID": "LCC-7f86b153-8480-f111-b78f-6045bdb25006",
"name": "Ocala",
"macID": "5CFCE1B67035",
"units": "Fahrenheit",
"indoorTemperature": 79,
"deviceModel": "T9-T10"
}
],
"users": []
}
]
51 changes: 51 additions & 0 deletions tests/components/lyric/snapshots/test_binary_sensor.ambr
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# serializer version: 1
# name: test_binary_sensor[binary_sensor.ocala_thermostat_device_pairing_enabled-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.ocala_thermostat_device_pairing_enabled',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Device pairing enabled',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Device pairing enabled',
'platform': 'lyric',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'device_pairing_enabled',
'unique_id': '5CFCE1B67035_device_pairing_enabled',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensor[binary_sensor.ocala_thermostat_device_pairing_enabled-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Ocala Thermostat Device pairing enabled',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.ocala_thermostat_device_pairing_enabled',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
28 changes: 28 additions & 0 deletions tests/components/lyric/test_binary_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""Tests for the Honeywell Lyric binary sensor platform."""

from unittest.mock import patch

import pytest
from syrupy.assertion import SnapshotAssertion

from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er

from . import setup_integration

from tests.common import MockConfigEntry, snapshot_platform


@pytest.mark.usefixtures("setup_credentials", "mock_lyric_api")
async def test_binary_sensor(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
mock_config_entry: MockConfigEntry,
snapshot: SnapshotAssertion,
) -> None:
"""Test the Lyric binary sensor platform via a real config entry setup."""
with patch("homeassistant.components.lyric.PLATFORMS", [Platform.BINARY_SENSOR]):
await setup_integration(hass, mock_config_entry)

await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
Loading