Add valve entity to gardena (#120160)
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
This commit is contained in:
parent
ed0e0eee71
commit
b5a7fb1c33
5 changed files with 190 additions and 0 deletions
|
@ -26,6 +26,7 @@ PLATFORMS: list[Platform] = [
|
|||
Platform.NUMBER,
|
||||
Platform.SENSOR,
|
||||
Platform.SWITCH,
|
||||
Platform.VALVE,
|
||||
]
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
TIMEOUT = 20.0
|
||||
|
|
|
@ -50,6 +50,7 @@ class GardenaBluetoothValveSwitch(GardenaBluetoothEntity, SwitchEntity):
|
|||
self._attr_unique_id = f"{coordinator.address}-{Valve.state.uuid}"
|
||||
self._attr_translation_key = "state"
|
||||
self._attr_is_on = None
|
||||
self._attr_entity_registry_enabled_default = False
|
||||
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
self._attr_is_on = self.coordinator.get_cached(Valve.state)
|
||||
|
|
74
homeassistant/components/gardena_bluetooth/valve.py
Normal file
74
homeassistant/components/gardena_bluetooth/valve.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
"""Support for switch entities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from gardena_bluetooth.const import Valve
|
||||
|
||||
from homeassistant.components.valve import ValveEntity, ValveEntityFeature
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import Coordinator, GardenaBluetoothEntity
|
||||
|
||||
FALLBACK_WATERING_TIME_IN_SECONDS = 60 * 60
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up switch based on a config entry."""
|
||||
coordinator: Coordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
entities = []
|
||||
if GardenaBluetoothValve.characteristics.issubset(coordinator.characteristics):
|
||||
entities.append(GardenaBluetoothValve(coordinator))
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class GardenaBluetoothValve(GardenaBluetoothEntity, ValveEntity):
|
||||
"""Representation of a valve switch."""
|
||||
|
||||
_attr_name = None
|
||||
_attr_is_closed: bool | None = None
|
||||
_attr_reports_position = False
|
||||
_attr_supported_features = ValveEntityFeature.OPEN | ValveEntityFeature.CLOSE
|
||||
|
||||
characteristics = {
|
||||
Valve.state.uuid,
|
||||
Valve.manual_watering_time.uuid,
|
||||
Valve.remaining_open_time.uuid,
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: Coordinator,
|
||||
) -> None:
|
||||
"""Initialize the switch."""
|
||||
super().__init__(
|
||||
coordinator, {Valve.state.uuid, Valve.manual_watering_time.uuid}
|
||||
)
|
||||
self._attr_unique_id = f"{coordinator.address}-{Valve.state.uuid}"
|
||||
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
self._attr_is_closed = not self.coordinator.get_cached(Valve.state)
|
||||
super()._handle_coordinator_update()
|
||||
|
||||
async def async_open_valve(self, **kwargs: Any) -> None:
|
||||
"""Turn the entity on."""
|
||||
value = (
|
||||
self.coordinator.get_cached(Valve.manual_watering_time)
|
||||
or FALLBACK_WATERING_TIME_IN_SECONDS
|
||||
)
|
||||
await self.coordinator.write(Valve.remaining_open_time, value)
|
||||
self._attr_is_closed = False
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_close_valve(self, **kwargs: Any) -> None:
|
||||
"""Turn the entity off."""
|
||||
await self.coordinator.write(Valve.remaining_open_time, 0)
|
||||
self._attr_is_closed = True
|
||||
self.async_write_ha_state()
|
29
tests/components/gardena_bluetooth/snapshots/test_valve.ambr
Normal file
29
tests/components/gardena_bluetooth/snapshots/test_valve.ambr
Normal file
|
@ -0,0 +1,29 @@
|
|||
# serializer version: 1
|
||||
# name: test_setup
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mock Title',
|
||||
'supported_features': <ValveEntityFeature: 3>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'valve.mock_title',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'closed',
|
||||
})
|
||||
# ---
|
||||
# name: test_setup.1
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mock Title',
|
||||
'supported_features': <ValveEntityFeature: 3>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'valve.mock_title',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'open',
|
||||
})
|
||||
# ---
|
85
tests/components/gardena_bluetooth/test_valve.py
Normal file
85
tests/components/gardena_bluetooth/test_valve.py
Normal file
|
@ -0,0 +1,85 @@
|
|||
"""Test Gardena Bluetooth valve."""
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
from unittest.mock import Mock, call
|
||||
|
||||
from gardena_bluetooth.const import Valve
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.valve import DOMAIN as VALVE_DOMAIN
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
SERVICE_CLOSE_VALVE,
|
||||
SERVICE_OPEN_VALVE,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import setup_entry
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_switch_chars(mock_read_char_raw):
|
||||
"""Mock data on device."""
|
||||
mock_read_char_raw[Valve.state.uuid] = b"\x00"
|
||||
mock_read_char_raw[Valve.remaining_open_time.uuid] = (
|
||||
Valve.remaining_open_time.encode(0)
|
||||
)
|
||||
mock_read_char_raw[Valve.manual_watering_time.uuid] = (
|
||||
Valve.manual_watering_time.encode(1000)
|
||||
)
|
||||
return mock_read_char_raw
|
||||
|
||||
|
||||
async def test_setup(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
mock_entry: MockConfigEntry,
|
||||
mock_client: Mock,
|
||||
mock_switch_chars: dict[str, bytes],
|
||||
scan_step: Callable[[], Awaitable[None]],
|
||||
) -> None:
|
||||
"""Test setup creates expected entities."""
|
||||
|
||||
entity_id = "valve.mock_title"
|
||||
await setup_entry(hass, mock_entry, [Platform.VALVE])
|
||||
assert hass.states.get(entity_id) == snapshot
|
||||
|
||||
mock_switch_chars[Valve.state.uuid] = b"\x01"
|
||||
await scan_step()
|
||||
assert hass.states.get(entity_id) == snapshot
|
||||
|
||||
|
||||
async def test_switching(
|
||||
hass: HomeAssistant,
|
||||
mock_entry: MockConfigEntry,
|
||||
mock_client: Mock,
|
||||
mock_switch_chars: dict[str, bytes],
|
||||
) -> None:
|
||||
"""Test switching makes correct calls."""
|
||||
|
||||
entity_id = "valve.mock_title"
|
||||
await setup_entry(hass, mock_entry, [Platform.VALVE])
|
||||
assert hass.states.get(entity_id)
|
||||
|
||||
await hass.services.async_call(
|
||||
VALVE_DOMAIN,
|
||||
SERVICE_OPEN_VALVE,
|
||||
{ATTR_ENTITY_ID: entity_id},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
await hass.services.async_call(
|
||||
VALVE_DOMAIN,
|
||||
SERVICE_CLOSE_VALVE,
|
||||
{ATTR_ENTITY_ID: entity_id},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert mock_client.write_char.mock_calls == [
|
||||
call(Valve.remaining_open_time, 1000),
|
||||
call(Valve.remaining_open_time, 0),
|
||||
]
|
Loading…
Add table
Reference in a new issue