|
@@ -3,6 +3,11 @@
|
|
|
from unittest.mock import AsyncMock, Mock
|
|
from unittest.mock import AsyncMock, Mock
|
|
|
|
|
|
|
|
import pytest
|
|
import pytest
|
|
|
|
|
+from homeassistant.components.valve import (
|
|
|
|
|
+ ValveEntityFeature,
|
|
|
|
|
+ ValveState,
|
|
|
|
|
+)
|
|
|
|
|
+from homeassistant.components.valve.const import ValveEntityStateAttribute
|
|
|
from pytest_homeassistant_custom_component.common import MockConfigEntry
|
|
from pytest_homeassistant_custom_component.common import MockConfigEntry
|
|
|
|
|
|
|
|
from custom_components.tuya_local.const import (
|
|
from custom_components.tuya_local.const import (
|
|
@@ -11,8 +16,29 @@ from custom_components.tuya_local.const import (
|
|
|
CONF_TYPE,
|
|
CONF_TYPE,
|
|
|
DOMAIN,
|
|
DOMAIN,
|
|
|
)
|
|
)
|
|
|
|
|
+from custom_components.tuya_local.helpers.device_config import get_config
|
|
|
from custom_components.tuya_local.valve import TuyaLocalValve, async_setup_entry
|
|
from custom_components.tuya_local.valve import TuyaLocalValve, async_setup_entry
|
|
|
|
|
|
|
|
|
|
+from .helpers import assert_device_properties_set, mock_device
|
|
|
|
|
+
|
|
|
|
|
+FRANKEVER_DPS = {
|
|
|
|
|
+ "1": True,
|
|
|
|
|
+ "9": 0,
|
|
|
|
|
+ "38": "memory",
|
|
|
|
|
+ "101": 100,
|
|
|
|
|
+ "102": 100,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _make_frankever_valve(mocker, dps=None):
|
|
|
|
|
+ """Create a valve using the FrankEver position-controlled profile."""
|
|
|
|
|
+ config = get_config("frankever_watervalve")
|
|
|
|
|
+ entity_config = next(
|
|
|
|
|
+ entity for entity in config.all_entities() if entity.entity == "valve"
|
|
|
|
|
+ )
|
|
|
|
|
+ device = mock_device(dps or FRANKEVER_DPS, mocker)
|
|
|
|
|
+ return TuyaLocalValve(device, entity_config), device
|
|
|
|
|
+
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.asyncio
|
|
|
async def test_init_entry(hass):
|
|
async def test_init_entry(hass):
|
|
@@ -66,6 +92,92 @@ async def test_init_entry_fails_if_device_has_no_valve(hass):
|
|
|
m_add_entities.assert_not_called()
|
|
m_add_entities.assert_not_called()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+@pytest.mark.parametrize(
|
|
|
|
|
+ ("position", "expected_state"),
|
|
|
|
|
+ [
|
|
|
|
|
+ (0, ValveState.CLOSED),
|
|
|
|
|
+ (20, ValveState.OPEN),
|
|
|
|
|
+ (100, ValveState.OPEN),
|
|
|
|
|
+ ],
|
|
|
|
|
+)
|
|
|
|
|
+def test_separate_current_position(mocker, position, expected_state):
|
|
|
|
|
+ """Current position is reported by the read-only feedback DP."""
|
|
|
|
|
+ dps = {**FRANKEVER_DPS, "101": position, "102": position}
|
|
|
|
|
+ valve, _ = _make_frankever_valve(mocker, dps)
|
|
|
|
|
+
|
|
|
|
|
+ assert valve.current_valve_position == position
|
|
|
|
|
+ assert valve.is_closed is (position == 0)
|
|
|
|
|
+ assert valve.state == expected_state
|
|
|
|
|
+ assert valve.state_attributes == {
|
|
|
|
|
+ ValveEntityStateAttribute.IS_CLOSED: position == 0,
|
|
|
|
|
+ ValveEntityStateAttribute.CURRENT_POSITION: position,
|
|
|
|
|
+ }
|
|
|
|
|
+ assert valve.supported_features == (
|
|
|
|
|
+ ValveEntityFeature.OPEN
|
|
|
|
|
+ | ValveEntityFeature.CLOSE
|
|
|
|
|
+ | ValveEntityFeature.SET_POSITION
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@pytest.mark.asyncio
|
|
|
|
|
+async def test_set_position_writes_target_only(mocker):
|
|
|
|
|
+ """Setting a target position does not change the separate switch."""
|
|
|
|
|
+ valve, device = _make_frankever_valve(mocker)
|
|
|
|
|
+
|
|
|
|
|
+ async with assert_device_properties_set(device, {"101": 50}):
|
|
|
|
|
+ await valve.async_set_valve_position(50)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@pytest.mark.asyncio
|
|
|
|
|
+async def test_set_zero_writes_target_only(mocker):
|
|
|
|
|
+ """Setting a zero target position does not change the separate switch."""
|
|
|
|
|
+ valve, device = _make_frankever_valve(mocker)
|
|
|
|
|
+
|
|
|
|
|
+ async with assert_device_properties_set(device, {"101": 0}):
|
|
|
|
|
+ await valve.async_set_valve_position(0)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@pytest.mark.asyncio
|
|
|
|
|
+async def test_open_uses_switch(mocker):
|
|
|
|
|
+ """Opening uses the switch without changing a non-zero target position."""
|
|
|
|
|
+ valve, device = _make_frankever_valve(mocker)
|
|
|
|
|
+
|
|
|
|
|
+ async with assert_device_properties_set(device, {"1": True}):
|
|
|
|
|
+ await valve.async_open_valve()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@pytest.mark.asyncio
|
|
|
|
|
+async def test_close_uses_switch(mocker):
|
|
|
|
|
+ """Closing uses the switch without changing the target position."""
|
|
|
|
|
+ valve, device = _make_frankever_valve(mocker)
|
|
|
|
|
+
|
|
|
|
|
+ async with assert_device_properties_set(device, {"1": False}):
|
|
|
|
|
+ await valve.async_close_valve()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_frankever_product_match_is_preferred():
|
|
|
|
|
+ """The explicit product match is stronger than the Tellur DPS match."""
|
|
|
|
|
+ frankever = get_config("frankever_watervalve")
|
|
|
|
|
+ tellur = get_config("tellur_tll331501_watervalve")
|
|
|
|
|
+ product_ids = ["nzx0kku9d6eq59nt"]
|
|
|
|
|
+
|
|
|
|
|
+ assert frankever.matches_product(product_ids[0])
|
|
|
|
|
+ assert frankever.match_quality(FRANKEVER_DPS, product_ids) == 101
|
|
|
|
|
+ assert tellur.match_quality(FRANKEVER_DPS) == 100
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_frankever_auxiliary_entity_values(mocker):
|
|
|
|
|
+ """Countdown and initial-state mappings retain their observed values."""
|
|
|
|
|
+ config = get_config("frankever_watervalve")
|
|
|
|
|
+ entities = {entity.entity: entity for entity in config.all_entities()}
|
|
|
|
|
+ device = mock_device(FRANKEVER_DPS, mocker)
|
|
|
|
|
+
|
|
|
|
|
+ assert entities["time"].find_dps("second").get_value(device) == 0
|
|
|
|
|
+ initial_state = entities["select"].find_dps("option")
|
|
|
|
|
+ assert initial_state.get_value(device) == "memory"
|
|
|
|
|
+ assert initial_state.values(device) == ["off", "on", "memory"]
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.asyncio
|
|
|
async def test_init_entry_fails_if_config_is_missing(hass):
|
|
async def test_init_entry_fails_if_config_is_missing(hass):
|
|
|
"""Test initialisation when device has no matching entity"""
|
|
"""Test initialisation when device has no matching entity"""
|