mirror of
https://github.com/home-assistant/core.git
synced 2025-12-24 12:59:34 +00:00
Add Demo valves with position support (#154657)
This commit is contained in:
@@ -3,12 +3,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.valve import ValveEntity, ValveEntityFeature, ValveState
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.event import async_track_utc_time_change
|
||||
|
||||
OPEN_CLOSE_DELAY = 2 # Used to give a realistic open/close experience in frontend
|
||||
|
||||
@@ -23,6 +25,8 @@ async def async_setup_entry(
|
||||
[
|
||||
DemoValve("Front Garden", ValveState.OPEN),
|
||||
DemoValve("Orchard", ValveState.CLOSED),
|
||||
DemoValve("Back Garden", ValveState.CLOSED, position=70),
|
||||
DemoValve("Trees", ValveState.CLOSED, position=30),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -37,6 +41,7 @@ class DemoValve(ValveEntity):
|
||||
name: str,
|
||||
state: str,
|
||||
moveable: bool = True,
|
||||
position: int | None = None,
|
||||
) -> None:
|
||||
"""Initialize the valve."""
|
||||
self._attr_name = name
|
||||
@@ -46,11 +51,23 @@ class DemoValve(ValveEntity):
|
||||
)
|
||||
self._state = state
|
||||
self._moveable = moveable
|
||||
self._attr_reports_position = False
|
||||
self._unsub_listener_valve: CALLBACK_TYPE | None = None
|
||||
self._set_position: int = 0
|
||||
self._position: int = 0
|
||||
if position is None:
|
||||
return
|
||||
|
||||
self._position = self._set_position = position
|
||||
self._attr_reports_position = True
|
||||
self._attr_supported_features |= (
|
||||
ValveEntityFeature.SET_POSITION | ValveEntityFeature.STOP
|
||||
)
|
||||
|
||||
@property
|
||||
def is_open(self) -> bool:
|
||||
"""Return true if valve is open."""
|
||||
return self._state == ValveState.OPEN
|
||||
def current_valve_position(self) -> int:
|
||||
"""Return current position of valve."""
|
||||
return self._position
|
||||
|
||||
@property
|
||||
def is_opening(self) -> bool:
|
||||
@@ -67,11 +84,6 @@ class DemoValve(ValveEntity):
|
||||
"""Return true if valve is closed."""
|
||||
return self._state == ValveState.CLOSED
|
||||
|
||||
@property
|
||||
def reports_position(self) -> bool:
|
||||
"""Return True if entity reports position, False otherwise."""
|
||||
return False
|
||||
|
||||
async def async_open_valve(self, **kwargs: Any) -> None:
|
||||
"""Open the valve."""
|
||||
self._state = ValveState.OPENING
|
||||
@@ -87,3 +99,45 @@ class DemoValve(ValveEntity):
|
||||
await asyncio.sleep(OPEN_CLOSE_DELAY)
|
||||
self._state = ValveState.CLOSED
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_stop_valve(self) -> None:
|
||||
"""Stop the valve."""
|
||||
self._state = ValveState.OPEN if self._position > 0 else ValveState.CLOSED
|
||||
if self._unsub_listener_valve is not None:
|
||||
self._unsub_listener_valve()
|
||||
self._unsub_listener_valve = None
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_set_valve_position(self, position: int) -> None:
|
||||
"""Move the valve to a specific position."""
|
||||
if position == self._position:
|
||||
return
|
||||
if position > self._position:
|
||||
self._state = ValveState.OPENING
|
||||
else:
|
||||
self._state = ValveState.CLOSING
|
||||
|
||||
self._set_position = round(position, -1)
|
||||
self._listen_valve()
|
||||
self.async_write_ha_state()
|
||||
|
||||
@callback
|
||||
def _listen_valve(self) -> None:
|
||||
"""Listen for changes in valve."""
|
||||
if self._unsub_listener_valve is None:
|
||||
self._unsub_listener_valve = async_track_utc_time_change(
|
||||
self.hass, self._time_changed_valve
|
||||
)
|
||||
|
||||
async def _time_changed_valve(self, now: datetime) -> None:
|
||||
"""Track time changes."""
|
||||
if self._state == ValveState.OPENING:
|
||||
self._position += 10
|
||||
elif self._state == ValveState.CLOSING:
|
||||
self._position -= 10
|
||||
|
||||
if self._position in (100, 0, self._set_position):
|
||||
await self.async_stop_valve()
|
||||
return
|
||||
|
||||
self.async_write_ha_state()
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
"""The tests for the Demo valve platform."""
|
||||
|
||||
from datetime import timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.demo import DOMAIN, valve as demo_valve
|
||||
from homeassistant.components.valve import (
|
||||
ATTR_CURRENT_POSITION,
|
||||
ATTR_POSITION,
|
||||
DOMAIN as VALVE_DOMAIN,
|
||||
SERVICE_CLOSE_VALVE,
|
||||
SERVICE_OPEN_VALVE,
|
||||
SERVICE_SET_VALVE_POSITION,
|
||||
ValveState,
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, EVENT_STATE_CHANGED, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from tests.common import async_capture_events
|
||||
from tests.common import async_capture_events, async_fire_time_changed
|
||||
|
||||
FRONT_GARDEN = "valve.front_garden"
|
||||
ORCHARD = "valve.orchard"
|
||||
BACK_GARDEN = "valve.back_garden"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -81,3 +87,59 @@ async def test_opening(hass: HomeAssistant) -> None:
|
||||
|
||||
assert state_changes[1].data["entity_id"] == ORCHARD
|
||||
assert state_changes[1].data["new_state"].state == ValveState.OPEN
|
||||
|
||||
|
||||
async def test_set_valve_position(hass: HomeAssistant) -> None:
|
||||
"""Test moving the valve to a specific position."""
|
||||
state = hass.states.get(BACK_GARDEN)
|
||||
assert state.attributes[ATTR_CURRENT_POSITION] == 70
|
||||
|
||||
# close to 10%
|
||||
await hass.services.async_call(
|
||||
VALVE_DOMAIN,
|
||||
SERVICE_SET_VALVE_POSITION,
|
||||
{ATTR_ENTITY_ID: BACK_GARDEN, ATTR_POSITION: 10},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get(BACK_GARDEN)
|
||||
assert state.state == ValveState.CLOSING
|
||||
|
||||
for _ in range(6):
|
||||
future = dt_util.utcnow() + timedelta(seconds=1)
|
||||
async_fire_time_changed(hass, future)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(BACK_GARDEN)
|
||||
assert state.attributes[ATTR_CURRENT_POSITION] == 10
|
||||
assert state.state == ValveState.OPEN
|
||||
|
||||
# open to 80%
|
||||
await hass.services.async_call(
|
||||
VALVE_DOMAIN,
|
||||
SERVICE_SET_VALVE_POSITION,
|
||||
{ATTR_ENTITY_ID: BACK_GARDEN, ATTR_POSITION: 80},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get(BACK_GARDEN)
|
||||
assert state.state == ValveState.OPENING
|
||||
|
||||
for _ in range(7):
|
||||
future = dt_util.utcnow() + timedelta(seconds=1)
|
||||
async_fire_time_changed(hass, future)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(BACK_GARDEN)
|
||||
assert state.attributes[ATTR_CURRENT_POSITION] == 80
|
||||
assert state.state == ValveState.OPEN
|
||||
|
||||
# test valve is at requested position
|
||||
state_changes = async_capture_events(hass, EVENT_STATE_CHANGED)
|
||||
await hass.services.async_call(
|
||||
VALVE_DOMAIN,
|
||||
SERVICE_SET_VALVE_POSITION,
|
||||
{ATTR_ENTITY_ID: BACK_GARDEN, ATTR_POSITION: 80},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(state_changes) == 0
|
||||
|
||||
Reference in New Issue
Block a user