Skip Tuya update if it is not relevent (#160407)

This commit is contained in:
epenet
2026-01-09 07:01:43 +01:00
committed by GitHub
parent 1ac2280266
commit 5e43708a40
15 changed files with 455 additions and 68 deletions

View File

@@ -463,6 +463,11 @@ class TuyaBinarySensorEntity(TuyaEntity, BinarySensorEntity):
self._attr_unique_id = f"{super().unique_id}{description.key}"
self._dpcode_wrapper = dpcode_wrapper
@property
def is_on(self) -> bool | None:
"""Return true if sensor is on."""
return self._read_wrapper(self._dpcode_wrapper)
async def _handle_state_update(
self,
updated_status_properties: list[str] | None,
@@ -472,8 +477,3 @@ class TuyaBinarySensorEntity(TuyaEntity, BinarySensorEntity):
if self._dpcode_wrapper.skip_update(self.device, updated_status_properties):
return
self.async_write_ha_state()
@property
def is_on(self) -> bool | None:
"""Return true if sensor is on."""
return self._read_wrapper(self._dpcode_wrapper)

View File

@@ -551,6 +551,16 @@ class TuyaNumberEntity(TuyaEntity, NumberEntity):
"""Return the entity value to represent the entity state."""
return self._read_wrapper(self._dpcode_wrapper)
async def _handle_state_update(
self,
updated_status_properties: list[str] | None,
dp_timestamps: dict | None = None,
) -> None:
"""Handle state update, only if this entity's dpcode was actually updated."""
if self._dpcode_wrapper.skip_update(self.device, updated_status_properties):
return
self.async_write_ha_state()
async def async_set_native_value(self, value: float) -> None:
"""Set new value."""
await self._async_send_wrapper_updates(self._dpcode_wrapper, value)

View File

@@ -407,6 +407,16 @@ class TuyaSelectEntity(TuyaEntity, SelectEntity):
"""Return the selected entity option to represent the entity state."""
return self._read_wrapper(self._dpcode_wrapper)
async def _handle_state_update(
self,
updated_status_properties: list[str] | None,
dp_timestamps: dict | None = None,
) -> None:
"""Handle state update, only if this entity's dpcode was actually updated."""
if self._dpcode_wrapper.skip_update(self.device, updated_status_properties):
return
self.async_write_ha_state()
async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
await self._async_send_wrapper_updates(self._dpcode_wrapper, option)

View File

@@ -1849,3 +1849,13 @@ class TuyaSensorEntity(TuyaEntity, SensorEntity):
def native_value(self) -> StateType:
"""Return the value reported by the sensor."""
return self._read_wrapper(self._dpcode_wrapper)
async def _handle_state_update(
self,
updated_status_properties: list[str] | None,
dp_timestamps: dict | None = None,
) -> None:
"""Handle state update, only if this entity's dpcode was actually updated."""
if self._dpcode_wrapper.skip_update(self.device, updated_status_properties):
return
self.async_write_ha_state()

View File

@@ -107,6 +107,16 @@ class TuyaSirenEntity(TuyaEntity, SirenEntity):
"""Return true if siren is on."""
return self._read_wrapper(self._dpcode_wrapper)
async def _handle_state_update(
self,
updated_status_properties: list[str] | None,
dp_timestamps: dict | None = None,
) -> None:
"""Handle state update, only if this entity's dpcode was actually updated."""
if self._dpcode_wrapper.skip_update(self.device, updated_status_properties):
return
self.async_write_ha_state()
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the siren on."""
await self._async_send_wrapper_updates(self._dpcode_wrapper, True)

View File

@@ -1040,6 +1040,16 @@ class TuyaSwitchEntity(TuyaEntity, SwitchEntity):
"""Return true if switch is on."""
return self._read_wrapper(self._dpcode_wrapper)
async def _handle_state_update(
self,
updated_status_properties: list[str] | None,
dp_timestamps: dict | None = None,
) -> None:
"""Handle state update, only if this entity's dpcode was actually updated."""
if self._dpcode_wrapper.skip_update(self.device, updated_status_properties):
return
self.async_write_ha_state()
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on."""
await self._async_send_wrapper_updates(self._dpcode_wrapper, True)

View File

@@ -137,6 +137,16 @@ class TuyaValveEntity(TuyaEntity, ValveEntity):
return None
return not is_open
async def _handle_state_update(
self,
updated_status_properties: list[str] | None,
dp_timestamps: dict | None = None,
) -> None:
"""Handle state update, only if this entity's dpcode was actually updated."""
if self._dpcode_wrapper.skip_update(self.device, updated_status_properties):
return
self.async_write_ha_state()
async def async_open_valve(self) -> None:
"""Open the valve."""
await self._async_send_wrapper_updates(self._dpcode_wrapper, True)

View File

@@ -6,6 +6,7 @@ import pathlib
from typing import Any
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
from tuya_sharing import CustomerDevice, Manager
from homeassistant.components.tuya import DeviceListener
@@ -61,3 +62,40 @@ async def initialize_entry(
with patch("homeassistant.components.tuya.Manager", return_value=mock_manager):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
async def check_selective_state_update(
hass: HomeAssistant,
mock_device: CustomerDevice,
mock_listener: MockDeviceListener,
freezer: FrozenDateTimeFactory,
*,
entity_id: str,
dpcode: str,
initial_state: str,
updates: dict[str, Any],
expected_state: str,
last_reported: str,
) -> None:
"""Test selective state update.
This test verifies that when an update event comes with properties that do NOT
include the dpcode (e.g., a battery event for a door sensor),
the entity state is not changed and last_reported is not updated.
"""
initial_reported = "2024-01-01T00:00:00+00:00"
assert hass.states.get(entity_id).state == initial_state
assert hass.states.get(entity_id).last_reported.isoformat() == initial_reported
# Force update the dpcode and trigger device update
freezer.tick(30)
mock_device.status[dpcode] = None
await mock_listener.async_send_device_update(hass, mock_device, {})
assert hass.states.get(entity_id).state == initial_state
assert hass.states.get(entity_id).last_reported.isoformat() == initial_reported
# Trigger device update with provided updates
freezer.tick(30)
await mock_listener.async_send_device_update(hass, mock_device, updates)
assert hass.states.get(entity_id).state == expected_state
assert hass.states.get(entity_id).last_reported.isoformat() == last_reported

View File

@@ -14,7 +14,7 @@ from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import MockDeviceListener, initialize_entry
from . import MockDeviceListener, check_selective_state_update, initialize_entry
from tests.common import MockConfigEntry, snapshot_platform
@@ -35,6 +35,54 @@ async def test_platform_setup_and_discovery(
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.parametrize(
"mock_device_code",
["mcs_oxslv1c9"],
)
@pytest.mark.parametrize(
("updates", "expected_state", "last_reported"),
[
# Update without dpcode - state should not change, last_reported stays at initial
({"battery_percentage": 80}, "off", "2024-01-01T00:00:00+00:00"),
# Update with dpcode - state should change, last_reported advances
({"doorcontact_state": True}, "on", "2024-01-01T00:01:00+00:00"),
# Update with multiple properties including dpcode - state should change
(
{"battery_percentage": 50, "doorcontact_state": True},
"on",
"2024-01-01T00:01:00+00:00",
),
],
)
@patch("homeassistant.components.tuya.PLATFORMS", [Platform.BINARY_SENSOR])
@pytest.mark.freeze_time("2024-01-01")
async def test_selective_state_update(
hass: HomeAssistant,
mock_manager: Manager,
mock_config_entry: MockConfigEntry,
mock_device: CustomerDevice,
mock_listener: MockDeviceListener,
freezer: FrozenDateTimeFactory,
updates: dict[str, Any],
expected_state: str,
last_reported: str,
) -> None:
"""Test skip_update/last_reported."""
await initialize_entry(hass, mock_manager, mock_config_entry, mock_device)
await check_selective_state_update(
hass,
mock_device,
mock_listener,
freezer,
entity_id="binary_sensor.window_downstairs_door",
dpcode="doorcontact_state",
initial_state="off",
updates=updates,
expected_state=expected_state,
last_reported=last_reported,
)
@pytest.mark.parametrize(
"mock_device_code",
["cs_zibqa9dutqyaxym2"],
@@ -75,59 +123,3 @@ async def test_bitmap(
assert hass.states.get("binary_sensor.dehumidifier_tank_full").state == tankfull
assert hass.states.get("binary_sensor.dehumidifier_defrost").state == defrost
assert hass.states.get("binary_sensor.dehumidifier_wet").state == wet
@pytest.mark.parametrize(
"mock_device_code",
["mcs_oxslv1c9"],
)
@pytest.mark.parametrize(
("updates", "expected_state", "last_reported"),
[
# Update without dpcode - state should not change, last_reported stays at initial
({"battery_percentage": 80}, "off", "2024-01-01T00:00:00+00:00"),
# Update with dpcode - state should change, last_reported advances
({"doorcontact_state": True}, "on", "2024-01-01T00:01:00+00:00"),
# Update with multiple properties including dpcode - state should change
(
{"battery_percentage": 50, "doorcontact_state": True},
"on",
"2024-01-01T00:01:00+00:00",
),
],
)
@patch("homeassistant.components.tuya.PLATFORMS", [Platform.BINARY_SENSOR])
@pytest.mark.freeze_time("2024-01-01")
async def test_selective_state_update(
hass: HomeAssistant,
mock_manager: Manager,
mock_config_entry: MockConfigEntry,
mock_device: CustomerDevice,
mock_listener: MockDeviceListener,
freezer: FrozenDateTimeFactory,
updates: dict[str, Any],
expected_state: str,
last_reported: str,
) -> None:
"""Test binary sensor only updates when its dpcode is in updated properties.
This test verifies that when an update event comes with properties that do NOT
include the binary sensor's dpcode (e.g., a battery event for a door sensor),
the binary sensor state is not changed and last_reported is not updated.
"""
entity_id = "binary_sensor.window_downstairs_door"
await initialize_entry(hass, mock_manager, mock_config_entry, mock_device)
assert hass.states.get(entity_id).state == "off"
assert (
hass.states.get(entity_id).last_reported.isoformat()
== "2024-01-01T00:00:00+00:00"
)
# Force update the dpcode - should be ignored unless event contains the property
mock_device.status["doorcontact_state"] = True
freezer.tick(60)
await mock_listener.async_send_device_update(hass, mock_device, updates)
assert hass.states.get(entity_id).state == expected_state
assert hass.states.get(entity_id).last_reported.isoformat() == last_reported

View File

@@ -2,8 +2,10 @@
from __future__ import annotations
from typing import Any
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy.assertion import SnapshotAssertion
from tuya_sharing import CustomerDevice, Manager
@@ -17,7 +19,7 @@ from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import initialize_entry
from . import MockDeviceListener, check_selective_state_update, initialize_entry
from tests.common import MockConfigEntry, snapshot_platform
@@ -37,6 +39,54 @@ async def test_platform_setup_and_discovery(
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.parametrize(
"mock_device_code",
["mal_gyitctrjj1kefxp2"],
)
@pytest.mark.parametrize(
("updates", "expected_state", "last_reported"),
[
# Update without dpcode - state should not change, last_reported stays at initial
({"switch_alarm_sound": True}, "15.0", "2024-01-01T00:00:00+00:00"),
# Update with dpcode - state should change, last_reported advances
({"delay_set": 17}, "17.0", "2024-01-01T00:01:00+00:00"),
# Update with multiple properties including dpcode - state should change
(
{"switch_alarm_sound": True, "delay_set": 17},
"17.0",
"2024-01-01T00:01:00+00:00",
),
],
)
@patch("homeassistant.components.tuya.PLATFORMS", [Platform.NUMBER])
@pytest.mark.freeze_time("2024-01-01")
async def test_selective_state_update(
hass: HomeAssistant,
mock_manager: Manager,
mock_config_entry: MockConfigEntry,
mock_device: CustomerDevice,
mock_listener: MockDeviceListener,
freezer: FrozenDateTimeFactory,
updates: dict[str, Any],
expected_state: str,
last_reported: str,
) -> None:
"""Test skip_update/last_reported."""
await initialize_entry(hass, mock_manager, mock_config_entry, mock_device)
await check_selective_state_update(
hass,
mock_device,
mock_listener,
freezer,
entity_id="number.multifunction_alarm_arm_delay",
dpcode="delay_set",
initial_state="15.0",
updates=updates,
expected_state=expected_state,
last_reported=last_reported,
)
@pytest.mark.parametrize(
"mock_device_code",
["mal_gyitctrjj1kefxp2"],

View File

@@ -2,8 +2,10 @@
from __future__ import annotations
from typing import Any
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy.assertion import SnapshotAssertion
from tuya_sharing import CustomerDevice, Manager
@@ -18,7 +20,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import entity_registry as er
from . import initialize_entry
from . import MockDeviceListener, check_selective_state_update, initialize_entry
from tests.common import MockConfigEntry, snapshot_platform
@@ -38,6 +40,54 @@ async def test_platform_setup_and_discovery(
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.parametrize(
"mock_device_code",
["cl_zah67ekd"],
)
@pytest.mark.parametrize(
("updates", "expected_state", "last_reported"),
[
# Update without dpcode - state should not change, last_reported stays at initial
({"control": "stop"}, "forward", "2024-01-01T00:00:00+00:00"),
# Update with dpcode - state should change, last_reported advances
({"control_back_mode": "back"}, "back", "2024-01-01T00:01:00+00:00"),
# Update with multiple properties including dpcode - state should change
(
{"control": "stop", "control_back_mode": "back"},
"back",
"2024-01-01T00:01:00+00:00",
),
],
)
@patch("homeassistant.components.tuya.PLATFORMS", [Platform.SELECT])
@pytest.mark.freeze_time("2024-01-01")
async def test_selective_state_update(
hass: HomeAssistant,
mock_manager: Manager,
mock_config_entry: MockConfigEntry,
mock_device: CustomerDevice,
mock_listener: MockDeviceListener,
freezer: FrozenDateTimeFactory,
updates: dict[str, Any],
expected_state: str,
last_reported: str,
) -> None:
"""Test skip_update/last_reported."""
await initialize_entry(hass, mock_manager, mock_config_entry, mock_device)
await check_selective_state_update(
hass,
mock_device,
mock_listener,
freezer,
entity_id="select.kitchen_blinds_motor_mode",
dpcode="control_back_mode",
initial_state="forward",
updates=updates,
expected_state=expected_state,
last_reported=last_reported,
)
@pytest.mark.parametrize(
"mock_device_code",
["cl_zah67ekd"],

View File

@@ -2,8 +2,10 @@
from __future__ import annotations
from typing import Any
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy.assertion import SnapshotAssertion
from tuya_sharing import CustomerDevice, Manager
@@ -12,7 +14,7 @@ from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import initialize_entry
from . import MockDeviceListener, check_selective_state_update, initialize_entry
from tests.common import MockConfigEntry, snapshot_platform
@@ -31,3 +33,51 @@ async def test_platform_setup_and_discovery(
await initialize_entry(hass, mock_manager, mock_config_entry, mock_devices)
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.parametrize(
"mock_device_code",
["mcs_8yhypbo7"],
)
@pytest.mark.parametrize(
("updates", "expected_state", "last_reported"),
[
# Update without dpcode - state should not change, last_reported stays at initial
({"doorcontact_state": True}, "62.0", "2024-01-01T00:00:00+00:00"),
# Update with dpcode - state should change, last_reported advances
({"battery_percentage": 50}, "50.0", "2024-01-01T00:01:00+00:00"),
# Update with multiple properties including dpcode - state should change
(
{"doorcontact_state": True, "battery_percentage": 50},
"50.0",
"2024-01-01T00:01:00+00:00",
),
],
)
@patch("homeassistant.components.tuya.PLATFORMS", [Platform.SENSOR])
@pytest.mark.freeze_time("2024-01-01")
async def test_selective_state_update(
hass: HomeAssistant,
mock_manager: Manager,
mock_config_entry: MockConfigEntry,
mock_device: CustomerDevice,
mock_listener: MockDeviceListener,
freezer: FrozenDateTimeFactory,
updates: dict[str, Any],
expected_state: str,
last_reported: str,
) -> None:
"""Test skip_update/last_reported."""
await initialize_entry(hass, mock_manager, mock_config_entry, mock_device)
await check_selective_state_update(
hass,
mock_device,
mock_listener,
freezer,
entity_id="sensor.boite_aux_lettres_arriere_battery",
dpcode="battery_percentage",
initial_state="62.0",
updates=updates,
expected_state=expected_state,
last_reported=last_reported,
)

View File

@@ -5,6 +5,7 @@ from __future__ import annotations
from typing import Any
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy.assertion import SnapshotAssertion
from tuya_sharing import CustomerDevice, Manager
@@ -18,7 +19,7 @@ from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import initialize_entry
from . import MockDeviceListener, check_selective_state_update, initialize_entry
from tests.common import MockConfigEntry, snapshot_platform
@@ -38,6 +39,54 @@ async def test_platform_setup_and_discovery(
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.parametrize(
"mock_device_code",
["sp_sdd5f5f2dl5wydjf"],
)
@pytest.mark.parametrize(
("updates", "expected_state", "last_reported"),
[
# Update without dpcode - state should not change, last_reported stays at initial
({"basic_wdr": False}, "off", "2024-01-01T00:00:00+00:00"),
# Update with dpcode - state should change, last_reported advances
({"siren_switch": True}, "on", "2024-01-01T00:01:00+00:00"),
# Update with multiple properties including dpcode - state should change
(
{"basic_wdr": False, "siren_switch": True},
"on",
"2024-01-01T00:01:00+00:00",
),
],
)
@patch("homeassistant.components.tuya.PLATFORMS", [Platform.SIREN])
@pytest.mark.freeze_time("2024-01-01")
async def test_selective_state_update(
hass: HomeAssistant,
mock_manager: Manager,
mock_config_entry: MockConfigEntry,
mock_device: CustomerDevice,
mock_listener: MockDeviceListener,
freezer: FrozenDateTimeFactory,
updates: dict[str, Any],
expected_state: str,
last_reported: str,
) -> None:
"""Test skip_update/last_reported."""
await initialize_entry(hass, mock_manager, mock_config_entry, mock_device)
await check_selective_state_update(
hass,
mock_device,
mock_listener,
freezer,
entity_id="siren.c9",
dpcode="siren_switch",
initial_state="off",
updates=updates,
expected_state=expected_state,
last_reported=last_reported,
)
@patch("homeassistant.components.tuya.PLATFORMS", [Platform.SIREN])
@pytest.mark.parametrize(
"mock_device_code",

View File

@@ -5,6 +5,7 @@ from __future__ import annotations
from typing import Any
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy.assertion import SnapshotAssertion
from tuya_sharing import CustomerDevice, Manager
@@ -19,7 +20,7 @@ from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er, issue_registry as ir
from . import initialize_entry
from . import MockDeviceListener, check_selective_state_update, initialize_entry
from tests.common import MockConfigEntry, snapshot_platform
@@ -39,6 +40,54 @@ async def test_platform_setup_and_discovery(
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.parametrize(
"mock_device_code",
["cz_PGEkBctAbtzKOZng"],
)
@pytest.mark.parametrize(
("updates", "expected_state", "last_reported"),
[
# Update without dpcode - state should not change, last_reported stays at initial
({"countdown_1": 50}, "off", "2024-01-01T00:00:00+00:00"),
# Update with dpcode - state should change, last_reported advances
({"switch": True}, "on", "2024-01-01T00:01:00+00:00"),
# Update with multiple properties including dpcode - state should change
(
{"countdown_1": 50, "switch": True},
"on",
"2024-01-01T00:01:00+00:00",
),
],
)
@patch("homeassistant.components.tuya.PLATFORMS", [Platform.SWITCH])
@pytest.mark.freeze_time("2024-01-01")
async def test_selective_state_update(
hass: HomeAssistant,
mock_manager: Manager,
mock_config_entry: MockConfigEntry,
mock_device: CustomerDevice,
mock_listener: MockDeviceListener,
freezer: FrozenDateTimeFactory,
updates: dict[str, Any],
expected_state: str,
last_reported: str,
) -> None:
"""Test skip_update/last_reported."""
await initialize_entry(hass, mock_manager, mock_config_entry, mock_device)
await check_selective_state_update(
hass,
mock_device,
mock_listener,
freezer,
entity_id="switch.din_socket",
dpcode="switch",
initial_state="off",
updates=updates,
expected_state=expected_state,
last_reported=last_reported,
)
@pytest.mark.parametrize(
("preexisting_entity", "disabled_by", "expected_entity", "expected_issue"),
[

View File

@@ -5,6 +5,7 @@ from __future__ import annotations
from typing import Any
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy.assertion import SnapshotAssertion
from tuya_sharing import CustomerDevice, Manager
@@ -18,7 +19,7 @@ from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import initialize_entry
from . import MockDeviceListener, check_selective_state_update, initialize_entry
from tests.common import MockConfigEntry, snapshot_platform
@@ -38,6 +39,54 @@ async def test_platform_setup_and_discovery(
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.parametrize(
"mock_device_code",
["sfkzq_ed7frwissyqrejic"],
)
@pytest.mark.parametrize(
("updates", "expected_state", "last_reported"),
[
# Update without dpcode - state should not change, last_reported stays at initial
({"battery_percentage": 50}, "open", "2024-01-01T00:00:00+00:00"),
# Update with dpcode - state should change, last_reported advances
({"switch_1": False}, "closed", "2024-01-01T00:01:00+00:00"),
# Update with multiple properties including dpcode - state should change
(
{"battery_percentage": 50, "switch_1": False},
"closed",
"2024-01-01T00:01:00+00:00",
),
],
)
@patch("homeassistant.components.tuya.PLATFORMS", [Platform.VALVE])
@pytest.mark.freeze_time("2024-01-01")
async def test_selective_state_update(
hass: HomeAssistant,
mock_manager: Manager,
mock_config_entry: MockConfigEntry,
mock_device: CustomerDevice,
mock_listener: MockDeviceListener,
freezer: FrozenDateTimeFactory,
updates: dict[str, Any],
expected_state: str,
last_reported: str,
) -> None:
"""Test skip_update/last_reported."""
await initialize_entry(hass, mock_manager, mock_config_entry, mock_device)
await check_selective_state_update(
hass,
mock_device,
mock_listener,
freezer,
entity_id="valve.jie_hashui_fa_valve_1",
dpcode="switch_1",
initial_state="open",
updates=updates,
expected_state=expected_state,
last_reported=last_reported,
)
@patch("homeassistant.components.tuya.PLATFORMS", [Platform.VALVE])
@pytest.mark.parametrize(
"mock_device_code",