Skip to content

Add Device Pairing Enabled binary sensor to Lyric integration - #177062

Open
clutch2sft wants to merge 13 commits into
home-assistant:devfrom
clutch2sft:binary-sensor-room-motion
Open

Add Device Pairing Enabled binary sensor to Lyric integration#177062
clutch2sft wants to merge 13 commits into
home-assistant:devfrom
clutch2sft:binary-sensor-room-motion

Conversation

@clutch2sft

@clutch2sft clutch2sft commented Jul 22, 2026

Copy link
Copy Markdown

Proposed change

Adds a new binary_sensor platform to the lyric integration, exposing a
field that was already being parsed by the coordinator but never surfaced
as an entity:

  • Device Pairing Enabled (device-level, diagnostic) — device.settings.device_pairing_enabled

Device Pairing Enabled introduces a DEVICE_BINARY_SENSORS list mirroring
the existing DEVICE_SENSORS pattern.

Room Motion and Vacation Hold were originally part of this PR too, but both
depend on upstream aiolyric fixes that haven't released yet (field-name
mismatches tracked in timmo001/aiolyric#165 and the vacation-hold-key fix),
so they've been split out to a held-back branch to avoid shipping entities
that can't work correctly yet. They'll come back as their own PR once the
dependency chain lands.

  • Room Motion (per paired IndoorAirSensor room accessory) — accessory.detect_motion
  • Vacation Hold (device-level) — device.vacation_hold.enabled
  • Device Pairing Enabled (device-level, diagnostic) — device.settings.device_pairing_enabled

Room Motion follows the same per-accessory pattern already used for Room
Temperature/Humidity in sensor.py. Vacation Hold and Device Pairing Enabled
introduce a DEVICE_BINARY_SENSORS list mirroring the existing DEVICE_SENSORS
pattern.

Dependency note: Room Motion only populates once room/accessory data is being
fetched at all, which depends on #177022 (fixes the coordinator silently skipping
this fetch for some thermostats). Vacation Hold and Device Pairing Enabled are
device-level fields from the base /locations response, which is always fetched —
they work independently of #177022.

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New integration (thank you!)
  • New feature (which adds functionality to an existing integration)
  • Deprecation (breaking change to happen in the future)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

Checklist

  • I understand the code I am submitting and can explain how it works.
  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the development checklist
  • I have followed the perfect PR recommendations
  • The code has been formatted using Ruff (ruff format homeassistant tests)
  • Tests have been added to verify that the new code works.
  • Any generated code has been carefully reviewed for correctness and compliance with project standards.

If user exposed functionality or configuration variables are added/changed:

If the code communicates with devices, web services, or third-party tools:

  • The manifest file has all fields filled out correctly.
    Updated and included derived files by running: python3 -m script.hassfest.
  • New or updated dependencies have been added to requirements_all.txt.
    Updated by running python3 -m script.gen_requirements_all.
  • For the updated dependencies a diff between library versions and ideally a link to the changelog/release notes is added to the PR description.

To help with the load of incoming pull requests:

Resideo's priority endpoint reports per-accessory motion detection
(detectMotion) for paired IndoorAirSensor room accessories, which
the integration parsed but never surfaced as an entity. Adds a new
binary_sensor platform following the same per-accessory pattern
already used for Room Temperature/Humidity in sensor.py.
Resideo's device response includes vacationHold.enabled, which was
parsed but never surfaced. Adds a device-level binary sensor
alongside the existing per-accessory Room Motion sensor, introducing
a DEVICE_BINARY_SENSORS list mirroring the DEVICE_SENSORS pattern
already used in sensor.py.
Resideo's device response includes settings.devicePairingEnabled,
which was parsed but never surfaced. Adds it as a diagnostic binary
sensor alongside Vacation Hold, using the same DEVICE_BINARY_SENSORS
list.
@home-assistant

Copy link
Copy Markdown
Contributor

Hey there @timmo001, mind taking a look at this pull request as it has been labeled with an integration (lyric) you are listed as a code owner for? Thanks!

Code owner commands

Code owners of lyric can trigger bot actions by commenting:

  • @home-assistant close Closes the pull request.
  • @home-assistant mark-draft Mark the pull request as draft.
  • @home-assistant ready-for-review Remove the draft status from the pull request.
  • @home-assistant rename Awesome new title Renames the pull request.
  • @home-assistant reopen Reopen the pull request.
  • @home-assistant unassign lyric Removes the current integration label and assignees on the pull request, add the integration domain after the command.
  • @home-assistant update-branch Update the pull request branch with the base branch.
  • @home-assistant add-label needs-more-information Add a label (needs-more-information, problem in dependency, problem in custom component, problem in config, problem in device, feature-request) to the pull request.
  • @home-assistant remove-label needs-more-information Remove a label (needs-more-information, problem in dependency, problem in custom component, problem in config, problem in device, feature-request) on the pull request.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds Lyric binary sensors for room motion, vacation hold, and device pairing status.

Changes:

  • Registers the binary sensor platform.
  • Adds device and accessory binary sensor entities.
  • Adds translated entity names.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
homeassistant/components/lyric/__init__.py Registers the platform.
homeassistant/components/lyric/binary_sensor.py Implements three binary sensors.
homeassistant/components/lyric/strings.json Adds entity names.

LyricBinarySensorEntityDescription(
key="vacation_hold",
translation_key="vacation_hold",
value_fn=lambda device: device.vacation_hold.enabled,

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Confirmed — good catch. vacationHold.Enabled (capital E) is what Resideo actually returns; I have a live captured payload showing it. Fixed in timmo001/aiolyric here: clutch2sft/aiolyric#170 (branch fix-vacation-hold-key).

translation_key="room_motion",
device_class=BinarySensorDeviceClass.MOTION,
value_fn=lambda _, accessory: accessory.detect_motion,
suitable_fn=lambda _, accessory: accessory.type == "IndoorAirSensor",

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Correct, and already tracked — this needs timmo001/aiolyric#165 (the currentPriority/priority and type/sensorType field fix) merged and released before Room Motion populates for real accounts. Filing it as a dependency rather than blocking on it here, since #165 is a separate project on its own release cycle, and this PR is still correct/complete for what core can control. Once #165 releases, the follow-up is a one-line manifest.json version bump — tracked separately.

Comment on lines +67 to +71
async def async_setup_entry(
hass: HomeAssistant,
entry: LyricConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
}
},
"entity": {
"binary_sensor": {

Copy link
Copy Markdown
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

Covers entity creation (async_setup_entry), the accessory-type filter
that gates Room Motion to IndoorAirSensor accessories only, the
Vacation Hold/Device Pairing Enabled value mappings, and diagnostic
entity_category metadata.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 23, 2026 10:08
Samielakkad

This comment was marked as resolved.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

homeassistant/components/lyric/binary_sensor.py:44

  • Fix the upstream vacation-hold key mapping and bump aiolyric before exposing this entity. The pinned 2.1.1 reads vacationHold["enabled"], but the live /locations payload in #177010 uses {"Enabled": ...} and LyricBaseObject performs no normalization, so this value always falls back to False and the entity cannot report an active vacation hold.
        value_fn=lambda device: device.vacation_hold.enabled,

homeassistant/components/lyric/binary_sensor.py:62

  • Depend on an aiolyric release that maps sensorType before filtering room accessories. In the pinned 2.1.1, LyricAccessory.type reads the nonexistent live-response key type; live priority responses use sensorType (timmo001/aiolyric#165), so .type is empty and every room-motion entity is filtered out.
        suitable_fn=lambda _, accessory: accessory.type == "IndoorAirSensor",

side_effect=lambda entities: added.append(list(entities))
)

await async_setup_entry(MagicMock(), entry, async_add_entities)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fair — the previous version built entities from MagicMocks with already-parsed properties set directly, so it would pass regardless of real payload/key mismatches. Reworked to construct real LyricDevice/LyricRoom/LyricAccessory objects from realistic payloads and assert through entity.is_on, exercising the actual aiolyric parsing boundary. For vacation_hold and room_motion specifically, the currently-pinned aiolyric==2.1.1 still has the exact key mismatches this PR's description already discloses (vacationHold.Enabled vs .enabled, sensorType vs type) — so those two are now xfail(strict=True) against the real live payload shape: they fail today for the same reason the real entities don't populate correctly yet, and strict=True means they'll turn into hard failures (forcing the marker's removal) the moment the companion aiolyric fixes are released and the manifest pin is bumped, rather than silently staying green. device_pairing_enabled has no known mismatch, so it gets a real end-to-end passing test today.

Addresses two review comments on the previous version of this test:

- Copilot: tests built entities from MagicMocks with pre-set already-
  parsed properties, so they'd pass even with the exact live payload/
  key mismatches this session found (e.g. vacationHold.Enabled vs
  .enabled). Now builds real LyricDevice/LyricRoom/LyricAccessory
  objects from realistic payloads and asserts through entity.is_on,
  exercising the actual parsing boundary.

- Maintainer (@Samielakkad): setup test only checked
  entity_description.key, not the generated unique_id - the part most
  likely to collide/drift as more room/accessory sensors are added.
  Added a dedicated test asserting exact unique_id values across
  multiple rooms/accessories.

Since aiolyric 2.1.1 (the currently pinned release) still has the
vacationHold.Enabled and accessory sensorType mismatches, two new
tests use xfail(strict=True) against the real live payload shape:
they currently fail for the same reason the real entities don't work
yet, and will start passing (forcing marker removal, since strict=True
turns an unexpected pass into a failure) once the companion aiolyric
fixes are released and the manifest pin is bumped. This documents the
known gap explicitly instead of hiding it behind mocks.

device_pairing_enabled has no known aiolyric mismatch, so it gets a
real end-to-end pass today, proving the pattern isn't blanket broken.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 23, 2026 10:23
@clutch2sft

Copy link
Copy Markdown
Author

The setup test should assert the generated entity IDs/unique IDs, not only the description keys.

This PR creates both device-level entities and accessory-level entities, and the accessory unique id includes the parent device MAC, room id, accessory id, and description key. That is the part most likely to collide or drift when adding more room/accessory binary sensors later.

A test assertion around the created entities? unique_id values would protect the platform contract better than just checking that two device descriptions and one accessory description were created.

Good catch, added. There's now a dedicated test (test_async_setup_entry_generates_correct_unique_ids) using real LyricDevice/LyricRoom/LyricAccessory objects across two rooms (one with a non-qualifying Thermostat-type accessory mixed in) that asserts the exact unique_id set for both device-level and accessory-level entities — confirming the room-id/accessory-id components of the accessory unique_id don't collide.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (3)

tests/components/lyric/test_binary_sensor.py:74

  • Set up the config entry through hass.config_entries.async_setup(...) instead of invoking the platform directly. The tests pylint job rejects this call with home-assistant-tests-direct-platform-async-setup-entry, so the current branch cannot pass CI.
    await async_setup_entry(MagicMock(), entry, async_add_entities)

tests/components/lyric/test_binary_sensor.py:134

  • Shorten this docstring and avoid describing this mocked platform-level test as end-to-end. The parsing distinction is already evident from the payload and assertions.
    Unlike vacation_hold and room_motion, this field isn't affected by any
    known aiolyric field-name mismatch, so this exercises the full
    integration boundary (real LyricDevice -> entity.is_on) end-to-end.

tests/components/lyric/test_binary_sensor.py:206

  • Remove the duplicated failure narrative from this docstring. The xfail reason immediately above already documents the upstream schema mismatch.
    Built from the actual /priority response shape captured from a live
    T9-T10 account - currently fails because accessory.type never matches
    "IndoorAirSensor" under the pending key-name fix, so no entity is
    created at all.

Comment on lines +79 to +86
"""Entity unique_ids are correctly formed and don't collide across rooms.

Uses real LyricDevice/LyricRoom/LyricAccessory objects (constructed with
field names aiolyric 2.1.1 already parses correctly) to verify the ID
formula itself: device-level IDs key off the device MAC, accessory-level
IDs additionally key off room id and accessory id - the parts most
likely to collide when more room/accessory sensors are added later.
"""
Comment on lines +167 to +172
"""Vacation Hold should resolve True given a live-shaped payload.

Built from the actual API response shape captured from a live account,
not a synthetic/pre-parsed mock - currently fails because of the
pending key-name fix, by design.
"""
CI enforces this as a hard rule (pylint plugin check
home-assistant-tests-direct-platform-async-setup-entry, W7420): a
platform's async_setup_entry must not be called directly in tests;
use hass.config_entries.async_setup(entry.entry_id) instead. The
previous version called binary_sensor.async_setup_entry directly with
mocked hass/entry/async_add_entities, which this now-enforced rule
flags.

Adds tests/components/lyric/conftest.py with fixtures for a fully
authenticated config entry (registered via application_credentials,
matching test_config_flow.py's proven pattern) and HTTP-level mocks
for the /locations and /priority endpoints using the actual live
payload shape captured from a real T9-T10 account this session -
including the real key names aiolyric 2.1.1 gets wrong (vacationHold
.Enabled, accessories[].sensorType, currentPriority vs priority).

Entities are looked up via entity_registry.async_get_entity_id() with
their known unique_id format rather than guessing generated entity_id
slugs, which is deterministic regardless of naming/translation
quirks.

device_pairing_enabled has no known field-name mismatch, so its test
passes for real against the currently-pinned aiolyric release.
vacation_hold and room_motion are xfail(strict=True) against the real
live payload shape for the same reasons already disclosed in this
PR's description; strict=True means they'll turn into hard failures
(forcing marker removal) once the companion aiolyric fixes are
released and the manifest pin is bumped, rather than silently staying
green.

Note: I could not run this against a real pytest+hass fixture harness
locally (blocked by a Windows/fcntl limitation unrelated to this
integration) - built carefully against the proven patterns in this
repo's own test_config_flow.py and a structurally similar OAuth2 +
DataUpdateCoordinator integration (iotty), but this needs a real
pytest run to confirm before considering it fully verified.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 23, 2026 11:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comment thread tests/components/lyric/conftest.py Outdated
Comment on lines +29 to +34
# Deliberately "LCC-"-prefixed: this branch is intentionally cut from a
# clean base without the coordinator fix from home-assistant/core#177022
# (which removes a device-ID-prefix heuristic gating the /priority fetch).
# Using a non-"LCC-" ID here would make room-level entities fail to be
# created for that unrelated, separately-tracked reason, muddying what
# these tests are actually checking.
clutch2sft and others added 2 commits July 24, 2026 06:45
Only the LCC- prefix requirement matters to the fixture; the branch/PR
context it referenced would rot as the codebase evolves.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
joostlek flagged the same conftest.py pattern on PR home-assistant#177067 (this
branch's conftest.py was cut from the same base): a bespoke "cred"
credential name instead of the DOMAIN default, setting up the lyric
domain directly instead of application_credentials, and mocking HTTP
responses instead of the aiolyric client itself. Bringing this branch
in line proactively rather than waiting for the same comments here.

- Register the test credential under DOMAIN, matching every other
  OAuth2 integration's conftest.
- Set up application_credentials directly instead of relying on it
  being pulled in as a side effect of lyric's manifest dependency.
- Patch Lyric.get_locations/get_thermostat_rooms to build real
  LyricLocation/LyricPriority objects instead of mocking HTTP at the
  aiohttp transport level. Field-name parsing still runs for real
  (the properties read straight off the same attributes dicts).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 24, 2026 12:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

tests/components/lyric/test_binary_sensor.py:88

  • Remove the duplicated upstream-failure narrative from this docstring. The xfail reason immediately above already documents why the test currently fails.
    """Vacation Hold should read "on" via a real config entry setup.

    The fixture location has vacationHold.Enabled = True (the real live
    shape), so this documents the currently-pinned aiolyric bug rather
    than hiding it - it fails today for the same reason the real entity

tests/components/lyric/test_binary_sensor.py:124

  • Remove the duplicated upstream-failure narrative from this docstring. The xfail reason immediately above already documents why the test currently fails.
    """Room Motion should be created via a real config entry setup.

    The fixture priority data uses the real live shape ("priority",
    "sensorType"), so under the currently-pinned aiolyric this entity
    doesn't get created at all - documents the gap instead of hiding it.

Comment on lines +49 to +55
"""Device Pairing Enabled is created via a real config entry setup.

Exercises the full boundary: real aiolyric parsing of the actual live
payload shape -> real coordinator -> real entity setup -> registered
state. devicePairingEnabled has no known aiolyric field-name mismatch,
so this passes against the currently-pinned release.
"""
Same issue as the earlier DEVICE_ID comment fix: these docstrings
restated the fixture/xfail-reason narrative rather than just the
behavior under test, which would go stale as aiolyric changes. The
xfail reason above each xfail test already documents the field-name
mismatch, so the docstring doesn't need to repeat it.
Copilot AI review requested due to automatic review settings July 25, 2026 17:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Comment thread tests/components/lyric/conftest.py Outdated
Comment on lines +38 to +61
LOCATIONS_RESPONSE = [
{
"locationID": LOCATION_ID,
"name": "Ocala P01",
"devices": [
{
"vacationHold": {"Enabled": True},
"scheduleStatus": "Resume",
"settings": {"devicePairingEnabled": True},
"deviceClass": "Thermostat",
"deviceType": "Thermostat",
"deviceID": DEVICE_ID,
"name": "Ocala",
"macID": MAC_ID,
"units": "Fahrenheit",
"indoorTemperature": 79,
"deviceModel": "T9-T10",
}
],
"users": [],
}
]

PRIORITY_RESPONSE = {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Put these in a json file in fixtures

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done — LOCATIONS_RESPONSE (and the priority payload, before it moved out entirely, see below) now live in tests/components/lyric/fixtures/*.json, loaded via load_json_array_fixture.

Comment thread tests/components/lyric/conftest.py Outdated
Comment on lines +116 to +145
@pytest.fixture
def mock_lyric_api() -> Generator[None]:
"""Patch the aiolyric client to build real Location/Priority objects.

Patches Lyric.get_locations/get_thermostat_rooms directly rather than
mocking HTTP responses, so tests exercise real aiolyric parsing (the
same LyricLocation/LyricPriority code reading the actual field names
Resideo returns) without depending on network-mocking machinery.
"""

async def get_locations(self: Lyric) -> None:
self._locations = [
LyricLocation(self._client, location) for location in LOCATIONS_RESPONSE
]
self._locations_dict = {
location.location_id: location for location in self._locations
}

async def get_thermostat_rooms(
self: Lyric, location_id: str, device_id: str
) -> None:
priority = LyricPriority(PRIORITY_RESPONSE)
self._priorities_dict[priority.device_id] = priority
self._rooms_dict[priority.device_id] = {
room.id: room for room in priority.current_priority.rooms
}

with (
patch.object(Lyric, "get_locations", get_locations),
patch.object(Lyric, "get_thermostat_rooms", get_thermostat_rooms),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Instead check mealie how we patch the object there. This is already better than just patching http request so that's an improvement :)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good call, thanks — switched to patching Lyric where the integration imports it, with autospec=True, same as mealie's MealieClient pattern. Real LyricLocation/LyricPriority objects are still built from the fixture JSON and assigned directly onto the mock (locations, rooms_dict, etc.), so field-name parsing is still exercised for real — this just changes how the client gets substituted, not what's actually under test. Bonus: since nothing touches the network anymore, I could drop the whole "give it a valid unexpired token" dance too.

Comment thread tests/components/lyric/conftest.py Outdated
Comment on lines +150 to +157
async def async_setup_lyric_entry(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
) -> None:
"""Set up the mock config entry and wait for it to settle."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this is more a method for __init__.py as it's not a fixture

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Right — moved it to tests/components/lyric/init.py as setup_integration, matching what dev's newer tests already do there.

Comment on lines +61 to +71
@pytest.mark.xfail(
strict=True,
reason=(
"aiolyric 2.1.1's VacationHold.enabled reads JSON key 'enabled', but "
"Resideo's live API returns 'Enabled' (capital E). Fixed upstream in "
"clutch2sft/aiolyric#fix-vacation-hold-key; once that's released and "
"the manifest pin is bumped, this will start passing for real and "
"this marker must be removed."
),
)
async def test_vacation_hold_created(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's not add failing tests directly IMO

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fair, and it lines up with what abmantis said on the Priority Status sensor PR too — don't ship an entity that can't work yet. Rather than keep arguing that case per-entity, I split this PR down to just device_pairing_enabled (works today against the currently-pinned aiolyric) and moved vacation_hold/room_motion to a held-back branch to pick back up once aiolyric#165 and the vacation-hold-key fix actually release.

That also resolved the second half of this comment for free: with only one entity left, and its suitable_fn always True, there was nothing left for a direct value_fn/suitable_fn test to usefully cover beyond what a single snapshot_platform test already asserts — so that's what's here now instead.

Comment on lines +120 to +150
def test_vacation_hold_value_fn() -> None:
"""Vacation Hold reflects device.vacation_hold.enabled."""
description = next(d for d in DEVICE_BINARY_SENSORS if d.key == "vacation_hold")
assert description.value_fn(_mock_device(vacation_enabled=True)) is True
assert description.value_fn(_mock_device(vacation_enabled=False)) is False


def test_device_pairing_enabled_value_fn() -> None:
"""Device Pairing Enabled reflects device.settings.device_pairing_enabled."""
description = next(
d for d in DEVICE_BINARY_SENSORS if d.key == "device_pairing_enabled"
)
assert description.value_fn(_mock_device(pairing_enabled=True)) is True
assert description.value_fn(_mock_device(pairing_enabled=False)) is False


def test_device_pairing_enabled_is_diagnostic() -> None:
"""Device Pairing Enabled is diagnostic; Vacation Hold is not."""
pairing = next(
d for d in DEVICE_BINARY_SENSORS if d.key == "device_pairing_enabled"
)
vacation = next(d for d in DEVICE_BINARY_SENSORS if d.key == "vacation_hold")
assert pairing.entity_category is EntityCategory.DIAGNOSTIC
assert vacation.entity_category is None


def test_room_motion_suitable_fn_filters_by_accessory_type() -> None:
"""Room Motion only applies to IndoorAirSensor accessories."""
description = ACCESSORY_BINARY_SENSORS[0]
sensor_accessory = _mock_accessory(1, "IndoorAirSensor")
thermostat_accessory = _mock_accessory(0, "Thermostat")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We don't test all these things directly. Instead use snapshot_platform to fixate all the entities at least once, and for things like suitable_fn, you can change the source data and see that it's not created

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

See above

@home-assistant
home-assistant Bot marked this pull request as draft July 28, 2026 11:11
@home-assistant

Copy link
Copy Markdown
Contributor

Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍

Learn more about our pull request process.

…helper

- Move LOCATIONS_RESPONSE/PRIORITY_RESPONSE from inline dicts into
  fixtures/locations.json and fixtures/priority.json.
- Patch Lyric where the integration imports it (autospec, matching
  mealie's client-mocking pattern) instead of patching individual
  methods on the aiolyric class directly. Real LyricLocation/LyricPriority
  objects are still built from the fixture JSON and assigned directly to
  the mock's locations/rooms_dict/priorities_dict, so field-name parsing
  is still exercised for real - this only changes how the client is
  substituted, not what gets tested. Bonus: no longer needs a valid
  OAuth token dance to avoid HTTP, since nothing touches the network at
  all now.
- Move async_setup_lyric_entry out of conftest.py into
  tests/components/lyric/__init__.py as setup_integration, matching the
  convention already used by this integration's other test module (it's
  a helper, not a fixture).
joostlek pushed back on shipping xfail-marked entities directly
(vacation_hold, room_motion), consistent with abmantis's separate
"keep as draft until the dependency lands" stance on the Priority
Status sensor PR. Rather than argue two reviewers down independently,
split scope: this PR now ships only device_pairing_enabled, which
works today against the currently-pinned aiolyric. vacation_hold and
room_motion move to a held-back branch
(binary-sensor-vacation-hold-room-motion) to resume once
timmo001/aiolyric#165 and the vacation-hold-key fix release.

- Remove ACCESSORY_BINARY_SENSORS/LyricAccessoryBinarySensor and the
  vacation_hold DEVICE_BINARY_SENSORS entry from binary_sensor.py, and
  their strings.json entries - dead code with only one entity left.
- Drop the now-unused priority.json fixture and the rooms_dict/
  priorities_dict setup in mock_lyric_api; nothing left needs room data.
- Replace the three hand-written entity tests (one xfail, two working)
  with a single snapshot_platform test, and drop the direct value_fn/
  suitable_fn unit tests - joostlek's other point on this PR. With only
  one entity and a suitable_fn that's always True, there's nothing left
  for those to usefully cover beyond what the snapshot already asserts.
Copilot AI review requested due to automatic review settings July 28, 2026 19:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

homeassistant/components/lyric/binary_sensor.py:37

  • Align the implementation with the advertised binary sensors. The PR description and linked documentation say this platform adds Room Motion, Vacation Hold, and Device Pairing Enabled, but DEVICE_BINARY_SENSORS now defines only Device Pairing Enabled, so the first two entities are never created.
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,
    ),

tests/components/lyric/conftest.py:63

  • Shorten this fixture docstring to describe only its behavior. The autospec/Mealie implementation narrative duplicates the body and will become stale when the mock setup changes.
    """Mock the aiolyric client, backed by a real Location parsed from a live-shaped fixture.

    Patches Lyric where the integration imports it (autospec, like the
    mealie client mock) rather than mocking HTTP responses, so tests
    exercise real aiolyric parsing - the same LyricLocation code reading

…ract

Resideo's own API docs document locationID as an Integer, and aiolyric's
get_thermostat_rooms already types it int, but this fixture quoted it
as a string - inherited from early in this session before checking the
real docs. No behavior change (the value is only ever interpolated into
a URL), but the fixture now matches reality.
Copilot AI review requested due to automatic review settings July 28, 2026 21:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (3)

homeassistant/components/lyric/binary_sensor.py:34

  • Implement the advertised Room Motion and Vacation Hold entities, or narrow the PR title and description to the pairing diagnostic. DEVICE_BINARY_SENSORS currently defines only Device Pairing Enabled, so the code does not deliver two of the three entities described as this PR's purpose.
DEVICE_BINARY_SENSORS: list[LyricBinarySensorEntityDescription] = [
    LyricBinarySensorEntityDescription(
        key="device_pairing_enabled",
        translation_key="device_pairing_enabled",
        entity_category=EntityCategory.DIAGNOSTIC,

tests/components/lyric/conftest.py:65

  • Condense this fixture docstring to a single purpose-focused sentence; the patch-location and Mealie comparison narrate implementation details that callers do not need.
    Patches Lyric where the integration imports it (autospec, like the
    mealie client mock) rather than mocking HTTP responses, so tests
    exercise real aiolyric parsing - the same LyricLocation code reading
    the actual field names Resideo returns.
    """

tests/components/lyric/test_binary_sensor.py:21

  • Activate the unused fixtures with pytest.mark.usefixtures instead of injecting them as unused parameters, following the repository's test guidance.
    setup_credentials: None,
    mock_lyric_api: MagicMock,

- Condense mock_lyric_api's docstring to a single purpose-focused
  sentence; drop the patch-location/Mealie-comparison narration.
- Apply setup_credentials/mock_lyric_api via usefixtures in
  test_binary_sensor instead of injecting them as unused parameters.
@clutch2sft clutch2sft changed the title Binary sensor room motion Add Device Pairing Enabled binary sensor to Lyric integration Jul 28, 2026
Copilot AI review requested due to automatic review settings July 28, 2026 21:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

@clutch2sft
clutch2sft marked this pull request as ready for review July 29, 2026 10:58
@home-assistant
home-assistant Bot requested a review from joostlek July 29, 2026 10:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants