mirror of
https://github.com/home-assistant/core.git
synced 2026-09-05 21:12:24 +01:00
Fix deviceinfo missing mac and s/n for Midea (#180594)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"""The Midea integration."""
|
||||
|
||||
from collections.abc import Mapping
|
||||
from functools import partial
|
||||
from typing import Any
|
||||
|
||||
from midealocal.const import ProtocolVersion
|
||||
@@ -11,6 +12,7 @@ from midealocal.discover import discover
|
||||
from homeassistant.const import (
|
||||
CONF_DEVICE_ID,
|
||||
CONF_IP_ADDRESS,
|
||||
CONF_MAC,
|
||||
CONF_MODEL,
|
||||
CONF_NAME,
|
||||
CONF_PORT,
|
||||
@@ -21,8 +23,9 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryError, ConfigEntryNotReady
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from .const import CONF_KEY, CONF_SUBTYPE, LOGGER
|
||||
from .const import CONF_KEY, CONF_SN, CONF_SUBTYPE, LOGGER
|
||||
from .entity import MideaConfigEntry
|
||||
|
||||
_PLATFORMS: list[Platform] = [
|
||||
@@ -133,6 +136,43 @@ async def async_setup_entry(hass: HomeAssistant, entry: MideaConfigEntry) -> boo
|
||||
return True
|
||||
|
||||
|
||||
async def async_migrate_entry(hass: HomeAssistant, entry: MideaConfigEntry) -> bool:
|
||||
"""Migrate an old config entry."""
|
||||
if entry.minor_version < 2:
|
||||
# minor_version 2 adds mac/serial number; discovery already parses both
|
||||
# from the device reply, so backfill them for pre-existing entries.
|
||||
discovered = await hass.async_add_executor_job(
|
||||
partial(discover, ip_address=entry.data[CONF_IP_ADDRESS])
|
||||
)
|
||||
device = discovered.get(entry.data[CONF_DEVICE_ID], {})
|
||||
new_data = {**entry.data}
|
||||
if mac := device.get(CONF_MAC):
|
||||
new_data[CONF_MAC] = mac
|
||||
if serial_number := device.get(CONF_SN):
|
||||
new_data[CONF_SN] = serial_number
|
||||
|
||||
# Drop any network-mac connection with an empty value so only a real
|
||||
# address remains; the device registry never removes these on its own.
|
||||
device_registry = dr.async_get(hass)
|
||||
for device_entry in dr.async_entries_for_config_entry(
|
||||
device_registry, entry.entry_id
|
||||
):
|
||||
connections = {
|
||||
connection
|
||||
for connection in device_entry.connections
|
||||
if connection[0] != dr.CONNECTION_NETWORK_MAC
|
||||
or str(connection[1]).lower() not in ("", "none")
|
||||
}
|
||||
if connections != device_entry.connections:
|
||||
device_registry.async_update_device(
|
||||
device_entry.id, new_connections=connections
|
||||
)
|
||||
|
||||
hass.config_entries.async_update_entry(entry, data=new_data, minor_version=2)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: MideaConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
return await hass.config_entries.async_unload_platforms(entry, _PLATFORMS)
|
||||
|
||||
@@ -21,6 +21,7 @@ from homeassistant.const import (
|
||||
CONF_DEVICE,
|
||||
CONF_DEVICE_ID,
|
||||
CONF_IP_ADDRESS,
|
||||
CONF_MAC,
|
||||
CONF_MODEL,
|
||||
CONF_NAME,
|
||||
CONF_PASSWORD,
|
||||
@@ -32,7 +33,15 @@ from homeassistant.const import (
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.selector import SelectSelector, SelectSelectorConfig
|
||||
|
||||
from .const import CONF_ACCOUNT, CONF_KEY, CONF_SERVER, CONF_SUBTYPE, DOMAIN, LOGGER
|
||||
from .const import (
|
||||
CONF_ACCOUNT,
|
||||
CONF_KEY,
|
||||
CONF_SERVER,
|
||||
CONF_SN,
|
||||
CONF_SUBTYPE,
|
||||
DOMAIN,
|
||||
LOGGER,
|
||||
)
|
||||
from .device_catalog import MIDEA_DEVICE_NAMES
|
||||
|
||||
DEFAULT_CLOUD: str = get_default_cloud()
|
||||
@@ -91,7 +100,7 @@ class MideaConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""
|
||||
|
||||
VERSION = 1
|
||||
MINOR_VERSION = 1
|
||||
MINOR_VERSION = 2
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""MideaConfigFlow class."""
|
||||
@@ -456,6 +465,8 @@ class MideaConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
CONF_IP_ADDRESS: device.get(CONF_IP_ADDRESS),
|
||||
CONF_PORT: device.get(CONF_PORT),
|
||||
CONF_MODEL: device.get(CONF_MODEL),
|
||||
CONF_MAC: device.get(CONF_MAC),
|
||||
CONF_SN: device.get(CONF_SN),
|
||||
}
|
||||
|
||||
# MUST get a auth passed token/key for v3 device, disable add before pass
|
||||
@@ -541,6 +552,8 @@ class MideaConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
CONF_SUBTYPE: self.found_device.get(CONF_SUBTYPE) or 0,
|
||||
CONF_TOKEN: self.found_device.get(CONF_TOKEN) or "",
|
||||
CONF_KEY: self.found_device.get(CONF_KEY) or "",
|
||||
CONF_MAC: self.found_device.get(CONF_MAC),
|
||||
CONF_SN: self.found_device.get(CONF_SN),
|
||||
}
|
||||
|
||||
async def _async_create_midea_entry(
|
||||
@@ -589,6 +602,10 @@ class MideaConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
CONF_TOKEN: user_input[CONF_TOKEN],
|
||||
CONF_KEY: user_input[CONF_KEY],
|
||||
}
|
||||
if mac := user_input.get(CONF_MAC):
|
||||
data[CONF_MAC] = mac
|
||||
if serial_number := user_input.get(CONF_SN):
|
||||
data[CONF_SN] = serial_number
|
||||
|
||||
return self.async_create_entry(
|
||||
title=name,
|
||||
@@ -679,6 +696,9 @@ class MideaConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
user_input[CONF_KEY] = keys["key"]
|
||||
user_input[CONF_TOKEN] = keys["token"]
|
||||
|
||||
user_input[CONF_MAC] = device.get(CONF_MAC)
|
||||
user_input[CONF_SN] = device.get(CONF_SN)
|
||||
|
||||
self.found_device = {
|
||||
CONF_DEVICE_ID: user_input[CONF_DEVICE_ID],
|
||||
CONF_NAME: self.found_device.get(CONF_NAME),
|
||||
@@ -689,6 +709,8 @@ class MideaConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
CONF_MODEL: user_input[CONF_MODEL],
|
||||
CONF_TOKEN: user_input[CONF_TOKEN],
|
||||
CONF_KEY: user_input[CONF_KEY],
|
||||
CONF_MAC: user_input[CONF_MAC],
|
||||
CONF_SN: user_input[CONF_SN],
|
||||
}
|
||||
|
||||
return await self._async_create_midea_entry(user_input)
|
||||
|
||||
@@ -12,6 +12,7 @@ CONF_KEY = "key"
|
||||
CONF_SUBTYPE = "subtype"
|
||||
CONF_ACCOUNT = "account"
|
||||
CONF_SERVER = "server"
|
||||
CONF_SN = "sn"
|
||||
|
||||
PRESET_MODE_NONE = "none"
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.const import CONF_TOKEN
|
||||
from homeassistant.const import CONF_MAC, CONF_TOKEN
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import CONF_KEY
|
||||
from .const import CONF_KEY, CONF_SN
|
||||
from .entity import MideaConfigEntry
|
||||
|
||||
TO_REDACT = {CONF_TOKEN, CONF_KEY}
|
||||
TO_REDACT = {CONF_TOKEN, CONF_KEY, CONF_MAC, CONF_SN}
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
|
||||
@@ -174,6 +174,7 @@ def mock_config_entry() -> Callable[[DummyDevice], MockConfigEntry]:
|
||||
def _create(device: DummyDevice) -> MockConfigEntry:
|
||||
return MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
minor_version=2,
|
||||
data={
|
||||
**BASE_DATA,
|
||||
CONF_TYPE: device.device_type,
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
from midealocal.const import ProtocolVersion
|
||||
|
||||
from homeassistant.components.midea.const import CONF_KEY, CONF_SUBTYPE
|
||||
from homeassistant.components.midea.const import CONF_KEY, CONF_SN, CONF_SUBTYPE
|
||||
from homeassistant.components.midea.device_catalog import MIDEA_DEVICE_NAMES
|
||||
from homeassistant.const import (
|
||||
CONF_DEVICE_ID,
|
||||
CONF_IP_ADDRESS,
|
||||
CONF_MAC,
|
||||
CONF_MODEL,
|
||||
CONF_PORT,
|
||||
CONF_PROTOCOL,
|
||||
@@ -38,6 +39,8 @@ DISCOVERY_RESULT = {
|
||||
TEST_DEVICE_ID: {
|
||||
**BASE_DATA,
|
||||
CONF_TYPE: TEST_TYPE,
|
||||
CONF_MAC: TEST_MAC_ADDRESS,
|
||||
CONF_SN: TEST_SERIAL_NUMBER,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
'discovery_keys': dict({
|
||||
}),
|
||||
'domain': 'midea',
|
||||
'minor_version': 1,
|
||||
'minor_version': 2,
|
||||
'options': dict({
|
||||
}),
|
||||
'pref_disable_new_entities': False,
|
||||
|
||||
@@ -17,6 +17,7 @@ from homeassistant.components.midea.const import (
|
||||
CONF_ACCOUNT,
|
||||
CONF_KEY,
|
||||
CONF_SERVER,
|
||||
CONF_SN,
|
||||
CONF_SUBTYPE,
|
||||
DOMAIN,
|
||||
)
|
||||
@@ -26,6 +27,7 @@ from homeassistant.const import (
|
||||
CONF_DEVICE,
|
||||
CONF_DEVICE_ID,
|
||||
CONF_IP_ADDRESS,
|
||||
CONF_MAC,
|
||||
CONF_MODEL,
|
||||
CONF_NAME,
|
||||
CONF_PASSWORD,
|
||||
@@ -44,9 +46,11 @@ from .const import (
|
||||
TEST_DEVICE_ID,
|
||||
TEST_IP_ADDRESS,
|
||||
TEST_KEY,
|
||||
TEST_MAC_ADDRESS,
|
||||
TEST_MODEL,
|
||||
TEST_PORT,
|
||||
TEST_PROTOCOL,
|
||||
TEST_SERIAL_NUMBER,
|
||||
TEST_SUBTYPE,
|
||||
TEST_TOKEN,
|
||||
TEST_TYPE,
|
||||
@@ -104,6 +108,8 @@ async def test_manual_flow_success(hass: HomeAssistant) -> None:
|
||||
CONF_SUBTYPE: TEST_SUBTYPE,
|
||||
CONF_TOKEN: TEST_TOKEN,
|
||||
CONF_KEY: TEST_KEY,
|
||||
CONF_MAC: TEST_MAC_ADDRESS,
|
||||
CONF_SN: TEST_SERIAL_NUMBER,
|
||||
}
|
||||
|
||||
|
||||
@@ -1056,7 +1062,13 @@ async def test_auto_flow_v1_v2_success_when_cloud_down(
|
||||
) -> None:
|
||||
"""Test v1/v2 devices are added without ever using the cloud, even if it is down."""
|
||||
mock_devices = {
|
||||
TEST_DEVICE_ID: {**BASE_DATA, CONF_TYPE: TEST_TYPE, CONF_PROTOCOL: protocol},
|
||||
TEST_DEVICE_ID: {
|
||||
**BASE_DATA,
|
||||
CONF_TYPE: TEST_TYPE,
|
||||
CONF_PROTOCOL: protocol,
|
||||
CONF_MAC: TEST_MAC_ADDRESS,
|
||||
CONF_SN: TEST_SERIAL_NUMBER,
|
||||
},
|
||||
}
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
@@ -1105,6 +1117,8 @@ async def test_auto_flow_v1_v2_success_when_cloud_down(
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["data"][CONF_DEVICE_ID] == TEST_DEVICE_ID
|
||||
assert result["data"][CONF_PROTOCOL] == protocol
|
||||
assert result["data"][CONF_MAC] == TEST_MAC_ADDRESS
|
||||
assert result["data"][CONF_SN] == TEST_SERIAL_NUMBER
|
||||
|
||||
|
||||
async def test_login_credentials_step_renders_with_cloud_servers(
|
||||
|
||||
@@ -4,11 +4,12 @@ from unittest.mock import patch
|
||||
|
||||
from midealocal.const import DeviceType, ProtocolVersion
|
||||
|
||||
from homeassistant.components.midea.const import CONF_KEY, CONF_SUBTYPE, DOMAIN
|
||||
from homeassistant.components.midea.const import CONF_KEY, CONF_SN, CONF_SUBTYPE, DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import (
|
||||
CONF_DEVICE_ID,
|
||||
CONF_IP_ADDRESS,
|
||||
CONF_MAC,
|
||||
CONF_MODEL,
|
||||
CONF_NAME,
|
||||
CONF_PORT,
|
||||
@@ -17,10 +18,11 @@ from homeassistant.const import (
|
||||
CONF_TYPE,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .conftest import DummyDevice
|
||||
from .const import TEST_DEVICE_ID
|
||||
from .const import TEST_DEVICE_ID, TEST_IP_ADDRESS, TEST_MAC_ADDRESS, TEST_SERIAL_NUMBER
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
@@ -45,7 +47,7 @@ async def test_async_setup(hass: HomeAssistant) -> None:
|
||||
|
||||
async def test_unload_entry(hass: HomeAssistant) -> None:
|
||||
"""Test async_unload_entry unloads platforms and closes the device."""
|
||||
entry = MockConfigEntry(domain=DOMAIN, data=_ENTRY_DATA)
|
||||
entry = MockConfigEntry(domain=DOMAIN, data=_ENTRY_DATA, minor_version=2)
|
||||
entry.add_to_hass(hass)
|
||||
device = DummyDevice(DeviceType.AC)
|
||||
with patch(
|
||||
@@ -62,7 +64,7 @@ async def test_unload_entry(hass: HomeAssistant) -> None:
|
||||
|
||||
async def test_async_setup_entry_paths(hass: HomeAssistant) -> None:
|
||||
"""Test async_setup_entry for success and no-device return."""
|
||||
entry = MockConfigEntry(domain=DOMAIN, data=_ENTRY_DATA)
|
||||
entry = MockConfigEntry(domain=DOMAIN, data=_ENTRY_DATA, minor_version=2)
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.midea.device_selector",
|
||||
@@ -74,6 +76,7 @@ async def test_async_setup_entry_paths(hass: HomeAssistant) -> None:
|
||||
entry2 = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={**_ENTRY_DATA, CONF_DEVICE_ID: TEST_DEVICE_ID + 1},
|
||||
minor_version=2,
|
||||
)
|
||||
entry2.add_to_hass(hass)
|
||||
with patch(
|
||||
@@ -94,7 +97,7 @@ async def test_setup_entry_not_ready_on_connect_failure(
|
||||
It can also leave the socket open in that case (e.g. when authentication
|
||||
fails), so the socket must be closed explicitly to avoid a ResourceWarning.
|
||||
"""
|
||||
entry = MockConfigEntry(domain=DOMAIN, data=_ENTRY_DATA)
|
||||
entry = MockConfigEntry(domain=DOMAIN, data=_ENTRY_DATA, minor_version=2)
|
||||
entry.add_to_hass(hass)
|
||||
device = DummyDevice(DeviceType.AC)
|
||||
with (
|
||||
@@ -164,3 +167,94 @@ async def test_setup_entry_no_recovery_when_device_not_discovered(
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
assert entry.state is ConfigEntryState.SETUP_RETRY
|
||||
assert entry.data[CONF_IP_ADDRESS] == _ENTRY_DATA[CONF_IP_ADDRESS]
|
||||
|
||||
|
||||
async def test_migrate_entry_backfills_mac_and_serial_number(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test migration to minor_version 2 backfills mac and serial number."""
|
||||
entry = MockConfigEntry(domain=DOMAIN, data=_ENTRY_DATA, minor_version=1)
|
||||
entry.add_to_hass(hass)
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.midea.discover",
|
||||
return_value={
|
||||
TEST_DEVICE_ID: {
|
||||
CONF_IP_ADDRESS: TEST_IP_ADDRESS,
|
||||
CONF_MAC: TEST_MAC_ADDRESS,
|
||||
CONF_SN: TEST_SERIAL_NUMBER,
|
||||
}
|
||||
},
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.midea.device_selector",
|
||||
return_value=DummyDevice(DeviceType.AC),
|
||||
),
|
||||
):
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
assert entry.minor_version == 2
|
||||
assert entry.data[CONF_MAC] == TEST_MAC_ADDRESS
|
||||
assert entry.data[CONF_SN] == TEST_SERIAL_NUMBER
|
||||
|
||||
|
||||
async def test_migrate_entry_drops_empty_mac_connection(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
) -> None:
|
||||
"""Test migration removes a leftover empty network-mac connection."""
|
||||
entry = MockConfigEntry(domain=DOMAIN, data=_ENTRY_DATA, minor_version=1)
|
||||
entry.add_to_hass(hass)
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
identifiers={(DOMAIN, str(TEST_DEVICE_ID))},
|
||||
connections={
|
||||
(dr.CONNECTION_NETWORK_MAC, "None"),
|
||||
(dr.CONNECTION_NETWORK_MAC, TEST_MAC_ADDRESS),
|
||||
},
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.midea.discover",
|
||||
return_value={
|
||||
TEST_DEVICE_ID: {
|
||||
CONF_IP_ADDRESS: TEST_IP_ADDRESS,
|
||||
CONF_MAC: TEST_MAC_ADDRESS,
|
||||
CONF_SN: TEST_SERIAL_NUMBER,
|
||||
}
|
||||
},
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.midea.device_selector",
|
||||
return_value=DummyDevice(DeviceType.AC),
|
||||
),
|
||||
):
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
device_entry = device_registry.async_get_device_by_identifier(
|
||||
(DOMAIN, str(TEST_DEVICE_ID)), entry.entry_id
|
||||
)
|
||||
assert device_entry is not None
|
||||
assert device_entry.connections == {(dr.CONNECTION_NETWORK_MAC, TEST_MAC_ADDRESS)}
|
||||
|
||||
|
||||
async def test_migrate_entry_without_discovery_result(hass: HomeAssistant) -> None:
|
||||
"""Test migration still completes when discovery finds nothing."""
|
||||
entry = MockConfigEntry(domain=DOMAIN, data=_ENTRY_DATA, minor_version=1)
|
||||
entry.add_to_hass(hass)
|
||||
with (
|
||||
patch("homeassistant.components.midea.discover", return_value={}),
|
||||
patch(
|
||||
"homeassistant.components.midea.device_selector",
|
||||
return_value=DummyDevice(DeviceType.AC),
|
||||
),
|
||||
):
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
assert entry.minor_version == 2
|
||||
assert CONF_MAC not in entry.data
|
||||
assert CONF_SN not in entry.data
|
||||
|
||||
Reference in New Issue
Block a user