mirror of
https://github.com/home-assistant/core.git
synced 2025-10-21 01:29:46 +00:00
Add valve group support (#154749)
Co-authored-by: Franck Nijhof <git@frenck.dev>
This commit is contained in:
@@ -72,6 +72,7 @@ PLATFORMS = [
|
||||
Platform.NOTIFY,
|
||||
Platform.SENSOR,
|
||||
Platform.SWITCH,
|
||||
Platform.VALVE,
|
||||
]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@@ -35,6 +35,7 @@ from .media_player import MediaPlayerGroup, async_create_preview_media_player
|
||||
from .notify import async_create_preview_notify
|
||||
from .sensor import async_create_preview_sensor
|
||||
from .switch import async_create_preview_switch
|
||||
from .valve import async_create_preview_valve
|
||||
|
||||
_STATISTIC_MEASURES = [
|
||||
"last",
|
||||
@@ -172,6 +173,7 @@ GROUP_TYPES = [
|
||||
"notify",
|
||||
"sensor",
|
||||
"switch",
|
||||
"valve",
|
||||
]
|
||||
|
||||
|
||||
@@ -253,6 +255,11 @@ CONFIG_FLOW = {
|
||||
preview="group",
|
||||
validate_user_input=set_group_type("switch"),
|
||||
),
|
||||
"valve": SchemaFlowFormStep(
|
||||
basic_group_config_schema("valve"),
|
||||
preview="group",
|
||||
validate_user_input=set_group_type("valve"),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -302,6 +309,10 @@ OPTIONS_FLOW = {
|
||||
partial(light_switch_options_schema, "switch"),
|
||||
preview="group",
|
||||
),
|
||||
"valve": SchemaFlowFormStep(
|
||||
partial(basic_group_options_schema, "valve"),
|
||||
preview="group",
|
||||
),
|
||||
}
|
||||
|
||||
PREVIEW_OPTIONS_SCHEMA: dict[str, vol.Schema] = {}
|
||||
@@ -321,6 +332,7 @@ CREATE_PREVIEW_ENTITY: dict[
|
||||
"notify": async_create_preview_notify,
|
||||
"sensor": async_create_preview_sensor,
|
||||
"switch": async_create_preview_switch,
|
||||
"valve": async_create_preview_valve,
|
||||
}
|
||||
|
||||
|
||||
|
@@ -16,7 +16,8 @@
|
||||
"media_player": "Media player group",
|
||||
"notify": "Notify group",
|
||||
"sensor": "Sensor group",
|
||||
"switch": "Switch group"
|
||||
"switch": "Switch group",
|
||||
"valve": "Valve group"
|
||||
}
|
||||
},
|
||||
"binary_sensor": {
|
||||
@@ -127,6 +128,18 @@
|
||||
"data_description": {
|
||||
"all": "[%key:component::group::config::step::binary_sensor::data_description::all%]"
|
||||
}
|
||||
},
|
||||
"valve": {
|
||||
"title": "[%key:component::group::config::step::user::title%]",
|
||||
"data": {
|
||||
"all": "[%key:component::group::config::step::binary_sensor::data::all%]",
|
||||
"entities": "[%key:component::group::config::step::binary_sensor::data::entities%]",
|
||||
"hide_members": "[%key:component::group::config::step::binary_sensor::data::hide_members%]",
|
||||
"name": "[%key:common::config_flow::data::name%]"
|
||||
},
|
||||
"data_description": {
|
||||
"all": "[%key:component::group::config::step::binary_sensor::data_description::all%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -212,6 +225,16 @@
|
||||
"data_description": {
|
||||
"all": "[%key:component::group::config::step::binary_sensor::data_description::all%]"
|
||||
}
|
||||
},
|
||||
"valve": {
|
||||
"data": {
|
||||
"all": "[%key:component::group::config::step::binary_sensor::data::all%]",
|
||||
"entities": "[%key:component::group::config::step::binary_sensor::data::entities%]",
|
||||
"hide_members": "[%key:component::group::config::step::binary_sensor::data::hide_members%]"
|
||||
},
|
||||
"data_description": {
|
||||
"all": "[%key:component::group::config::step::binary_sensor::data_description::all%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
262
homeassistant/components/group/valve.py
Normal file
262
homeassistant/components/group/valve.py
Normal file
@@ -0,0 +1,262 @@
|
||||
"""Platform allowing several valves to be grouped into one valve."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.valve import (
|
||||
ATTR_CURRENT_POSITION,
|
||||
ATTR_POSITION,
|
||||
DOMAIN as VALVE_DOMAIN,
|
||||
PLATFORM_SCHEMA as VALVE_PLATFORM_SCHEMA,
|
||||
ValveEntity,
|
||||
ValveEntityFeature,
|
||||
ValveState,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_SUPPORTED_FEATURES,
|
||||
CONF_ENTITIES,
|
||||
CONF_NAME,
|
||||
CONF_UNIQUE_ID,
|
||||
SERVICE_CLOSE_VALVE,
|
||||
SERVICE_OPEN_VALVE,
|
||||
SERVICE_SET_VALVE_POSITION,
|
||||
SERVICE_STOP_VALVE,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, State, callback
|
||||
from homeassistant.helpers import config_validation as cv, entity_registry as er
|
||||
from homeassistant.helpers.entity_platform import (
|
||||
AddConfigEntryEntitiesCallback,
|
||||
AddEntitiesCallback,
|
||||
)
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from .entity import GroupEntity
|
||||
from .util import reduce_attribute
|
||||
|
||||
KEY_OPEN_CLOSE = "open_close"
|
||||
KEY_STOP = "stop"
|
||||
KEY_SET_POSITION = "set_position"
|
||||
|
||||
DEFAULT_NAME = "Valve Group"
|
||||
|
||||
# No limit on parallel updates to enable a group calling another group
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
PLATFORM_SCHEMA = VALVE_PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_ENTITIES): cv.entities_domain(VALVE_DOMAIN),
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the Valve Group platform."""
|
||||
async_add_entities(
|
||||
[
|
||||
ValveGroup(
|
||||
config.get(CONF_UNIQUE_ID), config[CONF_NAME], config[CONF_ENTITIES]
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Initialize Valve Group config entry."""
|
||||
registry = er.async_get(hass)
|
||||
entities = er.async_validate_entity_ids(
|
||||
registry, config_entry.options[CONF_ENTITIES]
|
||||
)
|
||||
|
||||
async_add_entities(
|
||||
[ValveGroup(config_entry.entry_id, config_entry.title, entities)]
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
def async_create_preview_valve(
|
||||
hass: HomeAssistant, name: str, validated_config: dict[str, Any]
|
||||
) -> ValveGroup:
|
||||
"""Create a preview valve."""
|
||||
return ValveGroup(
|
||||
None,
|
||||
name,
|
||||
validated_config[CONF_ENTITIES],
|
||||
)
|
||||
|
||||
|
||||
class ValveGroup(GroupEntity, ValveEntity):
|
||||
"""Representation of a ValveGroup."""
|
||||
|
||||
_attr_available: bool = False
|
||||
_attr_current_valve_position: int | None = None
|
||||
_attr_is_closed: bool | None = None
|
||||
_attr_is_closing: bool | None = False
|
||||
_attr_is_opening: bool | None = False
|
||||
_attr_reports_position: bool = False
|
||||
|
||||
def __init__(self, unique_id: str | None, name: str, entities: list[str]) -> None:
|
||||
"""Initialize a ValveGroup entity."""
|
||||
self._entity_ids = entities
|
||||
self._valves: dict[str, set[str]] = {
|
||||
KEY_OPEN_CLOSE: set(),
|
||||
KEY_STOP: set(),
|
||||
KEY_SET_POSITION: set(),
|
||||
}
|
||||
|
||||
self._attr_name = name
|
||||
self._attr_extra_state_attributes = {ATTR_ENTITY_ID: entities}
|
||||
self._attr_unique_id = unique_id
|
||||
|
||||
@callback
|
||||
def async_update_supported_features(
|
||||
self,
|
||||
entity_id: str,
|
||||
new_state: State | None,
|
||||
) -> None:
|
||||
"""Update dictionaries with supported features."""
|
||||
if not new_state:
|
||||
for values in self._valves.values():
|
||||
values.discard(entity_id)
|
||||
return
|
||||
|
||||
features = new_state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
|
||||
if features & (ValveEntityFeature.OPEN | ValveEntityFeature.CLOSE):
|
||||
self._valves[KEY_OPEN_CLOSE].add(entity_id)
|
||||
else:
|
||||
self._valves[KEY_OPEN_CLOSE].discard(entity_id)
|
||||
if features & (ValveEntityFeature.STOP):
|
||||
self._valves[KEY_STOP].add(entity_id)
|
||||
else:
|
||||
self._valves[KEY_STOP].discard(entity_id)
|
||||
if features & (ValveEntityFeature.SET_POSITION):
|
||||
self._valves[KEY_SET_POSITION].add(entity_id)
|
||||
else:
|
||||
self._valves[KEY_SET_POSITION].discard(entity_id)
|
||||
|
||||
async def async_open_valve(self) -> None:
|
||||
"""Open the valves."""
|
||||
data = {ATTR_ENTITY_ID: self._valves[KEY_OPEN_CLOSE]}
|
||||
await self.hass.services.async_call(
|
||||
VALVE_DOMAIN, SERVICE_OPEN_VALVE, data, blocking=True, context=self._context
|
||||
)
|
||||
|
||||
async def async_handle_open_valve(self) -> None: # type: ignore[misc]
|
||||
"""Open the valves.
|
||||
|
||||
Override the base class to avoid calling the set position service
|
||||
for all valves. Transfer the service call to the base class and let
|
||||
it decide if the valve uses set position or open service.
|
||||
"""
|
||||
await self.async_open_valve()
|
||||
|
||||
async def async_close_valve(self) -> None:
|
||||
"""Close valves."""
|
||||
data = {ATTR_ENTITY_ID: self._valves[KEY_OPEN_CLOSE]}
|
||||
await self.hass.services.async_call(
|
||||
VALVE_DOMAIN,
|
||||
SERVICE_CLOSE_VALVE,
|
||||
data,
|
||||
blocking=True,
|
||||
context=self._context,
|
||||
)
|
||||
|
||||
async def async_handle_close_valve(self) -> None: # type: ignore[misc]
|
||||
"""Close the valves.
|
||||
|
||||
Override the base class to avoid calling the set position service
|
||||
for all valves. Transfer the service call to the base class and let
|
||||
it decide if the valve uses set position or close service.
|
||||
"""
|
||||
await self.async_close_valve()
|
||||
|
||||
async def async_set_valve_position(self, position: int) -> None:
|
||||
"""Move the valves to a specific position."""
|
||||
data = {
|
||||
ATTR_ENTITY_ID: self._valves[KEY_SET_POSITION],
|
||||
ATTR_POSITION: position,
|
||||
}
|
||||
await self.hass.services.async_call(
|
||||
VALVE_DOMAIN,
|
||||
SERVICE_SET_VALVE_POSITION,
|
||||
data,
|
||||
blocking=True,
|
||||
context=self._context,
|
||||
)
|
||||
|
||||
async def async_stop_valve(self) -> None:
|
||||
"""Stop the valves."""
|
||||
data = {ATTR_ENTITY_ID: self._valves[KEY_STOP]}
|
||||
await self.hass.services.async_call(
|
||||
VALVE_DOMAIN, SERVICE_STOP_VALVE, data, blocking=True, context=self._context
|
||||
)
|
||||
|
||||
@callback
|
||||
def async_update_group_state(self) -> None:
|
||||
"""Update state and attributes."""
|
||||
states = [
|
||||
state
|
||||
for entity_id in self._entity_ids
|
||||
if (state := self.hass.states.get(entity_id)) is not None
|
||||
]
|
||||
|
||||
# Set group as unavailable if all members are unavailable or missing
|
||||
self._attr_available = any(state.state != STATE_UNAVAILABLE for state in states)
|
||||
|
||||
self._attr_is_closed = True
|
||||
self._attr_is_closing = False
|
||||
self._attr_is_opening = False
|
||||
self._attr_reports_position = False
|
||||
self._update_assumed_state_from_members()
|
||||
for state in states:
|
||||
if state.attributes.get(ATTR_CURRENT_POSITION) is not None:
|
||||
self._attr_reports_position = True
|
||||
if state.state == ValveState.OPEN:
|
||||
self._attr_is_closed = False
|
||||
continue
|
||||
if state.state == ValveState.CLOSED:
|
||||
continue
|
||||
if state.state == ValveState.CLOSING:
|
||||
self._attr_is_closing = True
|
||||
continue
|
||||
if state.state == ValveState.OPENING:
|
||||
self._attr_is_opening = True
|
||||
continue
|
||||
|
||||
valid_state = any(
|
||||
state.state not in (STATE_UNKNOWN, STATE_UNAVAILABLE) for state in states
|
||||
)
|
||||
if not valid_state:
|
||||
# Set as unknown if all members are unknown or unavailable
|
||||
self._attr_is_closed = None
|
||||
|
||||
self._attr_current_valve_position = reduce_attribute(
|
||||
states, ATTR_CURRENT_POSITION
|
||||
)
|
||||
|
||||
supported_features = ValveEntityFeature(0)
|
||||
if self._valves[KEY_OPEN_CLOSE]:
|
||||
supported_features |= ValveEntityFeature.OPEN | ValveEntityFeature.CLOSE
|
||||
if self._valves[KEY_STOP]:
|
||||
supported_features |= ValveEntityFeature.STOP
|
||||
if self._valves[KEY_SET_POSITION]:
|
||||
supported_features |= ValveEntityFeature.SET_POSITION
|
||||
self._attr_supported_features = supported_features
|
@@ -1,4 +1,4 @@
|
||||
"""Test the Switch config flow."""
|
||||
"""Test the Group config flow."""
|
||||
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
@@ -60,6 +60,7 @@ from tests.typing import WebSocketGenerator
|
||||
),
|
||||
("switch", "on", "on", {}, {}, {"all": False}, {}),
|
||||
("switch", "on", "on", {}, {"all": True}, {"all": True}, {}),
|
||||
("valve", "open", "open", {}, {}, {}, {}),
|
||||
],
|
||||
)
|
||||
async def test_config_flow(
|
||||
@@ -148,6 +149,7 @@ async def test_config_flow(
|
||||
("notify", {}),
|
||||
("media_player", {}),
|
||||
("switch", {}),
|
||||
("valve", {}),
|
||||
],
|
||||
)
|
||||
async def test_config_flow_hides_members(
|
||||
@@ -222,6 +224,7 @@ async def test_config_flow_hides_members(
|
||||
{"ignore_non_numeric": False, "type": "sum"},
|
||||
),
|
||||
("switch", "on", {"all": False}, {}),
|
||||
("valve", "open", {}, {}),
|
||||
],
|
||||
)
|
||||
async def test_options(
|
||||
@@ -404,6 +407,7 @@ async def test_all_options(
|
||||
("notify", {}),
|
||||
("media_player", {}),
|
||||
("switch", {}),
|
||||
("valve", {}),
|
||||
],
|
||||
)
|
||||
async def test_options_flow_hides_members(
|
||||
@@ -487,6 +491,7 @@ LOCK_ATTRS = [{"supported_features": 1}, {}]
|
||||
NOTIFY_ATTRS = [{"supported_features": 0}, {}]
|
||||
MEDIA_PLAYER_ATTRS = [{"supported_features": 0}, {}]
|
||||
SENSOR_ATTRS = [{"icon": "mdi:calculator"}, {"max_entity_id": "sensor.input_two"}]
|
||||
VALVE_ATTRS = [{"supported_features": 0}, {}]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -503,6 +508,7 @@ SENSOR_ATTRS = [{"icon": "mdi:calculator"}, {"max_entity_id": "sensor.input_two"
|
||||
("media_player", {}, ["on", "off"], "on", MEDIA_PLAYER_ATTRS),
|
||||
("sensor", {"type": "max"}, ["10", "20"], "20.0", SENSOR_ATTRS),
|
||||
("switch", {}, ["on", "off"], "on", [{}, {}]),
|
||||
("valve", {}, ["open", "closed"], "open", VALVE_ATTRS),
|
||||
],
|
||||
)
|
||||
async def test_config_flow_preview(
|
||||
@@ -621,6 +627,7 @@ async def test_config_flow_preview(
|
||||
SENSOR_ATTRS,
|
||||
),
|
||||
("switch", {}, {}, ["on", "off"], "on", [{}, {}]),
|
||||
("valve", {}, {}, ["open", "closed"], "open", VALVE_ATTRS),
|
||||
],
|
||||
)
|
||||
async def test_option_flow_preview(
|
||||
|
688
tests/components/group/test_valve.py
Normal file
688
tests/components/group/test_valve.py
Normal file
@@ -0,0 +1,688 @@
|
||||
"""The tests for the group valve platform."""
|
||||
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.group.valve import DEFAULT_NAME
|
||||
from homeassistant.components.valve import (
|
||||
ATTR_CURRENT_POSITION,
|
||||
ATTR_POSITION,
|
||||
DOMAIN as VALVE_DOMAIN,
|
||||
ValveState,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ASSUMED_STATE,
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_FRIENDLY_NAME,
|
||||
ATTR_SUPPORTED_FEATURES,
|
||||
CONF_ENTITIES,
|
||||
CONF_UNIQUE_ID,
|
||||
SERVICE_CLOSE_VALVE,
|
||||
SERVICE_OPEN_VALVE,
|
||||
SERVICE_SET_VALVE_POSITION,
|
||||
SERVICE_STOP_VALVE,
|
||||
SERVICE_TOGGLE,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from tests.common import assert_setup_component, async_fire_time_changed
|
||||
|
||||
VALVE_GROUP = "valve.valve_group"
|
||||
DEMO_VALVE1 = "valve.front_garden"
|
||||
DEMO_VALVE2 = "valve.orchard"
|
||||
DEMO_VALVE_POS1 = "valve.back_garden"
|
||||
DEMO_VALVE_POS2 = "valve.trees"
|
||||
|
||||
CONFIG_ALL = {
|
||||
VALVE_DOMAIN: [
|
||||
{"platform": "demo"},
|
||||
{
|
||||
"platform": "group",
|
||||
CONF_ENTITIES: [DEMO_VALVE1, DEMO_VALVE2, DEMO_VALVE_POS1, DEMO_VALVE_POS2],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
CONFIG_POS = {
|
||||
VALVE_DOMAIN: [
|
||||
{"platform": "demo"},
|
||||
{
|
||||
"platform": "group",
|
||||
CONF_ENTITIES: [DEMO_VALVE_POS1, DEMO_VALVE_POS2],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
CONFIG_ATTRIBUTES = {
|
||||
VALVE_DOMAIN: {
|
||||
"platform": "group",
|
||||
CONF_ENTITIES: [DEMO_VALVE1, DEMO_VALVE2, DEMO_VALVE_POS1, DEMO_VALVE_POS2],
|
||||
CONF_UNIQUE_ID: "unique_identifier",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(scope="module", autouse=True)
|
||||
def patch_demo_open_close_delay():
|
||||
"""Patch demo valve open/close delay."""
|
||||
with patch("homeassistant.components.demo.valve.OPEN_CLOSE_DELAY", 0):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def setup_comp(
|
||||
hass: HomeAssistant, config_count: tuple[dict[str, Any], int]
|
||||
) -> None:
|
||||
"""Set up group valve component."""
|
||||
config, count = config_count
|
||||
with assert_setup_component(count, VALVE_DOMAIN):
|
||||
await async_setup_component(hass, VALVE_DOMAIN, config)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("config_count", [(CONFIG_ATTRIBUTES, 1)])
|
||||
@pytest.mark.usefixtures("setup_comp")
|
||||
async def test_state(hass: HomeAssistant) -> None:
|
||||
"""Test handling of state.
|
||||
|
||||
The group state is unknown if all group members are unknown or unavailable.
|
||||
Otherwise, the group state is opening if at least one group member is opening.
|
||||
Otherwise, the group state is closing if at least one group member is closing.
|
||||
Otherwise, the group state is open if at least one group member is open.
|
||||
Otherwise, the group state is closed.
|
||||
"""
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
# No entity has a valid state -> group state unavailable
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
assert state.attributes[ATTR_FRIENDLY_NAME] == DEFAULT_NAME
|
||||
assert ATTR_ENTITY_ID not in state.attributes
|
||||
assert ATTR_ASSUMED_STATE not in state.attributes
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
|
||||
assert ATTR_CURRENT_POSITION not in state.attributes
|
||||
|
||||
# Test group members exposed as attribute
|
||||
hass.states.async_set(DEMO_VALVE1, STATE_UNKNOWN, {})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.attributes[ATTR_ENTITY_ID] == [
|
||||
DEMO_VALVE1,
|
||||
DEMO_VALVE2,
|
||||
DEMO_VALVE_POS1,
|
||||
DEMO_VALVE_POS2,
|
||||
]
|
||||
|
||||
# The group state is unavailable if all group members are unavailable.
|
||||
hass.states.async_set(DEMO_VALVE1, STATE_UNAVAILABLE, {})
|
||||
hass.states.async_set(DEMO_VALVE_POS1, STATE_UNAVAILABLE, {})
|
||||
hass.states.async_set(DEMO_VALVE_POS2, STATE_UNAVAILABLE, {})
|
||||
hass.states.async_set(DEMO_VALVE2, STATE_UNAVAILABLE, {})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
# The group state is unknown if all group members are unknown or unavailable.
|
||||
for state_1 in (STATE_UNAVAILABLE, STATE_UNKNOWN):
|
||||
for state_2 in (STATE_UNAVAILABLE, STATE_UNKNOWN):
|
||||
for state_3 in (STATE_UNAVAILABLE, STATE_UNKNOWN):
|
||||
hass.states.async_set(DEMO_VALVE1, state_1, {})
|
||||
hass.states.async_set(DEMO_VALVE_POS1, state_2, {})
|
||||
hass.states.async_set(DEMO_VALVE_POS2, state_3, {})
|
||||
hass.states.async_set(DEMO_VALVE2, STATE_UNKNOWN, {})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.state == STATE_UNKNOWN
|
||||
|
||||
# At least one member opening -> group opening
|
||||
for state_1 in (
|
||||
ValveState.CLOSED,
|
||||
ValveState.CLOSING,
|
||||
ValveState.OPEN,
|
||||
ValveState.OPENING,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
):
|
||||
for state_2 in (
|
||||
ValveState.CLOSED,
|
||||
ValveState.CLOSING,
|
||||
ValveState.OPEN,
|
||||
ValveState.OPENING,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
):
|
||||
for state_3 in (
|
||||
ValveState.CLOSED,
|
||||
ValveState.CLOSING,
|
||||
ValveState.OPEN,
|
||||
ValveState.OPENING,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
):
|
||||
hass.states.async_set(DEMO_VALVE1, state_1, {})
|
||||
hass.states.async_set(DEMO_VALVE_POS1, state_2, {})
|
||||
hass.states.async_set(DEMO_VALVE_POS2, state_3, {})
|
||||
hass.states.async_set(DEMO_VALVE2, ValveState.OPENING, {})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.state == ValveState.OPENING
|
||||
|
||||
# At least one member closing -> group closing
|
||||
for state_1 in (
|
||||
ValveState.CLOSED,
|
||||
ValveState.CLOSING,
|
||||
ValveState.OPEN,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
):
|
||||
for state_2 in (
|
||||
ValveState.CLOSED,
|
||||
ValveState.CLOSING,
|
||||
ValveState.OPEN,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
):
|
||||
for state_3 in (
|
||||
ValveState.CLOSED,
|
||||
ValveState.CLOSING,
|
||||
ValveState.OPEN,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
):
|
||||
hass.states.async_set(DEMO_VALVE1, state_1, {})
|
||||
hass.states.async_set(DEMO_VALVE_POS1, state_2, {})
|
||||
hass.states.async_set(DEMO_VALVE_POS2, state_3, {})
|
||||
hass.states.async_set(DEMO_VALVE2, ValveState.CLOSING, {})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.state == ValveState.CLOSING
|
||||
|
||||
# At least one member open -> group open
|
||||
for state_1 in (
|
||||
ValveState.CLOSED,
|
||||
ValveState.OPEN,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
):
|
||||
for state_2 in (
|
||||
ValveState.CLOSED,
|
||||
ValveState.OPEN,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
):
|
||||
for state_3 in (
|
||||
ValveState.CLOSED,
|
||||
ValveState.OPEN,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
):
|
||||
hass.states.async_set(DEMO_VALVE1, state_1, {})
|
||||
hass.states.async_set(DEMO_VALVE_POS1, state_2, {})
|
||||
hass.states.async_set(DEMO_VALVE_POS2, state_3, {})
|
||||
hass.states.async_set(DEMO_VALVE2, ValveState.OPEN, {})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.state == ValveState.OPEN
|
||||
|
||||
# At least one member closed -> group closed
|
||||
for state_1 in (ValveState.CLOSED, STATE_UNAVAILABLE, STATE_UNKNOWN):
|
||||
for state_2 in (ValveState.CLOSED, STATE_UNAVAILABLE, STATE_UNKNOWN):
|
||||
for state_3 in (ValveState.CLOSED, STATE_UNAVAILABLE, STATE_UNKNOWN):
|
||||
hass.states.async_set(DEMO_VALVE1, state_1, {})
|
||||
hass.states.async_set(DEMO_VALVE_POS1, state_2, {})
|
||||
hass.states.async_set(DEMO_VALVE_POS2, state_3, {})
|
||||
hass.states.async_set(DEMO_VALVE2, ValveState.CLOSED, {})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.state == ValveState.CLOSED
|
||||
|
||||
# All group members removed from the state machine -> unavailable
|
||||
hass.states.async_remove(DEMO_VALVE1)
|
||||
hass.states.async_remove(DEMO_VALVE_POS1)
|
||||
hass.states.async_remove(DEMO_VALVE_POS2)
|
||||
hass.states.async_remove(DEMO_VALVE2)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
@pytest.mark.parametrize("config_count", [(CONFIG_ATTRIBUTES, 1)])
|
||||
@pytest.mark.usefixtures("setup_comp")
|
||||
async def test_attributes(
|
||||
hass: HomeAssistant, entity_registry: er.EntityRegistry
|
||||
) -> None:
|
||||
"""Test handling of state attributes."""
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
assert state.attributes[ATTR_FRIENDLY_NAME] == DEFAULT_NAME
|
||||
assert ATTR_ENTITY_ID not in state.attributes
|
||||
assert ATTR_ASSUMED_STATE not in state.attributes
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
|
||||
assert ATTR_CURRENT_POSITION not in state.attributes
|
||||
|
||||
# Set entity as closed
|
||||
hass.states.async_set(DEMO_VALVE1, ValveState.CLOSED, {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.state == ValveState.CLOSED
|
||||
assert state.attributes[ATTR_ENTITY_ID] == [
|
||||
DEMO_VALVE1,
|
||||
DEMO_VALVE2,
|
||||
DEMO_VALVE_POS1,
|
||||
DEMO_VALVE_POS2,
|
||||
]
|
||||
|
||||
# Set entity as opening
|
||||
hass.states.async_set(DEMO_VALVE1, ValveState.OPENING, {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.state == ValveState.OPENING
|
||||
|
||||
# Set entity as closing
|
||||
hass.states.async_set(DEMO_VALVE1, ValveState.CLOSING, {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.state == ValveState.CLOSING
|
||||
|
||||
# Set entity as unknown again
|
||||
hass.states.async_set(DEMO_VALVE1, STATE_UNKNOWN, {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.state == STATE_UNKNOWN
|
||||
|
||||
# Add Entity that supports open / close / stop
|
||||
hass.states.async_set(DEMO_VALVE1, ValveState.OPEN, {ATTR_SUPPORTED_FEATURES: 11})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.state == ValveState.OPEN
|
||||
assert ATTR_ASSUMED_STATE not in state.attributes
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 11
|
||||
assert ATTR_CURRENT_POSITION not in state.attributes
|
||||
|
||||
# Add Entity that supports set_valve_position
|
||||
hass.states.async_set(
|
||||
DEMO_VALVE_POS1,
|
||||
ValveState.OPEN,
|
||||
{ATTR_SUPPORTED_FEATURES: 4, ATTR_CURRENT_POSITION: 70},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.state == ValveState.OPEN
|
||||
assert ATTR_ASSUMED_STATE not in state.attributes
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 15
|
||||
assert state.attributes[ATTR_CURRENT_POSITION] == 70
|
||||
|
||||
### Test state when group members have different states ###
|
||||
|
||||
# Valves
|
||||
hass.states.async_remove(DEMO_VALVE_POS1)
|
||||
hass.states.async_remove(DEMO_VALVE_POS2)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.state == ValveState.OPEN
|
||||
assert ATTR_ASSUMED_STATE not in state.attributes
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 11
|
||||
assert ATTR_CURRENT_POSITION not in state.attributes
|
||||
|
||||
# Test entity registry integration
|
||||
entry = entity_registry.async_get(VALVE_GROUP)
|
||||
assert entry
|
||||
assert entry.unique_id == "unique_identifier"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("config_count", [(CONFIG_ALL, 2)])
|
||||
@pytest.mark.usefixtures("setup_comp")
|
||||
async def test_open_valves(hass: HomeAssistant) -> None:
|
||||
"""Test open valve function."""
|
||||
await hass.services.async_call(
|
||||
VALVE_DOMAIN, SERVICE_OPEN_VALVE, {ATTR_ENTITY_ID: VALVE_GROUP}, blocking=True
|
||||
)
|
||||
|
||||
for _ in range(10):
|
||||
future = dt_util.utcnow() + timedelta(seconds=1)
|
||||
async_fire_time_changed(hass, future)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.state == ValveState.OPEN
|
||||
assert state.attributes[ATTR_CURRENT_POSITION] == 100
|
||||
|
||||
assert hass.states.get(DEMO_VALVE1).state == ValveState.OPEN
|
||||
assert hass.states.get(DEMO_VALVE_POS1).attributes[ATTR_CURRENT_POSITION] == 100
|
||||
assert hass.states.get(DEMO_VALVE_POS2).attributes[ATTR_CURRENT_POSITION] == 100
|
||||
|
||||
|
||||
@pytest.mark.parametrize("config_count", [(CONFIG_ALL, 2)])
|
||||
@pytest.mark.usefixtures("setup_comp")
|
||||
async def test_close_valves(hass: HomeAssistant) -> None:
|
||||
"""Test close valve function."""
|
||||
await hass.services.async_call(
|
||||
VALVE_DOMAIN, SERVICE_CLOSE_VALVE, {ATTR_ENTITY_ID: VALVE_GROUP}, blocking=True
|
||||
)
|
||||
|
||||
for _ in range(10):
|
||||
future = dt_util.utcnow() + timedelta(seconds=1)
|
||||
async_fire_time_changed(hass, future)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.state == ValveState.CLOSED
|
||||
assert state.attributes[ATTR_CURRENT_POSITION] == 0
|
||||
|
||||
assert hass.states.get(DEMO_VALVE1).state == ValveState.CLOSED
|
||||
assert hass.states.get(DEMO_VALVE_POS1).attributes[ATTR_CURRENT_POSITION] == 0
|
||||
assert hass.states.get(DEMO_VALVE_POS2).attributes[ATTR_CURRENT_POSITION] == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("config_count", [(CONFIG_ALL, 2)])
|
||||
@pytest.mark.usefixtures("setup_comp")
|
||||
async def test_toggle_valves(hass: HomeAssistant) -> None:
|
||||
"""Test toggle valve function."""
|
||||
# Start valves in open state
|
||||
await hass.services.async_call(
|
||||
VALVE_DOMAIN, SERVICE_OPEN_VALVE, {ATTR_ENTITY_ID: VALVE_GROUP}, blocking=True
|
||||
)
|
||||
for _ in range(10):
|
||||
future = dt_util.utcnow() + timedelta(seconds=1)
|
||||
async_fire_time_changed(hass, future)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.state == ValveState.OPEN
|
||||
|
||||
# Toggle will close valves
|
||||
await hass.services.async_call(
|
||||
VALVE_DOMAIN, SERVICE_TOGGLE, {ATTR_ENTITY_ID: VALVE_GROUP}, blocking=True
|
||||
)
|
||||
for _ in range(10):
|
||||
future = dt_util.utcnow() + timedelta(seconds=1)
|
||||
async_fire_time_changed(hass, future)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.state == ValveState.CLOSED
|
||||
assert state.attributes[ATTR_CURRENT_POSITION] == 0
|
||||
|
||||
assert hass.states.get(DEMO_VALVE1).state == ValveState.CLOSED
|
||||
assert hass.states.get(DEMO_VALVE_POS1).attributes[ATTR_CURRENT_POSITION] == 0
|
||||
assert hass.states.get(DEMO_VALVE_POS2).attributes[ATTR_CURRENT_POSITION] == 0
|
||||
|
||||
# Toggle again will open valves
|
||||
await hass.services.async_call(
|
||||
VALVE_DOMAIN, SERVICE_TOGGLE, {ATTR_ENTITY_ID: VALVE_GROUP}, blocking=True
|
||||
)
|
||||
for _ in range(10):
|
||||
future = dt_util.utcnow() + timedelta(seconds=1)
|
||||
async_fire_time_changed(hass, future)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.state == ValveState.OPEN
|
||||
assert state.attributes[ATTR_CURRENT_POSITION] == 100
|
||||
|
||||
assert hass.states.get(DEMO_VALVE1).state == ValveState.OPEN
|
||||
assert hass.states.get(DEMO_VALVE_POS1).attributes[ATTR_CURRENT_POSITION] == 100
|
||||
assert hass.states.get(DEMO_VALVE_POS2).attributes[ATTR_CURRENT_POSITION] == 100
|
||||
|
||||
|
||||
@pytest.mark.parametrize("config_count", [(CONFIG_ALL, 2)])
|
||||
@pytest.mark.usefixtures("setup_comp")
|
||||
async def test_stop_valves(hass: HomeAssistant) -> None:
|
||||
"""Test stop valve function."""
|
||||
await hass.services.async_call(
|
||||
VALVE_DOMAIN, SERVICE_OPEN_VALVE, {ATTR_ENTITY_ID: VALVE_GROUP}, blocking=True
|
||||
)
|
||||
future = dt_util.utcnow() + timedelta(seconds=1)
|
||||
async_fire_time_changed(hass, future)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.state == ValveState.OPENING
|
||||
|
||||
await hass.services.async_call(
|
||||
VALVE_DOMAIN, SERVICE_STOP_VALVE, {ATTR_ENTITY_ID: VALVE_GROUP}, blocking=True
|
||||
)
|
||||
future = dt_util.utcnow() + timedelta(seconds=1)
|
||||
async_fire_time_changed(hass, future)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.state == ValveState.OPEN
|
||||
assert state.attributes[ATTR_CURRENT_POSITION] == 60 # (40 + 80) / 2
|
||||
|
||||
assert hass.states.get(DEMO_VALVE1).state == ValveState.OPEN
|
||||
assert hass.states.get(DEMO_VALVE_POS1).attributes[ATTR_CURRENT_POSITION] == 80
|
||||
assert hass.states.get(DEMO_VALVE_POS2).attributes[ATTR_CURRENT_POSITION] == 40
|
||||
|
||||
|
||||
@pytest.mark.parametrize("config_count", [(CONFIG_ALL, 2)])
|
||||
@pytest.mark.usefixtures("setup_comp")
|
||||
async def test_set_valve_position(hass: HomeAssistant) -> None:
|
||||
"""Test set valve position function."""
|
||||
await hass.services.async_call(
|
||||
VALVE_DOMAIN,
|
||||
SERVICE_SET_VALVE_POSITION,
|
||||
{ATTR_ENTITY_ID: VALVE_GROUP, ATTR_POSITION: 50},
|
||||
blocking=True,
|
||||
)
|
||||
for _ in range(4):
|
||||
future = dt_util.utcnow() + timedelta(seconds=1)
|
||||
async_fire_time_changed(hass, future)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.state == ValveState.OPEN
|
||||
assert state.attributes[ATTR_CURRENT_POSITION] == 50
|
||||
|
||||
assert hass.states.get(DEMO_VALVE1).state == ValveState.OPEN
|
||||
assert hass.states.get(DEMO_VALVE_POS1).attributes[ATTR_CURRENT_POSITION] == 50
|
||||
assert hass.states.get(DEMO_VALVE_POS2).attributes[ATTR_CURRENT_POSITION] == 50
|
||||
|
||||
|
||||
@pytest.mark.parametrize("config_count", [(CONFIG_POS, 2)])
|
||||
@pytest.mark.usefixtures("setup_comp")
|
||||
async def test_is_opening_closing(hass: HomeAssistant) -> None:
|
||||
"""Test is_opening property."""
|
||||
await hass.services.async_call(
|
||||
VALVE_DOMAIN, SERVICE_OPEN_VALVE, {ATTR_ENTITY_ID: VALVE_GROUP}, blocking=True
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Both valves opening -> opening
|
||||
assert hass.states.get(DEMO_VALVE_POS1).state == ValveState.OPENING
|
||||
assert hass.states.get(DEMO_VALVE_POS2).state == ValveState.OPENING
|
||||
assert hass.states.get(VALVE_GROUP).state == ValveState.OPENING
|
||||
|
||||
for _ in range(10):
|
||||
future = dt_util.utcnow() + timedelta(seconds=1)
|
||||
async_fire_time_changed(hass, future)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await hass.services.async_call(
|
||||
VALVE_DOMAIN, SERVICE_CLOSE_VALVE, {ATTR_ENTITY_ID: VALVE_GROUP}, blocking=True
|
||||
)
|
||||
|
||||
# Both valves closing -> closing
|
||||
assert hass.states.get(DEMO_VALVE_POS1).state == ValveState.CLOSING
|
||||
assert hass.states.get(DEMO_VALVE_POS2).state == ValveState.CLOSING
|
||||
assert hass.states.get(VALVE_GROUP).state == ValveState.CLOSING
|
||||
|
||||
hass.states.async_set(
|
||||
DEMO_VALVE_POS1, ValveState.OPENING, {ATTR_SUPPORTED_FEATURES: 11}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Closing + Opening -> Opening
|
||||
assert hass.states.get(DEMO_VALVE_POS2).state == ValveState.CLOSING
|
||||
assert hass.states.get(DEMO_VALVE_POS1).state == ValveState.OPENING
|
||||
assert hass.states.get(VALVE_GROUP).state == ValveState.OPENING
|
||||
|
||||
hass.states.async_set(
|
||||
DEMO_VALVE_POS1, ValveState.CLOSING, {ATTR_SUPPORTED_FEATURES: 11}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Both valves closing -> closing
|
||||
assert hass.states.get(DEMO_VALVE_POS2).state == ValveState.CLOSING
|
||||
assert hass.states.get(DEMO_VALVE_POS1).state == ValveState.CLOSING
|
||||
assert hass.states.get(VALVE_GROUP).state == ValveState.CLOSING
|
||||
|
||||
# Closed + Closing -> Closing
|
||||
hass.states.async_set(
|
||||
DEMO_VALVE_POS1, ValveState.CLOSED, {ATTR_SUPPORTED_FEATURES: 11}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get(DEMO_VALVE_POS2).state == ValveState.CLOSING
|
||||
assert hass.states.get(DEMO_VALVE_POS1).state == ValveState.CLOSED
|
||||
assert hass.states.get(VALVE_GROUP).state == ValveState.CLOSING
|
||||
|
||||
# Open + Closing -> Closing
|
||||
hass.states.async_set(
|
||||
DEMO_VALVE_POS1, ValveState.OPEN, {ATTR_SUPPORTED_FEATURES: 11}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get(DEMO_VALVE_POS2).state == ValveState.CLOSING
|
||||
assert hass.states.get(DEMO_VALVE_POS1).state == ValveState.OPEN
|
||||
assert hass.states.get(VALVE_GROUP).state == ValveState.CLOSING
|
||||
|
||||
# Closed + Opening -> Closing
|
||||
hass.states.async_set(
|
||||
DEMO_VALVE_POS2, ValveState.OPENING, {ATTR_SUPPORTED_FEATURES: 11}
|
||||
)
|
||||
hass.states.async_set(
|
||||
DEMO_VALVE_POS1, ValveState.CLOSED, {ATTR_SUPPORTED_FEATURES: 11}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get(DEMO_VALVE_POS2).state == ValveState.OPENING
|
||||
assert hass.states.get(DEMO_VALVE_POS1).state == ValveState.CLOSED
|
||||
assert hass.states.get(VALVE_GROUP).state == ValveState.OPENING
|
||||
|
||||
# Open + Opening -> Closing
|
||||
hass.states.async_set(
|
||||
DEMO_VALVE_POS1, ValveState.OPEN, {ATTR_SUPPORTED_FEATURES: 11}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get(DEMO_VALVE_POS2).state == ValveState.OPENING
|
||||
assert hass.states.get(DEMO_VALVE_POS1).state == ValveState.OPEN
|
||||
assert hass.states.get(VALVE_GROUP).state == ValveState.OPENING
|
||||
|
||||
|
||||
@pytest.mark.parametrize("config_count", [(CONFIG_ATTRIBUTES, 1)])
|
||||
@pytest.mark.usefixtures("setup_comp")
|
||||
async def test_assumed_state(hass: HomeAssistant) -> None:
|
||||
"""Test assumed_state attribute behavior."""
|
||||
# No members with assumed_state -> group doesn't have assumed_state in attributes
|
||||
hass.states.async_set(DEMO_VALVE1, ValveState.OPEN, {})
|
||||
hass.states.async_set(DEMO_VALVE_POS1, ValveState.OPEN, {})
|
||||
hass.states.async_set(DEMO_VALVE_POS2, ValveState.CLOSED, {})
|
||||
hass.states.async_set(DEMO_VALVE2, ValveState.CLOSED, {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert ATTR_ASSUMED_STATE not in state.attributes
|
||||
|
||||
# One member with assumed_state=True -> group has assumed_state=True
|
||||
hass.states.async_set(DEMO_VALVE1, ValveState.OPEN, {ATTR_ASSUMED_STATE: True})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.attributes.get(ATTR_ASSUMED_STATE) is True
|
||||
|
||||
# Multiple members with assumed_state=True -> group has assumed_state=True
|
||||
hass.states.async_set(
|
||||
DEMO_VALVE_POS2, ValveState.CLOSED, {ATTR_ASSUMED_STATE: True}
|
||||
)
|
||||
hass.states.async_set(DEMO_VALVE2, ValveState.CLOSED, {ATTR_ASSUMED_STATE: True})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.attributes.get(ATTR_ASSUMED_STATE) is True
|
||||
|
||||
# Unavailable member with assumed_state=True -> group has assumed_state=True
|
||||
hass.states.async_set(DEMO_VALVE1, ValveState.OPEN, {})
|
||||
hass.states.async_set(DEMO_VALVE_POS2, ValveState.CLOSED, {})
|
||||
hass.states.async_set(DEMO_VALVE2, STATE_UNAVAILABLE, {ATTR_ASSUMED_STATE: True})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.attributes.get(ATTR_ASSUMED_STATE) is True
|
||||
|
||||
# Unknown member with assumed_state=True -> group has assumed_state=True
|
||||
hass.states.async_set(DEMO_VALVE2, STATE_UNKNOWN, {ATTR_ASSUMED_STATE: True})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.attributes.get(ATTR_ASSUMED_STATE) is True
|
||||
|
||||
# All members without assumed_state -> group doesn't have assumed_state in attributes
|
||||
hass.states.async_set(DEMO_VALVE2, ValveState.CLOSED, {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert ATTR_ASSUMED_STATE not in state.attributes
|
||||
|
||||
|
||||
async def test_nested_group(hass: HomeAssistant) -> None:
|
||||
"""Test nested valve group."""
|
||||
await async_setup_component(
|
||||
hass,
|
||||
VALVE_DOMAIN,
|
||||
{
|
||||
VALVE_DOMAIN: [
|
||||
{"platform": "demo"},
|
||||
{
|
||||
"platform": "group",
|
||||
"entities": ["valve.bedroom_group"],
|
||||
"name": "Nested Group",
|
||||
},
|
||||
{
|
||||
"platform": "group",
|
||||
CONF_ENTITIES: [DEMO_VALVE_POS1, DEMO_VALVE_POS2],
|
||||
"name": "Bedroom Group",
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("valve.bedroom_group")
|
||||
assert state is not None
|
||||
assert state.state == ValveState.OPEN
|
||||
assert state.attributes.get(ATTR_ENTITY_ID) == [DEMO_VALVE_POS1, DEMO_VALVE_POS2]
|
||||
|
||||
state = hass.states.get("valve.nested_group")
|
||||
assert state is not None
|
||||
assert state.state == ValveState.OPEN
|
||||
assert state.attributes.get(ATTR_ENTITY_ID) == ["valve.bedroom_group"]
|
||||
|
||||
# Test controlling the nested group
|
||||
async with asyncio.timeout(0.5):
|
||||
await hass.services.async_call(
|
||||
VALVE_DOMAIN,
|
||||
SERVICE_CLOSE_VALVE,
|
||||
{ATTR_ENTITY_ID: "valve.nested_group"},
|
||||
blocking=True,
|
||||
)
|
||||
assert hass.states.get(DEMO_VALVE_POS1).state == ValveState.CLOSING
|
||||
assert hass.states.get(DEMO_VALVE_POS2).state == ValveState.CLOSING
|
||||
assert hass.states.get("valve.bedroom_group").state == ValveState.CLOSING
|
||||
assert hass.states.get("valve.nested_group").state == ValveState.CLOSING
|
Reference in New Issue
Block a user