Add valve entity to gardena (#120160)

Co-authored-by: Franck Nijhof <frenck@frenck.nl>
This commit is contained in:
Joakim Plate 2024-06-22 17:02:53 +02:00 committed by GitHub
parent ed0e0eee71
commit b5a7fb1c33
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 190 additions and 0 deletions

View file

@ -26,6 +26,7 @@ PLATFORMS: list[Platform] = [
Platform.NUMBER,
Platform.SENSOR,
Platform.SWITCH,
Platform.VALVE,
]
LOGGER = logging.getLogger(__name__)
TIMEOUT = 20.0

View file

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

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

View 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',
})
# ---

View 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),
]