mirror of
https://github.com/home-assistant/core.git
synced 2025-07-08 05:47:10 +00:00
Fix missing device name with legacy flux_led discovery (#62785)
This commit is contained in:
parent
ff3b7489ec
commit
ad68d0795e
@ -32,6 +32,7 @@ from .const import (
|
|||||||
)
|
)
|
||||||
from .coordinator import FluxLedUpdateCoordinator
|
from .coordinator import FluxLedUpdateCoordinator
|
||||||
from .discovery import (
|
from .discovery import (
|
||||||
|
async_build_cached_discovery,
|
||||||
async_clear_discovery_cache,
|
async_clear_discovery_cache,
|
||||||
async_discover_device,
|
async_discover_device,
|
||||||
async_discover_devices,
|
async_discover_devices,
|
||||||
@ -84,11 +85,14 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up Flux LED/MagicLight from a config entry."""
|
"""Set up Flux LED/MagicLight from a config entry."""
|
||||||
host = entry.data[CONF_HOST]
|
host = entry.data[CONF_HOST]
|
||||||
directed_discovery = None
|
discovery_cached = True
|
||||||
if discovery := async_get_discovery(hass, host):
|
if discovery := async_get_discovery(hass, host):
|
||||||
directed_discovery = False
|
discovery_cached = False
|
||||||
|
else:
|
||||||
|
discovery = async_build_cached_discovery(entry)
|
||||||
device: AIOWifiLedBulb = async_wifi_bulb_for_host(host, discovery=discovery)
|
device: AIOWifiLedBulb = async_wifi_bulb_for_host(host, discovery=discovery)
|
||||||
signal = SIGNAL_STATE_UPDATED.format(device.ipaddr)
|
signal = SIGNAL_STATE_UPDATED.format(device.ipaddr)
|
||||||
|
device.discovery = discovery
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_state_changed(*_: Any) -> None:
|
def _async_state_changed(*_: Any) -> None:
|
||||||
@ -103,23 +107,23 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
) from ex
|
) from ex
|
||||||
|
|
||||||
# UDP probe after successful connect only
|
# UDP probe after successful connect only
|
||||||
if not discovery and (discovery := await async_discover_device(hass, host)):
|
if discovery_cached:
|
||||||
directed_discovery = True
|
if directed_discovery := await async_discover_device(hass, host):
|
||||||
|
device.discovery = discovery = directed_discovery
|
||||||
|
discovery_cached = False
|
||||||
|
|
||||||
if discovery:
|
if entry.unique_id and discovery.get(ATTR_ID):
|
||||||
if entry.unique_id:
|
mac = dr.format_mac(cast(str, discovery[ATTR_ID]))
|
||||||
assert discovery[ATTR_ID] is not None
|
if mac != entry.unique_id:
|
||||||
mac = dr.format_mac(cast(str, discovery[ATTR_ID]))
|
# The device is offline and another flux_led device is now using the ip address
|
||||||
if mac != entry.unique_id:
|
raise ConfigEntryNotReady(
|
||||||
# The device is offline and another flux_led device is now using the ip address
|
f"Unexpected device found at {host}; Expected {entry.unique_id}, found {mac}"
|
||||||
raise ConfigEntryNotReady(
|
)
|
||||||
f"Unexpected device found at {host}; Expected {entry.unique_id}, found {mac}"
|
|
||||||
)
|
if not discovery_cached:
|
||||||
if directed_discovery:
|
# Only update the entry once we have verified the unique id
|
||||||
# Only update the entry once we have verified the unique id
|
# is either missing or we have verified it matches
|
||||||
# is either missing or we have verified it matches
|
async_update_entry_from_discovery(hass, entry, discovery, device.model_num)
|
||||||
async_update_entry_from_discovery(hass, entry, discovery)
|
|
||||||
device.discovery = discovery
|
|
||||||
|
|
||||||
coordinator = FluxLedUpdateCoordinator(hass, device, entry)
|
coordinator = FluxLedUpdateCoordinator(hass, device, entry)
|
||||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
hass.data[DOMAIN][entry.entry_id] = coordinator
|
||||||
|
@ -1,9 +1,17 @@
|
|||||||
"""Config flow for Flux LED/MagicLight."""
|
"""Config flow for Flux LED/MagicLight."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import contextlib
|
||||||
from typing import Any, Final, cast
|
from typing import Any, Final, cast
|
||||||
|
|
||||||
from flux_led.const import ATTR_ID, ATTR_IPADDR, ATTR_MODEL, ATTR_MODEL_DESCRIPTION
|
from flux_led.const import (
|
||||||
|
ATTR_ID,
|
||||||
|
ATTR_IPADDR,
|
||||||
|
ATTR_MODEL,
|
||||||
|
ATTR_MODEL_DESCRIPTION,
|
||||||
|
ATTR_MODEL_INFO,
|
||||||
|
ATTR_VERSION_NUM,
|
||||||
|
)
|
||||||
from flux_led.scanner import FluxLEDDiscovery
|
from flux_led.scanner import FluxLEDDiscovery
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@ -35,6 +43,7 @@ from .discovery import (
|
|||||||
async_populate_data_from_discovery,
|
async_populate_data_from_discovery,
|
||||||
async_update_entry_from_discovery,
|
async_update_entry_from_discovery,
|
||||||
)
|
)
|
||||||
|
from .util import format_as_flux_mac
|
||||||
|
|
||||||
CONF_DEVICE: Final = "device"
|
CONF_DEVICE: Final = "device"
|
||||||
|
|
||||||
@ -60,7 +69,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
self._discovered_device = FluxLEDDiscovery(
|
self._discovered_device = FluxLEDDiscovery(
|
||||||
ipaddr=discovery_info.ip,
|
ipaddr=discovery_info.ip,
|
||||||
model=None,
|
model=None,
|
||||||
id=discovery_info.macaddress.replace(":", ""),
|
id=format_as_flux_mac(discovery_info.macaddress),
|
||||||
model_num=None,
|
model_num=None,
|
||||||
version_num=None,
|
version_num=None,
|
||||||
firmware_date=None,
|
firmware_date=None,
|
||||||
@ -90,7 +99,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
await self.async_set_unique_id(mac)
|
await self.async_set_unique_id(mac)
|
||||||
for entry in self._async_current_entries(include_ignore=False):
|
for entry in self._async_current_entries(include_ignore=False):
|
||||||
if entry.unique_id == mac or entry.data[CONF_HOST] == host:
|
if entry.unique_id == mac or entry.data[CONF_HOST] == host:
|
||||||
if async_update_entry_from_discovery(self.hass, entry, device):
|
if async_update_entry_from_discovery(self.hass, entry, device, None):
|
||||||
self.hass.async_create_task(
|
self.hass.async_create_task(
|
||||||
self.hass.config_entries.async_reload(entry.entry_id)
|
self.hass.config_entries.async_reload(entry.entry_id)
|
||||||
)
|
)
|
||||||
@ -101,9 +110,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
return self.async_abort(reason="already_in_progress")
|
return self.async_abort(reason="already_in_progress")
|
||||||
if not device[ATTR_MODEL_DESCRIPTION]:
|
if not device[ATTR_MODEL_DESCRIPTION]:
|
||||||
try:
|
try:
|
||||||
device = await self._async_try_connect(
|
device = await self._async_try_connect(host, device)
|
||||||
host, device[ATTR_ID], device[ATTR_MODEL]
|
|
||||||
)
|
|
||||||
except FLUX_LED_EXCEPTIONS:
|
except FLUX_LED_EXCEPTIONS:
|
||||||
return self.async_abort(reason="cannot_connect")
|
return self.async_abort(reason="cannot_connect")
|
||||||
else:
|
else:
|
||||||
@ -157,7 +164,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
if not (host := user_input[CONF_HOST]):
|
if not (host := user_input[CONF_HOST]):
|
||||||
return await self.async_step_pick_device()
|
return await self.async_step_pick_device()
|
||||||
try:
|
try:
|
||||||
device = await self._async_try_connect(host, None, None)
|
device = await self._async_try_connect(host, None)
|
||||||
except FLUX_LED_EXCEPTIONS:
|
except FLUX_LED_EXCEPTIONS:
|
||||||
errors["base"] = "cannot_connect"
|
errors["base"] = "cannot_connect"
|
||||||
else:
|
else:
|
||||||
@ -182,7 +189,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
mac = user_input[CONF_DEVICE]
|
mac = user_input[CONF_DEVICE]
|
||||||
await self.async_set_unique_id(mac, raise_on_progress=False)
|
await self.async_set_unique_id(mac, raise_on_progress=False)
|
||||||
return self._async_create_entry_from_device(self._discovered_devices[mac])
|
device = self._discovered_devices[mac]
|
||||||
|
if not device.get(ATTR_MODEL_DESCRIPTION):
|
||||||
|
with contextlib.suppress(*FLUX_LED_EXCEPTIONS):
|
||||||
|
device = await self._async_try_connect(device[ATTR_IPADDR], device)
|
||||||
|
return self._async_create_entry_from_device(device)
|
||||||
|
|
||||||
current_unique_ids = self._async_current_ids()
|
current_unique_ids = self._async_current_ids()
|
||||||
current_hosts = {
|
current_hosts = {
|
||||||
@ -212,7 +223,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def _async_try_connect(
|
async def _async_try_connect(
|
||||||
self, host: str, mac_address: str | None, model: str | None
|
self, host: str, discovery: FluxLEDDiscovery | None
|
||||||
) -> FluxLEDDiscovery:
|
) -> FluxLEDDiscovery:
|
||||||
"""Try to connect."""
|
"""Try to connect."""
|
||||||
self._async_abort_entries_match({CONF_HOST: host})
|
self._async_abort_entries_match({CONF_HOST: host})
|
||||||
@ -226,18 +237,19 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
# AKA `HF-LPB100-ZJ200`
|
# AKA `HF-LPB100-ZJ200`
|
||||||
return device
|
return device
|
||||||
bulb = async_wifi_bulb_for_host(host, discovery=device)
|
bulb = async_wifi_bulb_for_host(host, discovery=device)
|
||||||
|
bulb.discovery = discovery
|
||||||
try:
|
try:
|
||||||
await bulb.async_setup(lambda: None)
|
await bulb.async_setup(lambda: None)
|
||||||
finally:
|
finally:
|
||||||
await bulb.async_stop()
|
await bulb.async_stop()
|
||||||
return FluxLEDDiscovery(
|
return FluxLEDDiscovery(
|
||||||
ipaddr=host,
|
ipaddr=host,
|
||||||
model=model,
|
model=discovery[ATTR_MODEL] if discovery else None,
|
||||||
id=mac_address,
|
id=discovery[ATTR_ID] if discovery else None,
|
||||||
model_num=bulb.model_num,
|
model_num=bulb.model_num,
|
||||||
version_num=None, # This is the minor version number
|
version_num=discovery[ATTR_VERSION_NUM] if discovery else None,
|
||||||
firmware_date=None,
|
firmware_date=None,
|
||||||
model_info=None,
|
model_info=discovery[ATTR_MODEL_INFO] if discovery else None,
|
||||||
model_description=bulb.model_data.description,
|
model_description=bulb.model_data.description,
|
||||||
remote_access_enabled=None,
|
remote_access_enabled=None,
|
||||||
remote_access_host=None,
|
remote_access_host=None,
|
||||||
|
@ -50,6 +50,9 @@ STARTUP_SCAN_TIMEOUT: Final = 5
|
|||||||
DISCOVER_SCAN_TIMEOUT: Final = 10
|
DISCOVER_SCAN_TIMEOUT: Final = 10
|
||||||
|
|
||||||
CONF_MODEL: Final = "model"
|
CONF_MODEL: Final = "model"
|
||||||
|
CONF_MODEL_NUM: Final = "model_num"
|
||||||
|
CONF_MODEL_INFO: Final = "model_info"
|
||||||
|
CONF_MODEL_DESCRIPTION: Final = "model_description"
|
||||||
CONF_MINOR_VERSION: Final = "minor_version"
|
CONF_MINOR_VERSION: Final = "minor_version"
|
||||||
CONF_REMOTE_ACCESS_ENABLED: Final = "remote_access_enabled"
|
CONF_REMOTE_ACCESS_ENABLED: Final = "remote_access_enabled"
|
||||||
CONF_REMOTE_ACCESS_HOST: Final = "remote_access_host"
|
CONF_REMOTE_ACCESS_HOST: Final = "remote_access_host"
|
||||||
|
@ -12,6 +12,8 @@ from flux_led.const import (
|
|||||||
ATTR_IPADDR,
|
ATTR_IPADDR,
|
||||||
ATTR_MODEL,
|
ATTR_MODEL,
|
||||||
ATTR_MODEL_DESCRIPTION,
|
ATTR_MODEL_DESCRIPTION,
|
||||||
|
ATTR_MODEL_INFO,
|
||||||
|
ATTR_MODEL_NUM,
|
||||||
ATTR_REMOTE_ACCESS_ENABLED,
|
ATTR_REMOTE_ACCESS_ENABLED,
|
||||||
ATTR_REMOTE_ACCESS_HOST,
|
ATTR_REMOTE_ACCESS_HOST,
|
||||||
ATTR_REMOTE_ACCESS_PORT,
|
ATTR_REMOTE_ACCESS_PORT,
|
||||||
@ -21,6 +23,7 @@ from flux_led.scanner import FluxLEDDiscovery
|
|||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components import network
|
from homeassistant.components import network
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_HOST, CONF_NAME
|
from homeassistant.const import CONF_HOST, CONF_NAME
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr
|
||||||
@ -29,6 +32,9 @@ from homeassistant.util.network import is_ip_address
|
|||||||
from .const import (
|
from .const import (
|
||||||
CONF_MINOR_VERSION,
|
CONF_MINOR_VERSION,
|
||||||
CONF_MODEL,
|
CONF_MODEL,
|
||||||
|
CONF_MODEL_DESCRIPTION,
|
||||||
|
CONF_MODEL_INFO,
|
||||||
|
CONF_MODEL_NUM,
|
||||||
CONF_REMOTE_ACCESS_ENABLED,
|
CONF_REMOTE_ACCESS_ENABLED,
|
||||||
CONF_REMOTE_ACCESS_HOST,
|
CONF_REMOTE_ACCESS_HOST,
|
||||||
CONF_REMOTE_ACCESS_PORT,
|
CONF_REMOTE_ACCESS_PORT,
|
||||||
@ -36,6 +42,7 @@ from .const import (
|
|||||||
DOMAIN,
|
DOMAIN,
|
||||||
FLUX_LED_DISCOVERY,
|
FLUX_LED_DISCOVERY,
|
||||||
)
|
)
|
||||||
|
from .util import format_as_flux_mac
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -47,9 +54,31 @@ CONF_TO_DISCOVERY: Final = {
|
|||||||
CONF_REMOTE_ACCESS_PORT: ATTR_REMOTE_ACCESS_PORT,
|
CONF_REMOTE_ACCESS_PORT: ATTR_REMOTE_ACCESS_PORT,
|
||||||
CONF_MINOR_VERSION: ATTR_VERSION_NUM,
|
CONF_MINOR_VERSION: ATTR_VERSION_NUM,
|
||||||
CONF_MODEL: ATTR_MODEL,
|
CONF_MODEL: ATTR_MODEL,
|
||||||
|
CONF_MODEL_NUM: ATTR_MODEL_NUM,
|
||||||
|
CONF_MODEL_INFO: ATTR_MODEL_INFO,
|
||||||
|
CONF_MODEL_DESCRIPTION: ATTR_MODEL_DESCRIPTION,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_build_cached_discovery(entry: ConfigEntry) -> FluxLEDDiscovery:
|
||||||
|
"""When discovery is unavailable, load it from the config entry."""
|
||||||
|
data = entry.data
|
||||||
|
return FluxLEDDiscovery(
|
||||||
|
ipaddr=data[CONF_HOST],
|
||||||
|
model=data.get(CONF_MODEL),
|
||||||
|
id=format_as_flux_mac(entry.unique_id),
|
||||||
|
model_num=data.get(CONF_MODEL_NUM),
|
||||||
|
version_num=data.get(CONF_MINOR_VERSION),
|
||||||
|
firmware_date=None,
|
||||||
|
model_info=data.get(CONF_MODEL_INFO),
|
||||||
|
model_description=data.get(CONF_MODEL_DESCRIPTION),
|
||||||
|
remote_access_enabled=data.get(CONF_REMOTE_ACCESS_ENABLED),
|
||||||
|
remote_access_host=data.get(CONF_REMOTE_ACCESS_HOST),
|
||||||
|
remote_access_port=data.get(CONF_REMOTE_ACCESS_PORT),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_name_from_discovery(device: FluxLEDDiscovery) -> str:
|
def async_name_from_discovery(device: FluxLEDDiscovery) -> str:
|
||||||
"""Convert a flux_led discovery to a human readable name."""
|
"""Convert a flux_led discovery to a human readable name."""
|
||||||
@ -72,6 +101,8 @@ def async_populate_data_from_discovery(
|
|||||||
for conf_key, discovery_key in CONF_TO_DISCOVERY.items():
|
for conf_key, discovery_key in CONF_TO_DISCOVERY.items():
|
||||||
if (
|
if (
|
||||||
device.get(discovery_key) is not None
|
device.get(discovery_key) is not None
|
||||||
|
and conf_key
|
||||||
|
not in data_updates # Prefer the model num from TCP instead of UDP
|
||||||
and current_data.get(conf_key) != device[discovery_key] # type: ignore[misc]
|
and current_data.get(conf_key) != device[discovery_key] # type: ignore[misc]
|
||||||
):
|
):
|
||||||
data_updates[conf_key] = device[discovery_key] # type: ignore[misc]
|
data_updates[conf_key] = device[discovery_key] # type: ignore[misc]
|
||||||
@ -79,7 +110,10 @@ def async_populate_data_from_discovery(
|
|||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_update_entry_from_discovery(
|
def async_update_entry_from_discovery(
|
||||||
hass: HomeAssistant, entry: config_entries.ConfigEntry, device: FluxLEDDiscovery
|
hass: HomeAssistant,
|
||||||
|
entry: config_entries.ConfigEntry,
|
||||||
|
device: FluxLEDDiscovery,
|
||||||
|
model_num: int | None,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Update a config entry from a flux_led discovery."""
|
"""Update a config entry from a flux_led discovery."""
|
||||||
data_updates: dict[str, Any] = {}
|
data_updates: dict[str, Any] = {}
|
||||||
@ -88,6 +122,8 @@ def async_update_entry_from_discovery(
|
|||||||
updates: dict[str, Any] = {}
|
updates: dict[str, Any] = {}
|
||||||
if not entry.unique_id:
|
if not entry.unique_id:
|
||||||
updates["unique_id"] = dr.format_mac(mac_address)
|
updates["unique_id"] = dr.format_mac(mac_address)
|
||||||
|
if model_num and entry.data.get(CONF_MODEL_NUM) != model_num:
|
||||||
|
data_updates[CONF_MODEL_NUM] = model_num
|
||||||
async_populate_data_from_discovery(entry.data, data_updates, device)
|
async_populate_data_from_discovery(entry.data, data_updates, device)
|
||||||
if not entry.data.get(CONF_NAME) or is_ip_address(entry.data[CONF_NAME]):
|
if not entry.data.get(CONF_NAME) or is_ip_address(entry.data[CONF_NAME]):
|
||||||
updates["title"] = data_updates[CONF_NAME] = async_name_from_discovery(device)
|
updates["title"] = data_updates[CONF_NAME] = async_name_from_discovery(device)
|
||||||
|
@ -24,7 +24,7 @@ def _async_device_info(
|
|||||||
version_num = device.version_num
|
version_num = device.version_num
|
||||||
if minor_version := entry.data.get(CONF_MINOR_VERSION):
|
if minor_version := entry.data.get(CONF_MINOR_VERSION):
|
||||||
sw_version = version_num + int(hex(minor_version)[2:]) / 100
|
sw_version = version_num + int(hex(minor_version)[2:]) / 100
|
||||||
sw_version_str = f"{sw_version:0.3f}"
|
sw_version_str = f"{sw_version:0.2f}"
|
||||||
else:
|
else:
|
||||||
sw_version_str = str(device.version_num)
|
sw_version_str = str(device.version_num)
|
||||||
return DeviceInfo(
|
return DeviceInfo(
|
||||||
|
@ -18,6 +18,11 @@ def _hass_color_modes(device: AIOWifiLedBulb) -> set[str]:
|
|||||||
return {_flux_color_mode_to_hass(mode, color_modes) for mode in color_modes}
|
return {_flux_color_mode_to_hass(mode, color_modes) for mode in color_modes}
|
||||||
|
|
||||||
|
|
||||||
|
def format_as_flux_mac(mac: str | None) -> str | None:
|
||||||
|
"""Convert a device registry formatted mac to flux mac."""
|
||||||
|
return None if mac is None else mac.replace(":", "").upper()
|
||||||
|
|
||||||
|
|
||||||
def _flux_color_mode_to_hass(
|
def _flux_color_mode_to_hass(
|
||||||
flux_color_mode: str | None, flux_color_modes: set[str]
|
flux_color_mode: str | None, flux_color_modes: set[str]
|
||||||
) -> str:
|
) -> str:
|
||||||
|
@ -24,11 +24,12 @@ MODULE = "homeassistant.components.flux_led"
|
|||||||
MODULE_CONFIG_FLOW = "homeassistant.components.flux_led.config_flow"
|
MODULE_CONFIG_FLOW = "homeassistant.components.flux_led.config_flow"
|
||||||
IP_ADDRESS = "127.0.0.1"
|
IP_ADDRESS = "127.0.0.1"
|
||||||
MODEL_NUM_HEX = "0x35"
|
MODEL_NUM_HEX = "0x35"
|
||||||
|
MODEL_NUM = 0x35
|
||||||
MODEL = "AZ120444"
|
MODEL = "AZ120444"
|
||||||
MODEL_DESCRIPTION = "Bulb RGBCW"
|
MODEL_DESCRIPTION = "Bulb RGBCW"
|
||||||
MAC_ADDRESS = "aa:bb:cc:dd:ee:ff"
|
MAC_ADDRESS = "aa:bb:cc:dd:ee:ff"
|
||||||
FLUX_MAC_ADDRESS = "aabbccddeeff"
|
FLUX_MAC_ADDRESS = "AABBCCDDEEFF"
|
||||||
SHORT_MAC_ADDRESS = "ddeeff"
|
SHORT_MAC_ADDRESS = "DDEEFF"
|
||||||
|
|
||||||
DEFAULT_ENTRY_TITLE = f"{MODEL_DESCRIPTION} {SHORT_MAC_ADDRESS}"
|
DEFAULT_ENTRY_TITLE = f"{MODEL_DESCRIPTION} {SHORT_MAC_ADDRESS}"
|
||||||
|
|
||||||
@ -52,7 +53,7 @@ FLUX_DISCOVERY = FluxLEDDiscovery(
|
|||||||
ipaddr=IP_ADDRESS,
|
ipaddr=IP_ADDRESS,
|
||||||
model=MODEL,
|
model=MODEL,
|
||||||
id=FLUX_MAC_ADDRESS,
|
id=FLUX_MAC_ADDRESS,
|
||||||
model_num=0x25,
|
model_num=MODEL_NUM,
|
||||||
version_num=0x04,
|
version_num=0x04,
|
||||||
firmware_date=datetime.date(2021, 5, 5),
|
firmware_date=datetime.date(2021, 5, 5),
|
||||||
model_info=MODEL,
|
model_info=MODEL,
|
||||||
@ -80,6 +81,16 @@ def _mocked_bulb() -> AIOWifiLedBulb:
|
|||||||
bulb.async_set_effect = AsyncMock()
|
bulb.async_set_effect = AsyncMock()
|
||||||
bulb.async_set_white_temp = AsyncMock()
|
bulb.async_set_white_temp = AsyncMock()
|
||||||
bulb.async_set_brightness = AsyncMock()
|
bulb.async_set_brightness = AsyncMock()
|
||||||
|
bulb.pixels_per_segment = 300
|
||||||
|
bulb.segments = 2
|
||||||
|
bulb.music_pixels_per_segment = 150
|
||||||
|
bulb.music_segments = 4
|
||||||
|
bulb.operating_mode = "RGB&W"
|
||||||
|
bulb.operating_modes = ["RGB&W", "RGB/W"]
|
||||||
|
bulb.wirings = ["RGBW", "GRBW", "BGRW"]
|
||||||
|
bulb.wiring = "BGRW"
|
||||||
|
bulb.ic_types = ["WS2812B", "UCS1618"]
|
||||||
|
bulb.ic_type = "WS2812B"
|
||||||
bulb.async_stop = AsyncMock()
|
bulb.async_stop = AsyncMock()
|
||||||
bulb.async_update = AsyncMock()
|
bulb.async_update = AsyncMock()
|
||||||
bulb.async_turn_off = AsyncMock()
|
bulb.async_turn_off = AsyncMock()
|
||||||
@ -102,8 +113,8 @@ def _mocked_bulb() -> AIOWifiLedBulb:
|
|||||||
bulb.color_temp = 2700
|
bulb.color_temp = 2700
|
||||||
bulb.getWhiteTemperature = MagicMock(return_value=(2700, 128))
|
bulb.getWhiteTemperature = MagicMock(return_value=(2700, 128))
|
||||||
bulb.brightness = 128
|
bulb.brightness = 128
|
||||||
bulb.model_num = 0x35
|
bulb.model_num = MODEL_NUM
|
||||||
bulb.model_data = MODEL_MAP[0x35]
|
bulb.model_data = MODEL_MAP[MODEL_NUM]
|
||||||
bulb.effect = None
|
bulb.effect = None
|
||||||
bulb.speed = 50
|
bulb.speed = 50
|
||||||
bulb.model = "Bulb RGBCW (0x35)"
|
bulb.model = "Bulb RGBCW (0x35)"
|
||||||
|
@ -13,6 +13,9 @@ from homeassistant.components.flux_led.const import (
|
|||||||
CONF_CUSTOM_EFFECT_TRANSITION,
|
CONF_CUSTOM_EFFECT_TRANSITION,
|
||||||
CONF_MINOR_VERSION,
|
CONF_MINOR_VERSION,
|
||||||
CONF_MODEL,
|
CONF_MODEL,
|
||||||
|
CONF_MODEL_DESCRIPTION,
|
||||||
|
CONF_MODEL_INFO,
|
||||||
|
CONF_MODEL_NUM,
|
||||||
CONF_REMOTE_ACCESS_ENABLED,
|
CONF_REMOTE_ACCESS_ENABLED,
|
||||||
CONF_REMOTE_ACCESS_HOST,
|
CONF_REMOTE_ACCESS_HOST,
|
||||||
CONF_REMOTE_ACCESS_PORT,
|
CONF_REMOTE_ACCESS_PORT,
|
||||||
@ -32,6 +35,8 @@ from . import (
|
|||||||
IP_ADDRESS,
|
IP_ADDRESS,
|
||||||
MAC_ADDRESS,
|
MAC_ADDRESS,
|
||||||
MODEL,
|
MODEL,
|
||||||
|
MODEL_DESCRIPTION,
|
||||||
|
MODEL_NUM,
|
||||||
MODULE,
|
MODULE,
|
||||||
_patch_discovery,
|
_patch_discovery,
|
||||||
_patch_wifibulb,
|
_patch_wifibulb,
|
||||||
@ -91,6 +96,85 @@ async def test_discovery(hass: HomeAssistant):
|
|||||||
CONF_HOST: IP_ADDRESS,
|
CONF_HOST: IP_ADDRESS,
|
||||||
CONF_NAME: DEFAULT_ENTRY_TITLE,
|
CONF_NAME: DEFAULT_ENTRY_TITLE,
|
||||||
CONF_MODEL: MODEL,
|
CONF_MODEL: MODEL,
|
||||||
|
CONF_MODEL_NUM: MODEL_NUM,
|
||||||
|
CONF_MODEL_INFO: MODEL,
|
||||||
|
CONF_MODEL_DESCRIPTION: MODEL_DESCRIPTION,
|
||||||
|
CONF_REMOTE_ACCESS_ENABLED: True,
|
||||||
|
CONF_REMOTE_ACCESS_HOST: "the.cloud",
|
||||||
|
CONF_REMOTE_ACCESS_PORT: 8816,
|
||||||
|
CONF_MINOR_VERSION: 0x04,
|
||||||
|
}
|
||||||
|
mock_setup.assert_called_once()
|
||||||
|
mock_setup_entry.assert_called_once()
|
||||||
|
|
||||||
|
# ignore configured devices
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert not result["errors"]
|
||||||
|
|
||||||
|
with _patch_discovery(), _patch_wifibulb():
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result2["type"] == "abort"
|
||||||
|
assert result2["reason"] == "no_devices_found"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_discovery_legacy(hass: HomeAssistant):
|
||||||
|
"""Test setting up discovery with a legacy device."""
|
||||||
|
with _patch_discovery(device=FLUX_DISCOVERY_PARTIAL), _patch_wifibulb():
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert not result["errors"]
|
||||||
|
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert result2["type"] == "form"
|
||||||
|
assert result2["step_id"] == "pick_device"
|
||||||
|
assert not result2["errors"]
|
||||||
|
|
||||||
|
# test we can try again
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert not result["errors"]
|
||||||
|
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert result2["type"] == "form"
|
||||||
|
assert result2["step_id"] == "pick_device"
|
||||||
|
assert not result2["errors"]
|
||||||
|
|
||||||
|
with _patch_discovery(), _patch_wifibulb(), patch(
|
||||||
|
f"{MODULE}.async_setup", return_value=True
|
||||||
|
) as mock_setup, patch(
|
||||||
|
f"{MODULE}.async_setup_entry", return_value=True
|
||||||
|
) as mock_setup_entry:
|
||||||
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{CONF_DEVICE: MAC_ADDRESS},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result3["type"] == "create_entry"
|
||||||
|
assert result3["title"] == DEFAULT_ENTRY_TITLE
|
||||||
|
assert result3["data"] == {
|
||||||
|
CONF_MINOR_VERSION: 4,
|
||||||
|
CONF_HOST: IP_ADDRESS,
|
||||||
|
CONF_NAME: DEFAULT_ENTRY_TITLE,
|
||||||
|
CONF_MODEL: MODEL,
|
||||||
|
CONF_MODEL_NUM: MODEL_NUM,
|
||||||
|
CONF_MODEL_INFO: MODEL,
|
||||||
|
CONF_MODEL_DESCRIPTION: MODEL_DESCRIPTION,
|
||||||
CONF_REMOTE_ACCESS_ENABLED: True,
|
CONF_REMOTE_ACCESS_ENABLED: True,
|
||||||
CONF_REMOTE_ACCESS_HOST: "the.cloud",
|
CONF_REMOTE_ACCESS_HOST: "the.cloud",
|
||||||
CONF_REMOTE_ACCESS_PORT: 8816,
|
CONF_REMOTE_ACCESS_PORT: 8816,
|
||||||
@ -171,6 +255,9 @@ async def test_discovery_with_existing_device_present(hass: HomeAssistant):
|
|||||||
CONF_HOST: IP_ADDRESS,
|
CONF_HOST: IP_ADDRESS,
|
||||||
CONF_NAME: DEFAULT_ENTRY_TITLE,
|
CONF_NAME: DEFAULT_ENTRY_TITLE,
|
||||||
CONF_MODEL: MODEL,
|
CONF_MODEL: MODEL,
|
||||||
|
CONF_MODEL_NUM: MODEL_NUM,
|
||||||
|
CONF_MODEL_INFO: MODEL,
|
||||||
|
CONF_MODEL_DESCRIPTION: MODEL_DESCRIPTION,
|
||||||
CONF_REMOTE_ACCESS_ENABLED: True,
|
CONF_REMOTE_ACCESS_ENABLED: True,
|
||||||
CONF_REMOTE_ACCESS_HOST: "the.cloud",
|
CONF_REMOTE_ACCESS_HOST: "the.cloud",
|
||||||
CONF_REMOTE_ACCESS_PORT: 8816,
|
CONF_REMOTE_ACCESS_PORT: 8816,
|
||||||
@ -245,6 +332,9 @@ async def test_manual_working_discovery(hass: HomeAssistant):
|
|||||||
CONF_HOST: IP_ADDRESS,
|
CONF_HOST: IP_ADDRESS,
|
||||||
CONF_NAME: DEFAULT_ENTRY_TITLE,
|
CONF_NAME: DEFAULT_ENTRY_TITLE,
|
||||||
CONF_MODEL: MODEL,
|
CONF_MODEL: MODEL,
|
||||||
|
CONF_MODEL_NUM: MODEL_NUM,
|
||||||
|
CONF_MODEL_INFO: MODEL,
|
||||||
|
CONF_MODEL_DESCRIPTION: MODEL_DESCRIPTION,
|
||||||
CONF_REMOTE_ACCESS_ENABLED: True,
|
CONF_REMOTE_ACCESS_ENABLED: True,
|
||||||
CONF_REMOTE_ACCESS_HOST: "the.cloud",
|
CONF_REMOTE_ACCESS_HOST: "the.cloud",
|
||||||
CONF_REMOTE_ACCESS_PORT: 8816,
|
CONF_REMOTE_ACCESS_PORT: 8816,
|
||||||
@ -283,7 +373,12 @@ async def test_manual_no_discovery_data(hass: HomeAssistant):
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert result["type"] == "create_entry"
|
assert result["type"] == "create_entry"
|
||||||
assert result["data"] == {CONF_HOST: IP_ADDRESS, CONF_NAME: IP_ADDRESS}
|
assert result["data"] == {
|
||||||
|
CONF_HOST: IP_ADDRESS,
|
||||||
|
CONF_MODEL_NUM: MODEL_NUM,
|
||||||
|
CONF_MODEL_DESCRIPTION: MODEL_DESCRIPTION,
|
||||||
|
CONF_NAME: IP_ADDRESS,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_discovered_by_discovery_and_dhcp(hass):
|
async def test_discovered_by_discovery_and_dhcp(hass):
|
||||||
@ -352,6 +447,9 @@ async def test_discovered_by_discovery(hass):
|
|||||||
CONF_HOST: IP_ADDRESS,
|
CONF_HOST: IP_ADDRESS,
|
||||||
CONF_NAME: DEFAULT_ENTRY_TITLE,
|
CONF_NAME: DEFAULT_ENTRY_TITLE,
|
||||||
CONF_MODEL: MODEL,
|
CONF_MODEL: MODEL,
|
||||||
|
CONF_MODEL_NUM: MODEL_NUM,
|
||||||
|
CONF_MODEL_INFO: MODEL,
|
||||||
|
CONF_MODEL_DESCRIPTION: MODEL_DESCRIPTION,
|
||||||
CONF_REMOTE_ACCESS_ENABLED: True,
|
CONF_REMOTE_ACCESS_ENABLED: True,
|
||||||
CONF_REMOTE_ACCESS_HOST: "the.cloud",
|
CONF_REMOTE_ACCESS_HOST: "the.cloud",
|
||||||
CONF_REMOTE_ACCESS_PORT: 8816,
|
CONF_REMOTE_ACCESS_PORT: 8816,
|
||||||
@ -387,6 +485,9 @@ async def test_discovered_by_dhcp_udp_responds(hass):
|
|||||||
CONF_HOST: IP_ADDRESS,
|
CONF_HOST: IP_ADDRESS,
|
||||||
CONF_NAME: DEFAULT_ENTRY_TITLE,
|
CONF_NAME: DEFAULT_ENTRY_TITLE,
|
||||||
CONF_MODEL: MODEL,
|
CONF_MODEL: MODEL,
|
||||||
|
CONF_MODEL_NUM: MODEL_NUM,
|
||||||
|
CONF_MODEL_INFO: MODEL,
|
||||||
|
CONF_MODEL_DESCRIPTION: MODEL_DESCRIPTION,
|
||||||
CONF_REMOTE_ACCESS_ENABLED: True,
|
CONF_REMOTE_ACCESS_ENABLED: True,
|
||||||
CONF_REMOTE_ACCESS_HOST: "the.cloud",
|
CONF_REMOTE_ACCESS_HOST: "the.cloud",
|
||||||
CONF_REMOTE_ACCESS_PORT: 8816,
|
CONF_REMOTE_ACCESS_PORT: 8816,
|
||||||
@ -419,6 +520,8 @@ async def test_discovered_by_dhcp_no_udp_response(hass):
|
|||||||
assert result2["type"] == "create_entry"
|
assert result2["type"] == "create_entry"
|
||||||
assert result2["data"] == {
|
assert result2["data"] == {
|
||||||
CONF_HOST: IP_ADDRESS,
|
CONF_HOST: IP_ADDRESS,
|
||||||
|
CONF_MODEL_NUM: MODEL_NUM,
|
||||||
|
CONF_MODEL_DESCRIPTION: MODEL_DESCRIPTION,
|
||||||
CONF_NAME: DEFAULT_ENTRY_TITLE,
|
CONF_NAME: DEFAULT_ENTRY_TITLE,
|
||||||
}
|
}
|
||||||
assert mock_async_setup.called
|
assert mock_async_setup.called
|
||||||
@ -448,6 +551,8 @@ async def test_discovered_by_dhcp_partial_udp_response_fallback_tcp(hass):
|
|||||||
assert result2["type"] == "create_entry"
|
assert result2["type"] == "create_entry"
|
||||||
assert result2["data"] == {
|
assert result2["data"] == {
|
||||||
CONF_HOST: IP_ADDRESS,
|
CONF_HOST: IP_ADDRESS,
|
||||||
|
CONF_MODEL_NUM: MODEL_NUM,
|
||||||
|
CONF_MODEL_DESCRIPTION: MODEL_DESCRIPTION,
|
||||||
CONF_NAME: DEFAULT_ENTRY_TITLE,
|
CONF_NAME: DEFAULT_ENTRY_TITLE,
|
||||||
}
|
}
|
||||||
assert mock_async_setup.called
|
assert mock_async_setup.called
|
||||||
|
Loading…
x
Reference in New Issue
Block a user