Skip to content

Use sub devices for Overkiz#162297

Draft
piitaya wants to merge 4 commits intodevfrom
overkiz_sub_devices
Draft

Use sub devices for Overkiz#162297
piitaya wants to merge 4 commits intodevfrom
overkiz_sub_devices

Conversation

@piitaya
Copy link
Member

@piitaya piitaya commented Feb 5, 2026

Proposed change

Some Overkiz devices like Atlantic Pass APC heat pumps have multiple sub-devices (zones, water heater, sensors) that belong to different physical locations. Previously, all sub-devices were grouped under a single device.

This change automatically detects when sub-devices have different placeOIDs and creates separate devices for each location, while maintaining proper device hierarchy linking them to the main actuator.

For example, my Heat Pump device has been split into 5 different devices :

  • Heat Pump (main devices) - Global ("All house" area)
  • Ground floor thermostat - Living room
  • First floor thermostat - Landing
  • Water heater - Laundry room
  • External temperature sensor - Outdoor

Devices list

CleanShot 2026-02-05 at 13 21 13

Main device

CleanShot 2026-02-05 at 13 26 45

Sub device

CleanShot 2026-02-05 at 13 27 44

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

  • This PR fixes or closes issue: fixes #
  • This PR is related to issue:
  • Link to documentation pull request:
  • Link to developer documentation pull request:
  • Link to frontend pull request:

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:

@home-assistant
Copy link

home-assistant bot commented Feb 5, 2026

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

Code owner commands

Code owners of overkiz can trigger bot actions by commenting:

  • @home-assistant close Closes the pull request.
  • @home-assistant rename Awesome new title Renames the pull request.
  • @home-assistant reopen Reopen the pull request.
  • @home-assistant unassign overkiz Removes the current integration label and assignees on the pull request, add the integration domain after the command.
  • @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.

@piitaya piitaya changed the title Overkiz sub devices Use sub devices for Overkiz Feb 5, 2026
@piitaya piitaya marked this pull request as ready for review February 5, 2026 12:28
@piitaya piitaya requested a review from iMicknl as a code owner February 5, 2026 12:28
Copilot AI review requested due to automatic review settings February 5, 2026 12:28
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request adds placeOID-based device grouping for the Overkiz integration, enabling automatic detection and separation of sub-devices belonging to different physical locations. Previously, all sub-devices (e.g., thermostats, water heaters, sensors) sharing a base device URL were grouped under a single device. Now, when sub-devices have different placeOID values, they are split into separate devices with proper hierarchical linking to the main actuator device.

Changes:

  • Introduced placeOID-based device grouping logic to automatically detect when sub-devices belong to different physical locations
  • Modified device identifier generation to use base_url#place_oid format when grouping is enabled
  • Updated via_device linking so sub-devices link to the main actuator's device instead of directly to the gateway
  • Added comprehensive unit tests for the new helper methods

Reviewed changes

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

File Description
homeassistant/components/overkiz/entity.py Core logic changes: added 3 new helper methods (_get_sibling_devices, _has_siblings_with_different_place_oid, _is_main_device_for_place_oid, _get_via_device_id) and modified generate_device_info to support placeOID-based grouping
tests/components/overkiz/test_entity.py New test file with 4 test functions covering the main scenarios for placeOID detection, main device identification, and via_device linking

Copy link
Contributor

Copilot AI left a comment

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 2 out of 2 changed files in this pull request and generated 3 comments.

Comment on lines +90 to +177
def test_get_via_device_id_sub_device_links_to_main() -> None:
"""Test sub-device links to main actuator with placeOID grouping."""
devices = [
_create_mock_device("io://gateway/123#1", "place-a", "Actuator"),
_create_mock_device("io://gateway/123#2", "place-b", "Zone"),
]
entity = _create_mock_entity(devices[1], devices)
entity.executor = Mock()
entity.executor.get_gateway_id = Mock(return_value="gateway-id")

result = OverkizEntity._get_via_device_id(entity, use_place_oid_grouping=True)

assert result == "io://gateway/123#place-a"


def test_get_via_device_id_main_device_links_to_gateway() -> None:
"""Test main device (#1) links to gateway."""
devices = [
_create_mock_device("io://gateway/123#1", "place-a", "Actuator"),
]
entity = _create_mock_entity(devices[0], devices)
entity.executor = Mock()
entity.executor.get_gateway_id = Mock(return_value="gateway-id")

result = OverkizEntity._get_via_device_id(entity, use_place_oid_grouping=True)

assert result == "gateway-id"


def test_has_siblings_with_no_place_oid() -> None:
"""Test device with no placeOID returns False."""
devices = [
_create_mock_device("io://gateway/123#1", None, "Device 1"),
_create_mock_device("io://gateway/123#2", "place-b", "Device 2"),
]
entity = _create_mock_entity(devices[0], devices)

result = OverkizEntity._has_siblings_with_different_place_oid(entity)

assert result is False


def test_is_main_device_with_no_place_oid() -> None:
"""Test device with no placeOID is always considered main."""
devices = [
_create_mock_device("io://gateway/123#2", None, "Device 2"),
_create_mock_device("io://gateway/123#1", "place-a", "Device 1"),
]
entity = _create_mock_entity(devices[0], devices)

result = OverkizEntity._is_main_device_for_place_oid(entity)

assert result is True


def test_get_via_device_id_main_device_without_place_oid() -> None:
"""Test fallback to gateway when #1 device has no placeOID."""
devices = [
_create_mock_device("io://gateway/123#1", None, "Actuator"),
_create_mock_device("io://gateway/123#2", "place-b", "Zone"),
]
entity = _create_mock_entity(devices[1], devices)
entity.executor = Mock()
entity.executor.get_gateway_id = Mock(return_value="gateway-id")

result = OverkizEntity._get_via_device_id(entity, use_place_oid_grouping=True)

assert result == "gateway-id"


@pytest.mark.parametrize(
("device_url", "expected"),
[
("io://gateway/123#4", 4),
("io://gateway/123#10", 10),
("io://gateway/123#abc", None),
("io://gateway/123#", None),
],
ids=["single_digit", "multi_digit", "non_numeric", "empty_suffix"],
)
def test_get_device_index(device_url: str, expected: int | None) -> None:
"""Test extracting numeric index from device URL."""
device = _create_mock_device(device_url, "place-a")
entity = _create_mock_entity(device, [device])

result = OverkizEntity._get_device_index(entity, device_url)

assert result == expected
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

Consider adding a test case for when place_oid_grouping is enabled but there are more than two sub-devices with different placeOIDs. This would verify the device hierarchy is correctly constructed when there are multiple zones or sub-devices, ensuring that all sub-devices properly link to the main actuator device.

Copilot uses AI. Check for mistakes.
Comment on lines +119 to +177
def test_has_siblings_with_no_place_oid() -> None:
"""Test device with no placeOID returns False."""
devices = [
_create_mock_device("io://gateway/123#1", None, "Device 1"),
_create_mock_device("io://gateway/123#2", "place-b", "Device 2"),
]
entity = _create_mock_entity(devices[0], devices)

result = OverkizEntity._has_siblings_with_different_place_oid(entity)

assert result is False


def test_is_main_device_with_no_place_oid() -> None:
"""Test device with no placeOID is always considered main."""
devices = [
_create_mock_device("io://gateway/123#2", None, "Device 2"),
_create_mock_device("io://gateway/123#1", "place-a", "Device 1"),
]
entity = _create_mock_entity(devices[0], devices)

result = OverkizEntity._is_main_device_for_place_oid(entity)

assert result is True


def test_get_via_device_id_main_device_without_place_oid() -> None:
"""Test fallback to gateway when #1 device has no placeOID."""
devices = [
_create_mock_device("io://gateway/123#1", None, "Actuator"),
_create_mock_device("io://gateway/123#2", "place-b", "Zone"),
]
entity = _create_mock_entity(devices[1], devices)
entity.executor = Mock()
entity.executor.get_gateway_id = Mock(return_value="gateway-id")

result = OverkizEntity._get_via_device_id(entity, use_place_oid_grouping=True)

assert result == "gateway-id"


@pytest.mark.parametrize(
("device_url", "expected"),
[
("io://gateway/123#4", 4),
("io://gateway/123#10", 10),
("io://gateway/123#abc", None),
("io://gateway/123#", None),
],
ids=["single_digit", "multi_digit", "non_numeric", "empty_suffix"],
)
def test_get_device_index(device_url: str, expected: int | None) -> None:
"""Test extracting numeric index from device URL."""
device = _create_mock_device(device_url, "place-a")
entity = _create_mock_entity(device, [device])

result = OverkizEntity._get_device_index(entity, device_url)

assert result == expected
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

Consider adding a test case for a scenario with three or more devices where some have place_oid and others don't. For example:

This would verify that the device with no place_oid correctly falls back to the old behavior (inheriting from parent) while devices with different place_oids create separate device entries.

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +43
def _create_mock_entity(device: Mock, all_devices: list[Mock]) -> Mock:
"""Create a mock entity with the given device and coordinator data."""
entity = Mock(spec=OverkizEntity)
entity.device = device
entity.device_url = device.device_url
entity.base_device_url = device.device_url.split("#")[0]
entity.coordinator = Mock()
entity.coordinator.data = {d.device_url: d for d in all_devices}

prefix = f"{entity.base_device_url}#"
entity._get_sibling_devices = lambda: [
d
for d in all_devices
if d.device_url != device.device_url and d.device_url.startswith(prefix)
]
entity._get_device_index = lambda url: (
int(url.split("#")[-1]) if url.split("#")[-1].isdigit() else None
)
return entity
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The mock entity setup is testing private methods by calling them directly with 'self' passed as the first argument. While this works, consider creating full OverkizEntity instances with proper mocking of dependencies to test the public API instead. This would better match Home Assistant testing conventions and ensure the entity initialization and device_info generation work correctly as a whole.

For example, instead of:

OverkizEntity._has_siblings_with_different_place_oid(entity)

Consider:

entity = OverkizEntity(device_url, mock_coordinator)
result = entity._has_siblings_with_different_place_oid()

This approach would provide more robust integration testing and catch issues with the overall flow.

Copilot uses AI. Check for mistakes.
@iMicknl
Copy link
Member

iMicknl commented Feb 15, 2026

Thanks @piitaya, great catch. I did not realize this situation could occur with sub devices.

  • Do these devices use a different placeOID by default? Is that defined during setup, or does the user need to configure something manually? And what happens if the placeOID changes after the integration is already set up in Overkiz?

  • We are currently working on a V2 of the pyOverkiz library to add extra helpers to the Device class. I will review whether we can include the necessary utilities to properly support this case as well.

I will go through your PR in more detail over the next few days!

@piitaya
Copy link
Member Author

piitaya commented Feb 25, 2026

I'm not sure if it was a different placeOID by default. I set up my heat pump years ago 😅. I also haven't tested a placeOID change.

@piitaya
Copy link
Member Author

piitaya commented Feb 25, 2026

@iMicknl I just checked, when changing the place in Somfy app, the entities are moved to a new device... 😢
So... may be it's better to hardcode the grouping instead of relying on placeOID.

@iMicknl iMicknl marked this pull request as draft February 28, 2026 13:33
@iMicknl
Copy link
Member

iMicknl commented Feb 28, 2026

So... may be it's better to hardcode the grouping instead of relying on placeOID.

@piitaya, what would be your proposal here? I am not sure what is the best way forward.

In general these climate devices are quite complex (also visible in the 25+ open issues) and the behavior between these devices differs a lot...

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.

3 participants