mirror of
https://github.com/home-assistant/core.git
synced 2025-08-03 18:48:22 +00:00
Merge branch 'dev' into block_pyserial_asyncio
This commit is contained in:
commit
264df97069
@ -10,16 +10,14 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.SENSOR]
|
||||
|
||||
AfterShipConfigEntry = ConfigEntry[AfterShip]
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: AfterShipConfigEntry) -> bool:
|
||||
"""Set up AfterShip from a config entry."""
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
|
||||
session = async_get_clientsession(hass)
|
||||
aftership = AfterShip(api_key=entry.data[CONF_API_KEY], session=session)
|
||||
|
||||
@ -28,7 +26,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
except AfterShipException as err:
|
||||
raise ConfigEntryNotReady from err
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id] = aftership
|
||||
entry.runtime_data = aftership
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
@ -37,7 +35,4 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
@ -8,7 +8,6 @@ from typing import Any, Final
|
||||
from pyaftership import AfterShip, AfterShipException
|
||||
|
||||
from homeassistant.components.sensor import SensorEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
@ -18,6 +17,7 @@ from homeassistant.helpers.dispatcher import (
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
from . import AfterShipConfigEntry
|
||||
from .const import (
|
||||
ADD_TRACKING_SERVICE_SCHEMA,
|
||||
ATTR_TRACKINGS,
|
||||
@ -41,11 +41,11 @@ PLATFORM_SCHEMA: Final = cv.removed(DOMAIN, raise_if_present=False)
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: AfterShipConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up AfterShip sensor entities based on a config entry."""
|
||||
aftership: AfterShip = hass.data[DOMAIN][config_entry.entry_id]
|
||||
aftership = config_entry.runtime_data
|
||||
|
||||
async_add_entities([AfterShipSensor(aftership, config_entry.title)], True)
|
||||
|
||||
|
@ -15,14 +15,16 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DOMAIN
|
||||
from .const import DOMAIN # noqa: F401
|
||||
from .coordinator import AirNowDataUpdateCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
PLATFORMS = [Platform.SENSOR]
|
||||
|
||||
AirNowConfigEntry = ConfigEntry[AirNowDataUpdateCoordinator]
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: AirNowConfigEntry) -> bool:
|
||||
"""Set up AirNow from a config entry."""
|
||||
api_key = entry.data[CONF_API_KEY]
|
||||
latitude = entry.data[CONF_LATITUDE]
|
||||
@ -44,8 +46,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
# Store Entity and Initialize Platforms
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
# Listen for option changes
|
||||
entry.async_on_unload(entry.add_update_listener(update_listener))
|
||||
@ -87,14 +88,9 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: AirNowConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
|
@ -5,7 +5,6 @@ from __future__ import annotations
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_API_KEY,
|
||||
CONF_LATITUDE,
|
||||
@ -14,8 +13,7 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import AirNowDataUpdateCoordinator
|
||||
from .const import DOMAIN
|
||||
from . import AirNowConfigEntry
|
||||
|
||||
ATTR_LATITUDE_CAP = "Latitude"
|
||||
ATTR_LONGITUDE_CAP = "Longitude"
|
||||
@ -40,10 +38,10 @@ TO_REDACT = {
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, entry: ConfigEntry
|
||||
hass: HomeAssistant, entry: AirNowConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
coordinator: AirNowDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
return async_redact_data(
|
||||
{
|
||||
|
@ -13,7 +13,6 @@ from homeassistant.components.sensor import (
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_TIME,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
@ -26,7 +25,7 @@ from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.util.dt import get_time_zone
|
||||
|
||||
from . import AirNowDataUpdateCoordinator
|
||||
from . import AirNowConfigEntry, AirNowDataUpdateCoordinator
|
||||
from .const import (
|
||||
ATTR_API_AQI,
|
||||
ATTR_API_AQI_DESCRIPTION,
|
||||
@ -116,11 +115,11 @@ SENSOR_TYPES: tuple[AirNowEntityDescription, ...] = (
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: AirNowConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up AirNow sensor entities based on a config entry."""
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
entities = [AirNowSensor(coordinator, description) for description in SENSOR_TYPES]
|
||||
|
||||
|
@ -6,13 +6,14 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import AirQCoordinator
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.SENSOR]
|
||||
|
||||
AirQConfigEntry = ConfigEntry[AirQCoordinator]
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: AirQConfigEntry) -> bool:
|
||||
"""Set up air-Q from a config entry."""
|
||||
|
||||
coordinator = AirQCoordinator(hass, entry)
|
||||
@ -20,18 +21,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
# Query the device for the first time and initialise coordinator.data
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
# Record the coordinator in a global store
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: AirQConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
@ -13,7 +13,6 @@ from homeassistant.components.sensor import (
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
|
||||
@ -28,11 +27,10 @@ from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import AirQCoordinator
|
||||
from . import AirQConfigEntry, AirQCoordinator
|
||||
from .const import (
|
||||
ACTIVITY_BECQUEREL_PER_CUBIC_METER,
|
||||
CONCENTRATION_GRAMS_PER_CUBIC_METER,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -400,12 +398,12 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigEntry,
|
||||
entry: AirQConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up sensor entities based on a config entry."""
|
||||
|
||||
coordinator = hass.data[DOMAIN][config.entry_id]
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
entities: list[AirQSensor] = []
|
||||
|
||||
|
@ -22,11 +22,11 @@ SCAN_INTERVAL = timedelta(minutes=6)
|
||||
|
||||
AirthingsDataCoordinatorType = DataUpdateCoordinator[dict[str, AirthingsDevice]]
|
||||
|
||||
AirthingsConfigEntry = ConfigEntry[AirthingsDataCoordinatorType]
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: AirthingsConfigEntry) -> bool:
|
||||
"""Set up Airthings from a config entry."""
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
|
||||
airthings = Airthings(
|
||||
entry.data[CONF_ID],
|
||||
entry.data[CONF_SECRET],
|
||||
@ -49,17 +49,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: AirthingsConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
@ -10,7 +10,6 @@ from homeassistant.components.sensor import (
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_BILLION,
|
||||
@ -27,7 +26,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import AirthingsDataCoordinatorType
|
||||
from . import AirthingsConfigEntry, AirthingsDataCoordinatorType
|
||||
from .const import DOMAIN
|
||||
|
||||
SENSORS: dict[str, SensorEntityDescription] = {
|
||||
@ -102,12 +101,12 @@ SENSORS: dict[str, SensorEntityDescription] = {
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: AirthingsConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Airthings sensor."""
|
||||
|
||||
coordinator: AirthingsDataCoordinatorType = hass.data[DOMAIN][entry.entry_id]
|
||||
coordinator = entry.runtime_data
|
||||
entities = [
|
||||
AirthingsHeaterEnergySensor(
|
||||
coordinator,
|
||||
|
@ -13,8 +13,10 @@ from .const import DOMAIN
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.CLIMATE]
|
||||
|
||||
Airtouch5ConfigEntry = ConfigEntry[Airtouch5SimpleClient]
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: Airtouch5ConfigEntry) -> bool:
|
||||
"""Set up Airtouch 5 from a config entry."""
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
@ -30,22 +32,21 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
raise ConfigEntryNotReady from t
|
||||
|
||||
# Store an API object for your platforms to access
|
||||
hass.data[DOMAIN][entry.entry_id] = client
|
||||
entry.runtime_data = client
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: Airtouch5ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
client: Airtouch5SimpleClient = hass.data[DOMAIN][entry.entry_id]
|
||||
client = entry.runtime_data
|
||||
await client.disconnect()
|
||||
client.ac_status_callbacks.clear()
|
||||
client.connection_state_callbacks.clear()
|
||||
client.data_packet_callbacks.clear()
|
||||
client.zone_status_callbacks.clear()
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
|
@ -34,12 +34,12 @@ from homeassistant.components.climate import (
|
||||
ClimateEntityFeature,
|
||||
HVACMode,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import Airtouch5ConfigEntry
|
||||
from .const import DOMAIN, FAN_INTELLIGENT_AUTO, FAN_TURBO
|
||||
from .entity import Airtouch5Entity
|
||||
|
||||
@ -92,11 +92,11 @@ FAN_MODE_TO_SET_AC_FAN_SPEED = {
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: Airtouch5ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Airtouch 5 Climate entities."""
|
||||
client: Airtouch5SimpleClient = hass.data[DOMAIN][config_entry.entry_id]
|
||||
client = config_entry.runtime_data
|
||||
|
||||
entities: list[ClimateEntity] = []
|
||||
|
||||
|
@ -8,7 +8,7 @@ from collections.abc import Callable, Collection, Mapping
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.const import ATTR_ASSUMED_STATE, ATTR_ENTITY_ID, STATE_ON
|
||||
from homeassistant.const import ATTR_ASSUMED_STATE, ATTR_ENTITY_ID, STATE_OFF, STATE_ON
|
||||
from homeassistant.core import (
|
||||
CALLBACK_TYPE,
|
||||
Event,
|
||||
@ -24,7 +24,7 @@ from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.event import async_track_state_change_event
|
||||
|
||||
from .const import ATTR_AUTO, ATTR_ORDER, DOMAIN, GROUP_ORDER, REG_KEY
|
||||
from .registry import GroupIntegrationRegistry
|
||||
from .registry import GroupIntegrationRegistry, SingleStateType
|
||||
|
||||
ENTITY_ID_FORMAT = DOMAIN + ".{}"
|
||||
|
||||
@ -133,6 +133,7 @@ class Group(Entity):
|
||||
_attr_should_poll = False
|
||||
tracking: tuple[str, ...]
|
||||
trackable: tuple[str, ...]
|
||||
single_state_type_key: SingleStateType | None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@ -153,7 +154,7 @@ class Group(Entity):
|
||||
self._attr_name = name
|
||||
self._state: str | None = None
|
||||
self._attr_icon = icon
|
||||
self._set_tracked(entity_ids)
|
||||
self._entity_ids = entity_ids
|
||||
self._on_off: dict[str, bool] = {}
|
||||
self._assumed: dict[str, bool] = {}
|
||||
self._on_states: set[str] = set()
|
||||
@ -287,6 +288,7 @@ class Group(Entity):
|
||||
if not entity_ids:
|
||||
self.tracking = ()
|
||||
self.trackable = ()
|
||||
self.single_state_type_key = None
|
||||
return
|
||||
|
||||
registry: GroupIntegrationRegistry = self.hass.data[REG_KEY]
|
||||
@ -294,16 +296,42 @@ class Group(Entity):
|
||||
|
||||
tracking: list[str] = []
|
||||
trackable: list[str] = []
|
||||
single_state_type_set: set[SingleStateType] = set()
|
||||
for ent_id in entity_ids:
|
||||
ent_id_lower = ent_id.lower()
|
||||
domain = split_entity_id(ent_id_lower)[0]
|
||||
tracking.append(ent_id_lower)
|
||||
if domain not in excluded_domains:
|
||||
trackable.append(ent_id_lower)
|
||||
if domain in registry.state_group_mapping:
|
||||
single_state_type_set.add(registry.state_group_mapping[domain])
|
||||
elif domain == DOMAIN:
|
||||
# If a group contains another group we check if that group
|
||||
# has a specific single state type
|
||||
if ent_id in registry.state_group_mapping:
|
||||
single_state_type_set.add(registry.state_group_mapping[ent_id])
|
||||
else:
|
||||
single_state_type_set.add(SingleStateType(STATE_ON, STATE_OFF))
|
||||
|
||||
if len(single_state_type_set) == 1:
|
||||
self.single_state_type_key = next(iter(single_state_type_set))
|
||||
# To support groups with nested groups we store the state type
|
||||
# per group entity_id if there is a single state type
|
||||
registry.state_group_mapping[self.entity_id] = self.single_state_type_key
|
||||
else:
|
||||
self.single_state_type_key = None
|
||||
self.async_on_remove(self._async_deregister)
|
||||
|
||||
self.trackable = tuple(trackable)
|
||||
self.tracking = tuple(tracking)
|
||||
|
||||
@callback
|
||||
def _async_deregister(self) -> None:
|
||||
"""Deregister group entity from the registry."""
|
||||
registry: GroupIntegrationRegistry = self.hass.data[REG_KEY]
|
||||
if self.entity_id in registry.state_group_mapping:
|
||||
registry.state_group_mapping.pop(self.entity_id)
|
||||
|
||||
@callback
|
||||
def _async_start(self, _: HomeAssistant | None = None) -> None:
|
||||
"""Start tracking members and write state."""
|
||||
@ -342,6 +370,7 @@ class Group(Entity):
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Handle addition to Home Assistant."""
|
||||
self._set_tracked(self._entity_ids)
|
||||
self.async_on_remove(start.async_at_start(self.hass, self._async_start))
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
@ -430,12 +459,14 @@ class Group(Entity):
|
||||
# have the same on state we use this state
|
||||
# and its hass.data[REG_KEY].on_off_mapping to off
|
||||
if num_on_states == 1:
|
||||
on_state = list(self._on_states)[0]
|
||||
on_state = next(iter(self._on_states))
|
||||
# If we do not have an on state for any domains
|
||||
# we use None (which will be STATE_UNKNOWN)
|
||||
elif num_on_states == 0:
|
||||
self._state = None
|
||||
return
|
||||
if self.single_state_type_key:
|
||||
on_state = self.single_state_type_key.on_state
|
||||
# If the entity domains have more than one
|
||||
# on state, we use STATE_ON/STATE_OFF
|
||||
else:
|
||||
@ -443,9 +474,10 @@ class Group(Entity):
|
||||
group_is_on = self.mode(self._on_off.values())
|
||||
if group_is_on:
|
||||
self._state = on_state
|
||||
elif self.single_state_type_key:
|
||||
self._state = self.single_state_type_key.off_state
|
||||
else:
|
||||
registry: GroupIntegrationRegistry = self.hass.data[REG_KEY]
|
||||
self._state = registry.on_off_mapping[on_state]
|
||||
self._state = STATE_OFF
|
||||
|
||||
|
||||
def async_get_component(hass: HomeAssistant) -> EntityComponent[Group]:
|
||||
|
@ -1,8 +1,12 @@
|
||||
"""Provide the functionality to group entities."""
|
||||
"""Provide the functionality to group entities.
|
||||
|
||||
Legacy group support will not be extended for new domains.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Protocol
|
||||
from dataclasses import dataclass
|
||||
from typing import Protocol
|
||||
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
@ -12,9 +16,6 @@ from homeassistant.helpers.integration_platform import (
|
||||
|
||||
from .const import DOMAIN, REG_KEY
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .entity import Group
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant) -> None:
|
||||
"""Set up the Group integration registry of integration platforms."""
|
||||
@ -43,6 +44,14 @@ def _process_group_platform(
|
||||
platform.async_describe_on_off_states(hass, registry)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class SingleStateType:
|
||||
"""Dataclass to store a single state type."""
|
||||
|
||||
on_state: str
|
||||
off_state: str
|
||||
|
||||
|
||||
class GroupIntegrationRegistry:
|
||||
"""Class to hold a registry of integrations."""
|
||||
|
||||
@ -53,8 +62,7 @@ class GroupIntegrationRegistry:
|
||||
self.off_on_mapping: dict[str, str] = {STATE_OFF: STATE_ON}
|
||||
self.on_states_by_domain: dict[str, set[str]] = {}
|
||||
self.exclude_domains: set[str] = set()
|
||||
self.state_group_mapping: dict[str, tuple[str, str]] = {}
|
||||
self.group_entities: set[Group] = set()
|
||||
self.state_group_mapping: dict[str, SingleStateType] = {}
|
||||
|
||||
@callback
|
||||
def exclude_domain(self, domain: str) -> None:
|
||||
@ -65,12 +73,16 @@ class GroupIntegrationRegistry:
|
||||
def on_off_states(
|
||||
self, domain: str, on_states: set[str], default_on_state: str, off_state: str
|
||||
) -> None:
|
||||
"""Register on and off states for the current domain."""
|
||||
"""Register on and off states for the current domain.
|
||||
|
||||
Legacy group support will not be extended for new domains.
|
||||
"""
|
||||
for on_state in on_states:
|
||||
if on_state not in self.on_off_mapping:
|
||||
self.on_off_mapping[on_state] = off_state
|
||||
|
||||
if len(on_states) == 1 and off_state not in self.off_on_mapping:
|
||||
if off_state not in self.off_on_mapping:
|
||||
self.off_on_mapping[off_state] = default_on_state
|
||||
self.state_group_mapping[domain] = SingleStateType(default_on_state, off_state)
|
||||
|
||||
self.on_states_by_domain[domain] = on_states
|
||||
|
@ -13,16 +13,7 @@ import voluptuous as vol
|
||||
from homeassistant import config as conf_util
|
||||
from homeassistant.components import websocket_api
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_CLIENT_ID,
|
||||
CONF_DISCOVERY,
|
||||
CONF_PASSWORD,
|
||||
CONF_PAYLOAD,
|
||||
CONF_PORT,
|
||||
CONF_PROTOCOL,
|
||||
CONF_USERNAME,
|
||||
SERVICE_RELOAD,
|
||||
)
|
||||
from homeassistant.const import CONF_DISCOVERY, CONF_PAYLOAD, SERVICE_RELOAD
|
||||
from homeassistant.core import HassJob, HomeAssistant, ServiceCall, callback
|
||||
from homeassistant.exceptions import (
|
||||
ConfigValidationError,
|
||||
@ -122,45 +113,6 @@ CONNECTION_SUCCESS = "connection_success"
|
||||
CONNECTION_FAILED = "connection_failed"
|
||||
CONNECTION_FAILED_RECOVERABLE = "connection_failed_recoverable"
|
||||
|
||||
CONFIG_ENTRY_CONFIG_KEYS = [
|
||||
CONF_BIRTH_MESSAGE,
|
||||
CONF_BROKER,
|
||||
CONF_CERTIFICATE,
|
||||
CONF_CLIENT_ID,
|
||||
CONF_CLIENT_CERT,
|
||||
CONF_CLIENT_KEY,
|
||||
CONF_DISCOVERY,
|
||||
CONF_DISCOVERY_PREFIX,
|
||||
CONF_KEEPALIVE,
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
CONF_PROTOCOL,
|
||||
CONF_TLS_INSECURE,
|
||||
CONF_TRANSPORT,
|
||||
CONF_WS_PATH,
|
||||
CONF_WS_HEADERS,
|
||||
CONF_USERNAME,
|
||||
CONF_WILL_MESSAGE,
|
||||
]
|
||||
|
||||
REMOVED_OPTIONS = vol.All(
|
||||
cv.removed(CONF_BIRTH_MESSAGE), # Removed in HA Core 2023.4
|
||||
cv.removed(CONF_BROKER), # Removed in HA Core 2023.4
|
||||
cv.removed(CONF_CERTIFICATE), # Removed in HA Core 2023.4
|
||||
cv.removed(CONF_CLIENT_ID), # Removed in HA Core 2023.4
|
||||
cv.removed(CONF_CLIENT_CERT), # Removed in HA Core 2023.4
|
||||
cv.removed(CONF_CLIENT_KEY), # Removed in HA Core 2023.4
|
||||
cv.removed(CONF_DISCOVERY), # Removed in HA Core 2022.3
|
||||
cv.removed(CONF_DISCOVERY_PREFIX), # Removed in HA Core 2023.4
|
||||
cv.removed(CONF_KEEPALIVE), # Removed in HA Core 2023.4
|
||||
cv.removed(CONF_PASSWORD), # Removed in HA Core 2023.4
|
||||
cv.removed(CONF_PORT), # Removed in HA Core 2023.4
|
||||
cv.removed(CONF_PROTOCOL), # Removed in HA Core 2023.4
|
||||
cv.removed(CONF_TLS_INSECURE), # Removed in HA Core 2023.4
|
||||
cv.removed(CONF_USERNAME), # Removed in HA Core 2023.4
|
||||
cv.removed(CONF_WILL_MESSAGE), # Removed in HA Core 2023.4
|
||||
)
|
||||
|
||||
# We accept 2 schemes for configuring manual MQTT items
|
||||
#
|
||||
# Preferred style:
|
||||
@ -187,7 +139,6 @@ CONFIG_SCHEMA = vol.Schema(
|
||||
DOMAIN: vol.All(
|
||||
cv.ensure_list,
|
||||
cv.remove_falsy,
|
||||
[REMOVED_OPTIONS],
|
||||
[CONFIG_SCHEMA_BASE],
|
||||
)
|
||||
},
|
||||
|
@ -197,7 +197,6 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
entry: ConfigEntry | None
|
||||
_hassio_discovery: dict[str, Any] | None = None
|
||||
_reauth_config_entry: ConfigEntry | None = None
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
|
@ -69,7 +69,9 @@ class RoborockMap(RoborockCoordinatedEntity, ImageEntity):
|
||||
try:
|
||||
self.cached_map = self._create_image(starting_map)
|
||||
except HomeAssistantError:
|
||||
# If we failed to update the image on init, we set cached_map to empty bytes so that we are unavailable and can try again later.
|
||||
# If we failed to update the image on init,
|
||||
# we set cached_map to empty bytes
|
||||
# so that we are unavailable and can try again later.
|
||||
self.cached_map = b""
|
||||
self._attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
|
||||
@ -84,7 +86,11 @@ class RoborockMap(RoborockCoordinatedEntity, ImageEntity):
|
||||
return self.map_flag == self.coordinator.current_map
|
||||
|
||||
def is_map_valid(self) -> bool:
|
||||
"""Update this map if it is the current active map, and the vacuum is cleaning or if it has never been set at all."""
|
||||
"""Update the map if it is valid.
|
||||
|
||||
Update this map if it is the currently active map, and the
|
||||
vacuum is cleaning, or if it has never been set at all.
|
||||
"""
|
||||
return self.cached_map == b"" or (
|
||||
self.is_selected
|
||||
and self.image_last_updated is not None
|
||||
@ -134,8 +140,9 @@ class RoborockMap(RoborockCoordinatedEntity, ImageEntity):
|
||||
async def create_coordinator_maps(
|
||||
coord: RoborockDataUpdateCoordinator,
|
||||
) -> list[RoborockMap]:
|
||||
"""Get the starting map information for all maps for this device. The following steps must be done synchronously.
|
||||
"""Get the starting map information for all maps for this device.
|
||||
|
||||
The following steps must be done synchronously.
|
||||
Only one map can be loaded at a time per device.
|
||||
"""
|
||||
entities = []
|
||||
@ -161,7 +168,8 @@ async def create_coordinator_maps(
|
||||
map_update = await asyncio.gather(
|
||||
*[coord.cloud_api.get_map_v1(), coord.get_rooms()], return_exceptions=True
|
||||
)
|
||||
# If we fail to get the map -> We should set it to empty byte, still create it, and set it as unavailable.
|
||||
# If we fail to get the map, we should set it to empty byte,
|
||||
# still create it, and set it as unavailable.
|
||||
api_data: bytes = map_update[0] if isinstance(map_update[0], bytes) else b""
|
||||
entities.append(
|
||||
RoborockMap(
|
||||
|
@ -8,7 +8,7 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["aiounifi"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["aiounifi==76"],
|
||||
"requirements": ["aiounifi==77"],
|
||||
"ssdp": [
|
||||
{
|
||||
"manufacturer": "Ubiquiti Networks",
|
||||
|
@ -57,4 +57,6 @@ SKU_TO_BASE_DEVICE = {
|
||||
"Vital100S": "Vital100S",
|
||||
"LAP-V102S-WUS": "Vital100S", # Alt ID Model Vital100S
|
||||
"LAP-V102S-AASR": "Vital100S", # Alt ID Model Vital100S
|
||||
"LAP-V102S-WEU": "Vital100S", # Alt ID Model Vital100S
|
||||
"LAP-V102S-WUK": "Vital100S", # Alt ID Model Vital100S
|
||||
}
|
||||
|
@ -386,7 +386,7 @@ aiotankerkoenig==0.4.1
|
||||
aiotractive==0.5.6
|
||||
|
||||
# homeassistant.components.unifi
|
||||
aiounifi==76
|
||||
aiounifi==77
|
||||
|
||||
# homeassistant.components.vlc_telnet
|
||||
aiovlc==0.1.0
|
||||
|
@ -359,7 +359,7 @@ aiotankerkoenig==0.4.1
|
||||
aiotractive==0.5.6
|
||||
|
||||
# homeassistant.components.unifi
|
||||
aiounifi==76
|
||||
aiounifi==77
|
||||
|
||||
# homeassistant.components.vlc_telnet
|
||||
aiovlc==0.1.0
|
||||
|
@ -10,6 +10,7 @@ from unittest.mock import patch
|
||||
import pytest
|
||||
|
||||
from homeassistant.components import group
|
||||
from homeassistant.components.group.registry import GroupIntegrationRegistry
|
||||
from homeassistant.const import (
|
||||
ATTR_ASSUMED_STATE,
|
||||
ATTR_FRIENDLY_NAME,
|
||||
@ -33,7 +34,116 @@ from homeassistant.setup import async_setup_component
|
||||
|
||||
from . import common
|
||||
|
||||
from tests.common import MockConfigEntry, assert_setup_component
|
||||
from tests.common import (
|
||||
MockConfigEntry,
|
||||
MockModule,
|
||||
MockPlatform,
|
||||
assert_setup_component,
|
||||
mock_integration,
|
||||
mock_platform,
|
||||
)
|
||||
|
||||
|
||||
async def help_test_mixed_entity_platforms_on_off_state_test(
|
||||
hass: HomeAssistant,
|
||||
on_off_states1: tuple[set[str], str, str],
|
||||
on_off_states2: tuple[set[str], str, str],
|
||||
entity_and_state1_state_2: tuple[str, str | None, str | None],
|
||||
group_state1: str,
|
||||
group_state2: str,
|
||||
grouped_groups: bool = False,
|
||||
) -> None:
|
||||
"""Help test on_off_states on mixed entity platforms."""
|
||||
|
||||
class MockGroupPlatform1(MockPlatform):
|
||||
"""Mock a group platform module for test1 integration."""
|
||||
|
||||
def async_describe_on_off_states(
|
||||
self, hass: HomeAssistant, registry: GroupIntegrationRegistry
|
||||
) -> None:
|
||||
"""Describe group on off states."""
|
||||
registry.on_off_states("test1", *on_off_states1)
|
||||
|
||||
class MockGroupPlatform2(MockPlatform):
|
||||
"""Mock a group platform module for test2 integration."""
|
||||
|
||||
def async_describe_on_off_states(
|
||||
self, hass: HomeAssistant, registry: GroupIntegrationRegistry
|
||||
) -> None:
|
||||
"""Describe group on off states."""
|
||||
registry.on_off_states("test2", *on_off_states2)
|
||||
|
||||
mock_integration(hass, MockModule(domain="test1"))
|
||||
mock_platform(hass, "test1.group", MockGroupPlatform1())
|
||||
assert await async_setup_component(hass, "test1", {"test1": {}})
|
||||
|
||||
mock_integration(hass, MockModule(domain="test2"))
|
||||
mock_platform(hass, "test2.group", MockGroupPlatform2())
|
||||
assert await async_setup_component(hass, "test2", {"test2": {}})
|
||||
|
||||
if grouped_groups:
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"group",
|
||||
{
|
||||
"group": {
|
||||
"test1": {
|
||||
"entities": [
|
||||
item[0]
|
||||
for item in entity_and_state1_state_2
|
||||
if item[0].startswith("test1.")
|
||||
]
|
||||
},
|
||||
"test2": {
|
||||
"entities": [
|
||||
item[0]
|
||||
for item in entity_and_state1_state_2
|
||||
if item[0].startswith("test2.")
|
||||
]
|
||||
},
|
||||
"test": {"entities": ["group.test1", "group.test2"]},
|
||||
}
|
||||
},
|
||||
)
|
||||
else:
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"group",
|
||||
{
|
||||
"group": {
|
||||
"test": {
|
||||
"entities": [item[0] for item in entity_and_state1_state_2]
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("group.test")
|
||||
assert state is not None
|
||||
|
||||
# Set first state
|
||||
for entity_id, state1, _ in entity_and_state1_state_2:
|
||||
hass.states.async_set(entity_id, state1)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("group.test")
|
||||
assert state is not None
|
||||
assert state.state == group_state1
|
||||
|
||||
# Set second state
|
||||
for entity_id, _, state2 in entity_and_state1_state_2:
|
||||
hass.states.async_set(entity_id, state2)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("group.test")
|
||||
assert state is not None
|
||||
assert state.state == group_state2
|
||||
|
||||
|
||||
async def test_setup_group_with_mixed_groupable_states(hass: HomeAssistant) -> None:
|
||||
@ -1560,6 +1670,7 @@ async def test_group_that_references_a_group_of_covers(hass: HomeAssistant) -> N
|
||||
for entity_id in entity_ids:
|
||||
hass.states.async_set(entity_id, "closed")
|
||||
await hass.async_block_till_done()
|
||||
assert await async_setup_component(hass, "cover", {})
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
@ -1643,6 +1754,7 @@ async def test_group_that_references_two_types_of_groups(hass: HomeAssistant) ->
|
||||
hass.states.async_set(entity_id, "home")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert await async_setup_component(hass, "cover", {})
|
||||
assert await async_setup_component(hass, "device_tracker", {})
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
@ -1884,3 +1996,216 @@ async def test_unhide_members_on_remove(
|
||||
# Check the group members are unhidden
|
||||
assert entity_registry.async_get(f"{group_type}.one").hidden_by == hidden_by
|
||||
assert entity_registry.async_get(f"{group_type}.three").hidden_by == hidden_by
|
||||
|
||||
|
||||
@pytest.mark.parametrize("grouped_groups", [False, True])
|
||||
@pytest.mark.parametrize(
|
||||
("on_off_states1", "on_off_states2"),
|
||||
[
|
||||
(
|
||||
(
|
||||
{
|
||||
"on_beer",
|
||||
"on_milk",
|
||||
},
|
||||
"on_beer", # default ON state test1
|
||||
"off_water", # default OFF state test1
|
||||
),
|
||||
(
|
||||
{
|
||||
"on_beer",
|
||||
"on_milk",
|
||||
},
|
||||
"on_milk", # default ON state test2
|
||||
"off_wine", # default OFF state test2
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
("entity_and_state1_state_2", "group_state1", "group_state2"),
|
||||
[
|
||||
# All OFF states, no change, so group stays OFF
|
||||
(
|
||||
[
|
||||
("test1.ent1", "off_water", "off_water"),
|
||||
("test1.ent2", "off_water", "off_water"),
|
||||
("test2.ent1", "off_wine", "off_wine"),
|
||||
("test2.ent2", "off_wine", "off_wine"),
|
||||
],
|
||||
STATE_OFF,
|
||||
STATE_OFF,
|
||||
),
|
||||
# All entities have state on_milk, but the state groups
|
||||
# are different so the group status defaults to ON / OFF
|
||||
(
|
||||
[
|
||||
("test1.ent1", "off_water", "on_milk"),
|
||||
("test1.ent2", "off_water", "on_milk"),
|
||||
("test2.ent1", "off_wine", "on_milk"),
|
||||
("test2.ent2", "off_wine", "on_milk"),
|
||||
],
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
),
|
||||
# Only test1 entities in group, all at ON state
|
||||
# group returns the default ON state `on_beer`
|
||||
(
|
||||
[
|
||||
("test1.ent1", "off_water", "on_milk"),
|
||||
("test1.ent2", "off_water", "on_beer"),
|
||||
],
|
||||
"off_water",
|
||||
"on_beer",
|
||||
),
|
||||
# Only test1 entities in group, all at ON state
|
||||
# group returns the default ON state `on_beer`
|
||||
(
|
||||
[
|
||||
("test1.ent1", "off_water", "on_milk"),
|
||||
("test1.ent2", "off_water", "on_milk"),
|
||||
],
|
||||
"off_water",
|
||||
"on_beer",
|
||||
),
|
||||
# Only test2 entities in group, all at ON state
|
||||
# group returns the default ON state `on_milk`
|
||||
(
|
||||
[
|
||||
("test2.ent1", "off_wine", "on_milk"),
|
||||
("test2.ent2", "off_wine", "on_milk"),
|
||||
],
|
||||
"off_wine",
|
||||
"on_milk",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_entity_platforms_with_multiple_on_states_no_state_match(
|
||||
hass: HomeAssistant,
|
||||
on_off_states1: tuple[set[str], str, str],
|
||||
on_off_states2: tuple[set[str], str, str],
|
||||
entity_and_state1_state_2: tuple[str, str | None, str | None],
|
||||
group_state1: str,
|
||||
group_state2: str,
|
||||
grouped_groups: bool,
|
||||
) -> None:
|
||||
"""Test custom entity platforms with multiple ON states without state match.
|
||||
|
||||
The test group 1 an 2 non matching (default_state_on, state_off) pairs.
|
||||
"""
|
||||
await help_test_mixed_entity_platforms_on_off_state_test(
|
||||
hass,
|
||||
on_off_states1,
|
||||
on_off_states2,
|
||||
entity_and_state1_state_2,
|
||||
group_state1,
|
||||
group_state2,
|
||||
grouped_groups,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("grouped_groups", [False, True])
|
||||
@pytest.mark.parametrize(
|
||||
("on_off_states1", "on_off_states2"),
|
||||
[
|
||||
(
|
||||
(
|
||||
{
|
||||
"on_beer",
|
||||
"on_milk",
|
||||
},
|
||||
"on_beer", # default ON state test1
|
||||
"off_water", # default OFF state test1
|
||||
),
|
||||
(
|
||||
{
|
||||
"on_beer",
|
||||
"on_wine",
|
||||
},
|
||||
"on_beer", # default ON state test2
|
||||
"off_water", # default OFF state test2
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
("entity_and_state1_state_2", "group_state1", "group_state2"),
|
||||
[
|
||||
# All OFF states, no change, so group stays OFF
|
||||
(
|
||||
[
|
||||
("test1.ent1", "off_water", "off_water"),
|
||||
("test1.ent2", "off_water", "off_water"),
|
||||
("test2.ent1", "off_water", "off_water"),
|
||||
("test2.ent2", "off_water", "off_water"),
|
||||
],
|
||||
"off_water",
|
||||
"off_water",
|
||||
),
|
||||
# All entities have ON state `on_milk`
|
||||
# but the group state will default to on_beer
|
||||
# which is the default ON state for both integrations.
|
||||
(
|
||||
[
|
||||
("test1.ent1", "off_water", "on_milk"),
|
||||
("test1.ent2", "off_water", "on_milk"),
|
||||
("test2.ent1", "off_water", "on_milk"),
|
||||
("test2.ent2", "off_water", "on_milk"),
|
||||
],
|
||||
"off_water",
|
||||
"on_beer",
|
||||
),
|
||||
# Only test1 entities in group, all at ON state
|
||||
# group returns the default ON state `on_beer`
|
||||
(
|
||||
[
|
||||
("test1.ent1", "off_water", "on_milk"),
|
||||
("test1.ent2", "off_water", "on_beer"),
|
||||
],
|
||||
"off_water",
|
||||
"on_beer",
|
||||
),
|
||||
# Only test1 entities in group, all at ON state
|
||||
# group returns the default ON state `on_beer`
|
||||
(
|
||||
[
|
||||
("test1.ent1", "off_water", "on_milk"),
|
||||
("test1.ent2", "off_water", "on_milk"),
|
||||
],
|
||||
"off_water",
|
||||
"on_beer",
|
||||
),
|
||||
# Only test2 entities in group, all at ON state
|
||||
# group returns the default ON state `on_milk`
|
||||
(
|
||||
[
|
||||
("test2.ent1", "off_water", "on_wine"),
|
||||
("test2.ent2", "off_water", "on_wine"),
|
||||
],
|
||||
"off_water",
|
||||
"on_beer",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_entity_platforms_with_multiple_on_states_with_state_match(
|
||||
hass: HomeAssistant,
|
||||
on_off_states1: tuple[set[str], str, str],
|
||||
on_off_states2: tuple[set[str], str, str],
|
||||
entity_and_state1_state_2: tuple[str, str | None, str | None],
|
||||
group_state1: str,
|
||||
group_state2: str,
|
||||
grouped_groups: bool,
|
||||
) -> None:
|
||||
"""Test custom entity platforms with multiple ON states with a state match.
|
||||
|
||||
The integrations test1 and test2 have matching (default_state_on, state_off) pairs.
|
||||
"""
|
||||
await help_test_mixed_entity_platforms_on_off_state_test(
|
||||
hass,
|
||||
on_off_states1,
|
||||
on_off_states2,
|
||||
entity_and_state1_state_2,
|
||||
group_state1,
|
||||
group_state2,
|
||||
grouped_groups,
|
||||
)
|
||||
|
@ -818,7 +818,7 @@ async def test_device_registry_calls(hass: HomeAssistant) -> None:
|
||||
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
assert len(dev_reg.devices) == 6
|
||||
|
||||
supervisor_mock_data = {
|
||||
|
@ -15,6 +15,13 @@ from homeassistant import config_entries
|
||||
from homeassistant.components import mqtt
|
||||
from homeassistant.components.hassio import HassioServiceInfo
|
||||
from homeassistant.components.mqtt.config_flow import PWD_NOT_CHANGED
|
||||
from homeassistant.const import (
|
||||
CONF_CLIENT_ID,
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
CONF_PROTOCOL,
|
||||
CONF_USERNAME,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
@ -230,8 +237,8 @@ async def test_user_v5_connection_works(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
mqtt.CONF_BROKER: "another-broker",
|
||||
mqtt.CONF_PORT: 2345,
|
||||
mqtt.CONF_PROTOCOL: "5",
|
||||
CONF_PORT: 2345,
|
||||
CONF_PROTOCOL: "5",
|
||||
},
|
||||
)
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
@ -468,7 +475,7 @@ async def test_option_flow(
|
||||
config_entry,
|
||||
data={
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_PORT: 1234,
|
||||
CONF_PORT: 1234,
|
||||
},
|
||||
)
|
||||
|
||||
@ -482,9 +489,9 @@ async def test_option_flow(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
mqtt.CONF_BROKER: "another-broker",
|
||||
mqtt.CONF_PORT: 2345,
|
||||
mqtt.CONF_USERNAME: "user",
|
||||
mqtt.CONF_PASSWORD: "pass",
|
||||
CONF_PORT: 2345,
|
||||
CONF_USERNAME: "user",
|
||||
CONF_PASSWORD: "pass",
|
||||
},
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
@ -516,9 +523,9 @@ async def test_option_flow(
|
||||
assert result["data"] == {}
|
||||
assert config_entry.data == {
|
||||
mqtt.CONF_BROKER: "another-broker",
|
||||
mqtt.CONF_PORT: 2345,
|
||||
mqtt.CONF_USERNAME: "user",
|
||||
mqtt.CONF_PASSWORD: "pass",
|
||||
CONF_PORT: 2345,
|
||||
CONF_USERNAME: "user",
|
||||
CONF_PASSWORD: "pass",
|
||||
mqtt.CONF_DISCOVERY: True,
|
||||
mqtt.CONF_DISCOVERY_PREFIX: "homeassistant",
|
||||
mqtt.CONF_BIRTH_MESSAGE: {
|
||||
@ -565,7 +572,7 @@ async def test_bad_certificate(
|
||||
file_id = mock_process_uploaded_file.file_id
|
||||
test_input = {
|
||||
mqtt.CONF_BROKER: "another-broker",
|
||||
mqtt.CONF_PORT: 2345,
|
||||
CONF_PORT: 2345,
|
||||
mqtt.CONF_CERTIFICATE: file_id[mqtt.CONF_CERTIFICATE],
|
||||
mqtt.CONF_CLIENT_CERT: file_id[mqtt.CONF_CLIENT_CERT],
|
||||
mqtt.CONF_CLIENT_KEY: file_id[mqtt.CONF_CLIENT_KEY],
|
||||
@ -599,11 +606,11 @@ async def test_bad_certificate(
|
||||
config_entry,
|
||||
data={
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_PORT: 1234,
|
||||
mqtt.CONF_CLIENT_ID: "custom1234",
|
||||
CONF_PORT: 1234,
|
||||
CONF_CLIENT_ID: "custom1234",
|
||||
mqtt.CONF_KEEPALIVE: 60,
|
||||
mqtt.CONF_TLS_INSECURE: False,
|
||||
mqtt.CONF_PROTOCOL: "3.1.1",
|
||||
CONF_PROTOCOL: "3.1.1",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
@ -618,13 +625,13 @@ async def test_bad_certificate(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
mqtt.CONF_BROKER: "another-broker",
|
||||
mqtt.CONF_PORT: 2345,
|
||||
CONF_PORT: 2345,
|
||||
mqtt.CONF_KEEPALIVE: 60,
|
||||
"set_client_cert": set_client_cert,
|
||||
"set_ca_cert": set_ca_cert,
|
||||
mqtt.CONF_TLS_INSECURE: tls_insecure,
|
||||
mqtt.CONF_PROTOCOL: "3.1.1",
|
||||
mqtt.CONF_CLIENT_ID: "custom1234",
|
||||
CONF_PROTOCOL: "3.1.1",
|
||||
CONF_CLIENT_ID: "custom1234",
|
||||
},
|
||||
)
|
||||
test_input["set_client_cert"] = set_client_cert
|
||||
@ -664,7 +671,7 @@ async def test_keepalive_validation(
|
||||
|
||||
test_input = {
|
||||
mqtt.CONF_BROKER: "another-broker",
|
||||
mqtt.CONF_PORT: 2345,
|
||||
CONF_PORT: 2345,
|
||||
mqtt.CONF_KEEPALIVE: input_value,
|
||||
}
|
||||
|
||||
@ -676,8 +683,8 @@ async def test_keepalive_validation(
|
||||
config_entry,
|
||||
data={
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_PORT: 1234,
|
||||
mqtt.CONF_CLIENT_ID: "custom1234",
|
||||
CONF_PORT: 1234,
|
||||
CONF_CLIENT_ID: "custom1234",
|
||||
},
|
||||
)
|
||||
|
||||
@ -715,7 +722,7 @@ async def test_disable_birth_will(
|
||||
config_entry,
|
||||
data={
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_PORT: 1234,
|
||||
CONF_PORT: 1234,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
@ -731,9 +738,9 @@ async def test_disable_birth_will(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
mqtt.CONF_BROKER: "another-broker",
|
||||
mqtt.CONF_PORT: 2345,
|
||||
mqtt.CONF_USERNAME: "user",
|
||||
mqtt.CONF_PASSWORD: "pass",
|
||||
CONF_PORT: 2345,
|
||||
CONF_USERNAME: "user",
|
||||
CONF_PASSWORD: "pass",
|
||||
},
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
@ -763,9 +770,9 @@ async def test_disable_birth_will(
|
||||
assert result["data"] == {}
|
||||
assert config_entry.data == {
|
||||
mqtt.CONF_BROKER: "another-broker",
|
||||
mqtt.CONF_PORT: 2345,
|
||||
mqtt.CONF_USERNAME: "user",
|
||||
mqtt.CONF_PASSWORD: "pass",
|
||||
CONF_PORT: 2345,
|
||||
CONF_USERNAME: "user",
|
||||
CONF_PASSWORD: "pass",
|
||||
mqtt.CONF_DISCOVERY: True,
|
||||
mqtt.CONF_DISCOVERY_PREFIX: "homeassistant",
|
||||
mqtt.CONF_BIRTH_MESSAGE: {},
|
||||
@ -791,7 +798,7 @@ async def test_invalid_discovery_prefix(
|
||||
config_entry,
|
||||
data={
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_PORT: 1234,
|
||||
CONF_PORT: 1234,
|
||||
mqtt.CONF_DISCOVERY: True,
|
||||
mqtt.CONF_DISCOVERY_PREFIX: "homeassistant",
|
||||
},
|
||||
@ -808,7 +815,7 @@ async def test_invalid_discovery_prefix(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
mqtt.CONF_BROKER: "another-broker",
|
||||
mqtt.CONF_PORT: 2345,
|
||||
CONF_PORT: 2345,
|
||||
},
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
@ -829,7 +836,7 @@ async def test_invalid_discovery_prefix(
|
||||
assert result["errors"]["base"] == "bad_discovery_prefix"
|
||||
assert config_entry.data == {
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_PORT: 1234,
|
||||
CONF_PORT: 1234,
|
||||
mqtt.CONF_DISCOVERY: True,
|
||||
mqtt.CONF_DISCOVERY_PREFIX: "homeassistant",
|
||||
}
|
||||
@ -873,9 +880,9 @@ async def test_option_flow_default_suggested_values(
|
||||
config_entry,
|
||||
data={
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_PORT: 1234,
|
||||
mqtt.CONF_USERNAME: "user",
|
||||
mqtt.CONF_PASSWORD: "pass",
|
||||
CONF_PORT: 1234,
|
||||
CONF_USERNAME: "user",
|
||||
CONF_PASSWORD: "pass",
|
||||
mqtt.CONF_DISCOVERY: True,
|
||||
mqtt.CONF_BIRTH_MESSAGE: {
|
||||
mqtt.ATTR_TOPIC: "ha_state/online",
|
||||
@ -898,11 +905,11 @@ async def test_option_flow_default_suggested_values(
|
||||
assert result["step_id"] == "broker"
|
||||
defaults = {
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_PORT: 1234,
|
||||
CONF_PORT: 1234,
|
||||
}
|
||||
suggested = {
|
||||
mqtt.CONF_USERNAME: "user",
|
||||
mqtt.CONF_PASSWORD: PWD_NOT_CHANGED,
|
||||
CONF_USERNAME: "user",
|
||||
CONF_PASSWORD: PWD_NOT_CHANGED,
|
||||
}
|
||||
for key, value in defaults.items():
|
||||
assert get_default(result["data_schema"].schema, key) == value
|
||||
@ -913,9 +920,9 @@ async def test_option_flow_default_suggested_values(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
mqtt.CONF_BROKER: "another-broker",
|
||||
mqtt.CONF_PORT: 2345,
|
||||
mqtt.CONF_USERNAME: "us3r",
|
||||
mqtt.CONF_PASSWORD: "p4ss",
|
||||
CONF_PORT: 2345,
|
||||
CONF_USERNAME: "us3r",
|
||||
CONF_PASSWORD: "p4ss",
|
||||
},
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
@ -960,11 +967,11 @@ async def test_option_flow_default_suggested_values(
|
||||
assert result["step_id"] == "broker"
|
||||
defaults = {
|
||||
mqtt.CONF_BROKER: "another-broker",
|
||||
mqtt.CONF_PORT: 2345,
|
||||
CONF_PORT: 2345,
|
||||
}
|
||||
suggested = {
|
||||
mqtt.CONF_USERNAME: "us3r",
|
||||
mqtt.CONF_PASSWORD: PWD_NOT_CHANGED,
|
||||
CONF_USERNAME: "us3r",
|
||||
CONF_PASSWORD: PWD_NOT_CHANGED,
|
||||
}
|
||||
for key, value in defaults.items():
|
||||
assert get_default(result["data_schema"].schema, key) == value
|
||||
@ -973,7 +980,7 @@ async def test_option_flow_default_suggested_values(
|
||||
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={mqtt.CONF_BROKER: "another-broker", mqtt.CONF_PORT: 2345},
|
||||
user_input={mqtt.CONF_BROKER: "another-broker", CONF_PORT: 2345},
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "options"
|
||||
@ -1030,7 +1037,7 @@ async def test_skipping_advanced_options(
|
||||
|
||||
test_input = {
|
||||
mqtt.CONF_BROKER: "another-broker",
|
||||
mqtt.CONF_PORT: 2345,
|
||||
CONF_PORT: 2345,
|
||||
"advanced_options": advanced_options,
|
||||
}
|
||||
|
||||
@ -1042,7 +1049,7 @@ async def test_skipping_advanced_options(
|
||||
config_entry,
|
||||
data={
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_PORT: 1234,
|
||||
CONF_PORT: 1234,
|
||||
},
|
||||
)
|
||||
|
||||
@ -1067,24 +1074,24 @@ async def test_skipping_advanced_options(
|
||||
(
|
||||
{
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_USERNAME: "username",
|
||||
mqtt.CONF_PASSWORD: "verysecret",
|
||||
CONF_USERNAME: "username",
|
||||
CONF_PASSWORD: "verysecret",
|
||||
},
|
||||
{
|
||||
mqtt.CONF_USERNAME: "username",
|
||||
mqtt.CONF_PASSWORD: "newpassword",
|
||||
CONF_USERNAME: "username",
|
||||
CONF_PASSWORD: "newpassword",
|
||||
},
|
||||
"newpassword",
|
||||
),
|
||||
(
|
||||
{
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_USERNAME: "username",
|
||||
mqtt.CONF_PASSWORD: "verysecret",
|
||||
CONF_USERNAME: "username",
|
||||
CONF_PASSWORD: "verysecret",
|
||||
},
|
||||
{
|
||||
mqtt.CONF_USERNAME: "username",
|
||||
mqtt.CONF_PASSWORD: PWD_NOT_CHANGED,
|
||||
CONF_USERNAME: "username",
|
||||
CONF_PASSWORD: PWD_NOT_CHANGED,
|
||||
},
|
||||
"verysecret",
|
||||
),
|
||||
@ -1153,7 +1160,7 @@ async def test_step_reauth(
|
||||
assert result["reason"] == "reauth_successful"
|
||||
|
||||
assert len(hass.config_entries.async_entries()) == 1
|
||||
assert config_entry.data.get(mqtt.CONF_PASSWORD) == new_password
|
||||
assert config_entry.data.get(CONF_PASSWORD) == new_password
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@ -1167,7 +1174,7 @@ async def test_options_user_connection_fails(
|
||||
config_entry,
|
||||
data={
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_PORT: 1234,
|
||||
CONF_PORT: 1234,
|
||||
},
|
||||
)
|
||||
result = await hass.config_entries.options.async_init(config_entry.entry_id)
|
||||
@ -1176,7 +1183,7 @@ async def test_options_user_connection_fails(
|
||||
mock_try_connection_time_out.reset_mock()
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={mqtt.CONF_BROKER: "bad-broker", mqtt.CONF_PORT: 2345},
|
||||
user_input={mqtt.CONF_BROKER: "bad-broker", CONF_PORT: 2345},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
@ -1187,7 +1194,7 @@ async def test_options_user_connection_fails(
|
||||
# Check config entry did not update
|
||||
assert config_entry.data == {
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_PORT: 1234,
|
||||
CONF_PORT: 1234,
|
||||
}
|
||||
|
||||
|
||||
@ -1201,7 +1208,7 @@ async def test_options_bad_birth_message_fails(
|
||||
config_entry,
|
||||
data={
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_PORT: 1234,
|
||||
CONF_PORT: 1234,
|
||||
},
|
||||
)
|
||||
|
||||
@ -1212,7 +1219,7 @@ async def test_options_bad_birth_message_fails(
|
||||
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={mqtt.CONF_BROKER: "another-broker", mqtt.CONF_PORT: 2345},
|
||||
user_input={mqtt.CONF_BROKER: "another-broker", CONF_PORT: 2345},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
@ -1228,7 +1235,7 @@ async def test_options_bad_birth_message_fails(
|
||||
# Check config entry did not update
|
||||
assert config_entry.data == {
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_PORT: 1234,
|
||||
CONF_PORT: 1234,
|
||||
}
|
||||
|
||||
|
||||
@ -1242,7 +1249,7 @@ async def test_options_bad_will_message_fails(
|
||||
config_entry,
|
||||
data={
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_PORT: 1234,
|
||||
CONF_PORT: 1234,
|
||||
},
|
||||
)
|
||||
|
||||
@ -1253,7 +1260,7 @@ async def test_options_bad_will_message_fails(
|
||||
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={mqtt.CONF_BROKER: "another-broker", mqtt.CONF_PORT: 2345},
|
||||
user_input={mqtt.CONF_BROKER: "another-broker", CONF_PORT: 2345},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
@ -1269,7 +1276,7 @@ async def test_options_bad_will_message_fails(
|
||||
# Check config entry did not update
|
||||
assert config_entry.data == {
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_PORT: 1234,
|
||||
CONF_PORT: 1234,
|
||||
}
|
||||
|
||||
|
||||
@ -1290,9 +1297,9 @@ async def test_try_connection_with_advanced_parameters(
|
||||
config_entry,
|
||||
data={
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_PORT: 1234,
|
||||
mqtt.CONF_USERNAME: "user",
|
||||
mqtt.CONF_PASSWORD: "pass",
|
||||
CONF_PORT: 1234,
|
||||
CONF_USERNAME: "user",
|
||||
CONF_PASSWORD: "pass",
|
||||
mqtt.CONF_TRANSPORT: "websockets",
|
||||
mqtt.CONF_CERTIFICATE: "auto",
|
||||
mqtt.CONF_TLS_INSECURE: True,
|
||||
@ -1323,15 +1330,15 @@ async def test_try_connection_with_advanced_parameters(
|
||||
assert result["step_id"] == "broker"
|
||||
defaults = {
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_PORT: 1234,
|
||||
CONF_PORT: 1234,
|
||||
"set_client_cert": True,
|
||||
"set_ca_cert": "auto",
|
||||
}
|
||||
suggested = {
|
||||
mqtt.CONF_USERNAME: "user",
|
||||
mqtt.CONF_PASSWORD: PWD_NOT_CHANGED,
|
||||
CONF_USERNAME: "user",
|
||||
CONF_PASSWORD: PWD_NOT_CHANGED,
|
||||
mqtt.CONF_TLS_INSECURE: True,
|
||||
mqtt.CONF_PROTOCOL: "3.1.1",
|
||||
CONF_PROTOCOL: "3.1.1",
|
||||
mqtt.CONF_TRANSPORT: "websockets",
|
||||
mqtt.CONF_WS_PATH: "/path/",
|
||||
mqtt.CONF_WS_HEADERS: '{"h1":"v1","h2":"v2"}',
|
||||
@ -1348,9 +1355,9 @@ async def test_try_connection_with_advanced_parameters(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
mqtt.CONF_BROKER: "another-broker",
|
||||
mqtt.CONF_PORT: 2345,
|
||||
mqtt.CONF_USERNAME: "us3r",
|
||||
mqtt.CONF_PASSWORD: "p4ss",
|
||||
CONF_PORT: 2345,
|
||||
CONF_USERNAME: "us3r",
|
||||
CONF_PASSWORD: "p4ss",
|
||||
"set_ca_cert": "auto",
|
||||
"set_client_cert": True,
|
||||
mqtt.CONF_TLS_INSECURE: True,
|
||||
@ -1409,7 +1416,7 @@ async def test_setup_with_advanced_settings(
|
||||
config_entry,
|
||||
data={
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_PORT: 1234,
|
||||
CONF_PORT: 1234,
|
||||
},
|
||||
)
|
||||
|
||||
@ -1427,21 +1434,21 @@ async def test_setup_with_advanced_settings(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_PORT: 2345,
|
||||
mqtt.CONF_USERNAME: "user",
|
||||
mqtt.CONF_PASSWORD: "secret",
|
||||
CONF_PORT: 2345,
|
||||
CONF_USERNAME: "user",
|
||||
CONF_PASSWORD: "secret",
|
||||
"advanced_options": True,
|
||||
},
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "broker"
|
||||
assert "advanced_options" not in result["data_schema"].schema
|
||||
assert result["data_schema"].schema[mqtt.CONF_CLIENT_ID]
|
||||
assert result["data_schema"].schema[CONF_CLIENT_ID]
|
||||
assert result["data_schema"].schema[mqtt.CONF_KEEPALIVE]
|
||||
assert result["data_schema"].schema["set_client_cert"]
|
||||
assert result["data_schema"].schema["set_ca_cert"]
|
||||
assert result["data_schema"].schema[mqtt.CONF_TLS_INSECURE]
|
||||
assert result["data_schema"].schema[mqtt.CONF_PROTOCOL]
|
||||
assert result["data_schema"].schema[CONF_PROTOCOL]
|
||||
assert result["data_schema"].schema[mqtt.CONF_TRANSPORT]
|
||||
assert mqtt.CONF_CLIENT_CERT not in result["data_schema"].schema
|
||||
assert mqtt.CONF_CLIENT_KEY not in result["data_schema"].schema
|
||||
@ -1451,26 +1458,26 @@ async def test_setup_with_advanced_settings(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_PORT: 2345,
|
||||
mqtt.CONF_USERNAME: "user",
|
||||
mqtt.CONF_PASSWORD: "secret",
|
||||
CONF_PORT: 2345,
|
||||
CONF_USERNAME: "user",
|
||||
CONF_PASSWORD: "secret",
|
||||
mqtt.CONF_KEEPALIVE: 30,
|
||||
"set_ca_cert": "auto",
|
||||
"set_client_cert": True,
|
||||
mqtt.CONF_TLS_INSECURE: True,
|
||||
mqtt.CONF_PROTOCOL: "3.1.1",
|
||||
CONF_PROTOCOL: "3.1.1",
|
||||
mqtt.CONF_TRANSPORT: "websockets",
|
||||
},
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "broker"
|
||||
assert "advanced_options" not in result["data_schema"].schema
|
||||
assert result["data_schema"].schema[mqtt.CONF_CLIENT_ID]
|
||||
assert result["data_schema"].schema[CONF_CLIENT_ID]
|
||||
assert result["data_schema"].schema[mqtt.CONF_KEEPALIVE]
|
||||
assert result["data_schema"].schema["set_client_cert"]
|
||||
assert result["data_schema"].schema["set_ca_cert"]
|
||||
assert result["data_schema"].schema[mqtt.CONF_TLS_INSECURE]
|
||||
assert result["data_schema"].schema[mqtt.CONF_PROTOCOL]
|
||||
assert result["data_schema"].schema[CONF_PROTOCOL]
|
||||
assert result["data_schema"].schema[mqtt.CONF_CLIENT_CERT]
|
||||
assert result["data_schema"].schema[mqtt.CONF_CLIENT_KEY]
|
||||
assert result["data_schema"].schema[mqtt.CONF_TRANSPORT]
|
||||
@ -1482,9 +1489,9 @@ async def test_setup_with_advanced_settings(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_PORT: 2345,
|
||||
mqtt.CONF_USERNAME: "user",
|
||||
mqtt.CONF_PASSWORD: "secret",
|
||||
CONF_PORT: 2345,
|
||||
CONF_USERNAME: "user",
|
||||
CONF_PASSWORD: "secret",
|
||||
mqtt.CONF_KEEPALIVE: 30,
|
||||
"set_ca_cert": "auto",
|
||||
"set_client_cert": True,
|
||||
@ -1507,9 +1514,9 @@ async def test_setup_with_advanced_settings(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_PORT: 2345,
|
||||
mqtt.CONF_USERNAME: "user",
|
||||
mqtt.CONF_PASSWORD: "secret",
|
||||
CONF_PORT: 2345,
|
||||
CONF_USERNAME: "user",
|
||||
CONF_PASSWORD: "secret",
|
||||
mqtt.CONF_KEEPALIVE: 30,
|
||||
"set_ca_cert": "auto",
|
||||
"set_client_cert": True,
|
||||
@ -1537,9 +1544,9 @@ async def test_setup_with_advanced_settings(
|
||||
# Check config entry result
|
||||
assert config_entry.data == {
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_PORT: 2345,
|
||||
mqtt.CONF_USERNAME: "user",
|
||||
mqtt.CONF_PASSWORD: "secret",
|
||||
CONF_PORT: 2345,
|
||||
CONF_USERNAME: "user",
|
||||
CONF_PASSWORD: "secret",
|
||||
mqtt.CONF_KEEPALIVE: 30,
|
||||
mqtt.CONF_CLIENT_CERT: "## mock client certificate file ##",
|
||||
mqtt.CONF_CLIENT_KEY: "## mock key file ##",
|
||||
@ -1569,7 +1576,7 @@ async def test_change_websockets_transport_to_tcp(
|
||||
config_entry,
|
||||
data={
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_PORT: 1234,
|
||||
CONF_PORT: 1234,
|
||||
mqtt.CONF_TRANSPORT: "websockets",
|
||||
mqtt.CONF_WS_HEADERS: {"header_1": "custom_header1"},
|
||||
mqtt.CONF_WS_PATH: "/some_path",
|
||||
@ -1590,7 +1597,7 @@ async def test_change_websockets_transport_to_tcp(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_PORT: 1234,
|
||||
CONF_PORT: 1234,
|
||||
mqtt.CONF_TRANSPORT: "tcp",
|
||||
mqtt.CONF_WS_HEADERS: '{"header_1": "custom_header1"}',
|
||||
mqtt.CONF_WS_PATH: "/some_path",
|
||||
@ -1611,7 +1618,7 @@ async def test_change_websockets_transport_to_tcp(
|
||||
# Check config entry result
|
||||
assert config_entry.data == {
|
||||
mqtt.CONF_BROKER: "test-broker",
|
||||
mqtt.CONF_PORT: 1234,
|
||||
CONF_PORT: 1234,
|
||||
mqtt.CONF_TRANSPORT: "tcp",
|
||||
mqtt.CONF_DISCOVERY: True,
|
||||
mqtt.CONF_DISCOVERY_PREFIX: "homeassistant_test",
|
||||
|
@ -6,6 +6,7 @@ from unittest.mock import ANY
|
||||
import pytest
|
||||
|
||||
from homeassistant.components import mqtt
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
|
||||
@ -143,8 +144,8 @@ async def test_entry_diagnostics(
|
||||
{
|
||||
mqtt.CONF_BROKER: "mock-broker",
|
||||
mqtt.CONF_BIRTH_MESSAGE: {},
|
||||
mqtt.CONF_PASSWORD: "hunter2",
|
||||
mqtt.CONF_USERNAME: "my_user",
|
||||
CONF_PASSWORD: "hunter2",
|
||||
CONF_USERNAME: "my_user",
|
||||
}
|
||||
],
|
||||
)
|
||||
|
@ -1338,22 +1338,6 @@ async def test_discovery_expansion_without_encoding_and_value_template_2(
|
||||
|
||||
ABBREVIATIONS_WHITE_LIST = [
|
||||
# MQTT client/server/trigger settings
|
||||
"CONF_BIRTH_MESSAGE",
|
||||
"CONF_BROKER",
|
||||
"CONF_CERTIFICATE",
|
||||
"CONF_CLIENT_CERT",
|
||||
"CONF_CLIENT_ID",
|
||||
"CONF_CLIENT_KEY",
|
||||
"CONF_DISCOVERY",
|
||||
"CONF_DISCOVERY_ID",
|
||||
"CONF_DISCOVERY_PREFIX",
|
||||
"CONF_EMBEDDED",
|
||||
"CONF_KEEPALIVE",
|
||||
"CONF_TLS_INSECURE",
|
||||
"CONF_TRANSPORT",
|
||||
"CONF_WILL_MESSAGE",
|
||||
"CONF_WS_PATH",
|
||||
"CONF_WS_HEADERS",
|
||||
# Integration info
|
||||
"CONF_SUPPORT_URL",
|
||||
# Undocumented device configuration
|
||||
@ -1373,6 +1357,14 @@ ABBREVIATIONS_WHITE_LIST = [
|
||||
"CONF_WHITE_VALUE",
|
||||
]
|
||||
|
||||
EXCLUDED_MODULES = {
|
||||
"const.py",
|
||||
"config.py",
|
||||
"config_flow.py",
|
||||
"device_trigger.py",
|
||||
"trigger.py",
|
||||
}
|
||||
|
||||
|
||||
async def test_missing_discover_abbreviations(
|
||||
hass: HomeAssistant,
|
||||
@ -1383,7 +1375,7 @@ async def test_missing_discover_abbreviations(
|
||||
missing = []
|
||||
regex = re.compile(r"(CONF_[a-zA-Z\d_]*) *= *[\'\"]([a-zA-Z\d_]*)[\'\"]")
|
||||
for fil in Path(mqtt.__file__).parent.rglob("*.py"):
|
||||
if fil.name == "trigger.py":
|
||||
if fil.name in EXCLUDED_MODULES:
|
||||
continue
|
||||
with open(fil, encoding="utf-8") as file:
|
||||
matches = re.findall(regex, file.read())
|
||||
|
@ -31,6 +31,7 @@ from homeassistant.components.sensor import SensorDeviceClass
|
||||
from homeassistant.config_entries import ConfigEntryDisabler, ConfigEntryState
|
||||
from homeassistant.const import (
|
||||
ATTR_ASSUMED_STATE,
|
||||
CONF_PROTOCOL,
|
||||
EVENT_HOMEASSISTANT_STARTED,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
SERVICE_RELOAD,
|
||||
@ -2221,21 +2222,21 @@ async def test_setup_manual_mqtt_with_invalid_config(
|
||||
(
|
||||
{
|
||||
mqtt.CONF_BROKER: "mock-broker",
|
||||
mqtt.CONF_PROTOCOL: "3.1",
|
||||
CONF_PROTOCOL: "3.1",
|
||||
},
|
||||
3,
|
||||
),
|
||||
(
|
||||
{
|
||||
mqtt.CONF_BROKER: "mock-broker",
|
||||
mqtt.CONF_PROTOCOL: "3.1.1",
|
||||
CONF_PROTOCOL: "3.1.1",
|
||||
},
|
||||
4,
|
||||
),
|
||||
(
|
||||
{
|
||||
mqtt.CONF_BROKER: "mock-broker",
|
||||
mqtt.CONF_PROTOCOL: "5",
|
||||
CONF_PROTOCOL: "5",
|
||||
},
|
||||
5,
|
||||
),
|
||||
|
Loading…
x
Reference in New Issue
Block a user