1
0
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:
Shay Levy
2025-10-19 00:54:02 +03:00
committed by GitHub
parent f1e72c1616
commit 6de2016aa3
2 changed files with 126 additions and 10 deletions

View File

@@ -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()

View File

@@ -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