Skip to content

vNext: Fix the DHCP step for the sub-units of a gateway - #548

Merged
RobHofmann merged 1 commit into
RobHofmann:5.0-devfrom
Twintop:fix-dhcp-gateway-sub-units
Oct 9, 2026
Merged

RobHofmann merged 1 commit into
RobHofmann:5.0-devfrom
Twintop:fix-dhcp-gateway-sub-units

Conversation

@Twintop

@Twintop Twintop commented Oct 9, 2026

Copy link
Copy Markdown

Context

This is a bug fix in preparation for Gree zone controller support. I found it while porting the changes I made for my own zone controller (Gree LE60-13/GH) to this project. The zone controller support itself will come in a later PR. Background and the full plan: https://github.com/Twintop/ha-gree-zone-controller/blob/main/upstream/README.md

Symptom

When Home Assistant reports the DHCP lease of a VRF gateway or a zone controller, the DHCP flow fails:

  File "/config/custom_components/gree_custom/config_flow.py", line 363, in async_step_dhcp
    new_data[CONF_DEVICES][discovery_info.macaddress][
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
KeyError: '<gateway mac>'

Seen on Home Assistant 2026.10 after a restart, with the 5 sub-units of a zone controller configured.

Cause

  1. The lease MAC is the MAC of the gateway. async_step_dhcp() scans that IP and gets the sub-units back. It finds the entry with the MAC of each sub-unit (d.mac), but then looks up the device with the lease MAC, which is not in the entry. For a normal unit the two MACs are the same, so this only fails behind a gateway.
  2. new_data = dict(entry_match.data) is a shallow copy. The host is changed inside the nested dicts, and those are the same objects as in the entry. async_update_entry() then compares two equal dicts, sees no change, does not save the new IP and does not reload the entry. This part also affects normal units.

Change

  • Look the device up with d.mac. For a normal unit that is the lease MAC, so nothing changes there.
  • Copy the entry data with copy.deepcopy(), so the entry sees the change and saves it.
  • docs/configuration.md: the automatic IP update also covers the sub-units of a gateway.

Testing

  • The test suite does not cover the config flow. pytest: 254 passed, as before.
  • Ruff passes. Mypy and Pylint show nothing new compared with 5.0-dev. I ran them without Home Assistant installed, so I compared the findings with the base branch.
  • Real device, Home Assistant 2026.10 with the 5 sub-units of a LE60-13/GH zone controller: before the change, the error above was in the log after a restart. With the change, it is gone. I could not test a real IP change, because the controller kept its IP.

When Home Assistant sees the DHCP lease of a VRF gateway or a zone
controller, the MAC in the lease is the gateway. The configured devices are
its sub-units, so looking the lease MAC up in the entry raised KeyError and
the flow failed. The MAC of the discovered device is used now.

The entry data was also changed in place through a shallow copy, so
async_update_entry saw no change and the new IP was never saved. A deep
copy fixes that.
@Twintop Twintop changed the title Fix the DHCP step for the sub-units of a gateway vNext: Fix the DHCP step for the sub-units of a gateway Oct 9, 2026

@RobHofmann RobHofmann left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks, this is a clean fix and both causes are right.

What I checked:

  • In Home Assistant 2026.10, async_update_entry only saves when entry.data != data. With the old shallow copy, the nested dicts were changed in place, so the comparison saw no change: the new IP was not saved and the entry was not reloaded. The deep copy fixes that.
  • d.mac is always a key in the entry, because get_entry_matching_mac(d.mac) already matched it.
  • I ran the real async_step_dhcp in my fake-device harness against 5.0-dev and against this PR merged onto the current 5.0-dev tip. Base: a gateway lease raises KeyError, and a plain unit with a new IP is not saved and not reloaded. PR: the sub-units of a gateway and a plain unit both get the new IP, with one reload per entry. An unchanged IP causes no update and no reload.
  • On my dev instance with my test unit, the PR starts clean and polls normally. I could not test a real DHCP lease there.

Two small things for a later PR, not blocking: the TODO: Check if this only returns True if the IP Changed can go now (it does), and copy.deepcopy(entry_match.data) does not need the dict().

@RobHofmann
RobHofmann merged commit 20bee20 into RobHofmann:5.0-dev Oct 9, 2026
3 of 4 checks passed
RobHofmann added a commit that referenced this pull request Oct 9, 2026
Lint installed ruff>=0.16.3, so CI picked up Ruff 0.17.0 the day it came
out. 0.17 sorts an import with a trailing pragma comment differently, and
const.py failed I001 on every push and PR since #547. None of #547, #548
or #549 touched const.py.

0.16.10 and 0.17.0 want opposite forms of that import, so no code passes
both. Pin Ruff to an exact version in lint.yml and requirements_dev.txt
so a new release cannot break CI again, and apply the 0.17 fix.
@RobHofmann

Copy link
Copy Markdown
Owner

Correction to my review: the dict() in copy.deepcopy(dict(entry_match.data)) is needed, so please leave it in.

ConfigEntry.data is a MappingProxyType, and copy.deepcopy cannot copy one:

TypeError: cannot pickle 'mappingproxy' object

Without the dict(), the DHCP step would fail for every unit. The other point still stands: the TODO can go, and I will remove it in a small follow-up PR.

RobHofmann added a commit that referenced this pull request Oct 9, 2026
async_update_entry only returns True when the entry data changed.
Since #548 copies the entry data deep, a new IP counts as a change,
so the open question in the TODO is answered.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants