mirror of
https://github.com/home-assistant/core.git
synced 2026-02-15 07:36:16 +00:00
Fix unavailable status in Tuya (#162709)
This commit is contained in:
@@ -51,9 +51,14 @@ class DeviceWrapper[T]:
|
||||
) -> bool:
|
||||
"""Determine if the wrapper should skip an update.
|
||||
|
||||
The default is to always skip, unless overridden in subclasses.
|
||||
The default is to always skip if updated properties is given,
|
||||
unless overridden in subclasses.
|
||||
"""
|
||||
return True
|
||||
# If updated_status_properties is None, we should not skip,
|
||||
# as we don't have information on what was updated
|
||||
# This happens for example on online/offline updates, where
|
||||
# we still want to update the entity state
|
||||
return updated_status_properties is not None
|
||||
|
||||
def read_device_status(self, device: CustomerDevice) -> T | None:
|
||||
"""Read device status and convert to a Home Assistant value."""
|
||||
@@ -88,9 +93,13 @@ class DPCodeWrapper(DeviceWrapper):
|
||||
By default, skip if updated_status_properties is given and
|
||||
does not include this dpcode.
|
||||
"""
|
||||
# If updated_status_properties is None, we should not skip,
|
||||
# as we don't have information on what was updated
|
||||
# This happens for example on online/offline updates, where
|
||||
# we still want to update the entity state
|
||||
return (
|
||||
updated_status_properties is None
|
||||
or self.dpcode not in updated_status_properties
|
||||
updated_status_properties is not None
|
||||
and self.dpcode not in updated_status_properties
|
||||
)
|
||||
|
||||
def _convert_value_to_raw_value(self, device: CustomerDevice, value: Any) -> Any:
|
||||
@@ -250,6 +259,13 @@ class DPCodeDeltaIntegerWrapper(DPCodeIntegerWrapper):
|
||||
|
||||
Processes delta accumulation before determining if update should be skipped.
|
||||
"""
|
||||
# If updated_status_properties is None, we should not skip,
|
||||
# as we don't have information on what was updated
|
||||
# This happens for example on online/offline updates, where
|
||||
# we still want to update the entity state but we have nothing
|
||||
# to accumulate, so we return False to not skip the update
|
||||
if updated_status_properties is None:
|
||||
return False
|
||||
if (
|
||||
super().skip_update(device, updated_status_properties, dp_timestamps)
|
||||
or dp_timestamps is None
|
||||
|
||||
@@ -16,6 +16,7 @@ from tuya_sharing import (
|
||||
)
|
||||
|
||||
from homeassistant.components.tuya import DOMAIN, DeviceListener
|
||||
from homeassistant.const import STATE_UNAVAILABLE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.json import json_dumps
|
||||
from homeassistant.util import dt as dt_util
|
||||
@@ -38,10 +39,13 @@ class MockDeviceListener(DeviceListener):
|
||||
device: CustomerDevice,
|
||||
updated_status_properties: dict[str, Any] | None = None,
|
||||
dp_timestamps: dict[str, int] | None = None,
|
||||
*,
|
||||
online: bool | None = None,
|
||||
) -> None:
|
||||
"""Mock update device method."""
|
||||
property_list: list[str] = []
|
||||
if updated_status_properties:
|
||||
property_list: list[str] | None = None
|
||||
if updated_status_properties is not None:
|
||||
property_list = []
|
||||
for key, value in updated_status_properties.items():
|
||||
if key not in device.status:
|
||||
raise ValueError(
|
||||
@@ -49,6 +53,8 @@ class MockDeviceListener(DeviceListener):
|
||||
)
|
||||
device.status[key] = value
|
||||
property_list.append(key)
|
||||
if online is not None:
|
||||
device.online = online
|
||||
self.update_device(device, property_list, dp_timestamps)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -185,15 +191,30 @@ async def check_selective_state_update(
|
||||
the entity state is not changed and last_reported is not updated.
|
||||
"""
|
||||
initial_reported = "2024-01-01T00:00:00+00:00"
|
||||
unavailable_reported = "2024-01-01T00:00:10+00:00"
|
||||
available_reported = "2024-01-01T00:00:20+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)
|
||||
# Trigger device offline
|
||||
freezer.tick(10)
|
||||
await mock_listener.async_send_device_update(hass, mock_device, online=False)
|
||||
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
|
||||
assert hass.states.get(entity_id).last_reported.isoformat() == unavailable_reported
|
||||
|
||||
# Trigger device online
|
||||
freezer.tick(10)
|
||||
await mock_listener.async_send_device_update(hass, mock_device, online=True)
|
||||
assert hass.states.get(entity_id).state == initial_state
|
||||
assert hass.states.get(entity_id).last_reported.isoformat() == available_reported
|
||||
|
||||
# Force update the dpcode and trigger device update without the dpcode
|
||||
# in updated properties - state should not change
|
||||
freezer.tick(10)
|
||||
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
|
||||
assert hass.states.get(entity_id).last_reported.isoformat() == available_reported
|
||||
|
||||
# Trigger device update with provided updates
|
||||
freezer.tick(30)
|
||||
|
||||
@@ -42,8 +42,9 @@ async def test_platform_setup_and_discovery(
|
||||
@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 without dpcode - state should not change, last_reported stays
|
||||
# at available_reported
|
||||
({"battery_percentage": 80}, "off", "2024-01-01T00:00:20+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
|
||||
|
||||
@@ -46,8 +46,9 @@ async def test_platform_setup_and_discovery(
|
||||
@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 without dpcode - state should not change, last_reported stays
|
||||
# at available_reported
|
||||
({"switch_alarm_sound": True}, "15.0", "2024-01-01T00:00:20+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
|
||||
|
||||
@@ -47,8 +47,9 @@ async def test_platform_setup_and_discovery(
|
||||
@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 without dpcode - state should not change, last_reported stays
|
||||
# at available_reported
|
||||
({"control": "stop"}, "forward", "2024-01-01T00:00:20+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
|
||||
|
||||
@@ -43,8 +43,9 @@ async def test_platform_setup_and_discovery(
|
||||
@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 without dpcode - state should not change, last_reported stays
|
||||
# at available_reported
|
||||
({"doorcontact_state": True}, "62.0", "2024-01-01T00:00:20+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
|
||||
|
||||
@@ -46,8 +46,9 @@ async def test_platform_setup_and_discovery(
|
||||
@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 without dpcode - state should not change, last_reported stays
|
||||
# at available_reported
|
||||
({"basic_wdr": False}, "off", "2024-01-01T00:00:20+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
|
||||
|
||||
@@ -47,8 +47,9 @@ async def test_platform_setup_and_discovery(
|
||||
@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 without dpcode - state should not change, last_reported stays
|
||||
# at available_reported
|
||||
({"countdown_1": 50}, "off", "2024-01-01T00:00:20+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
|
||||
|
||||
@@ -46,8 +46,9 @@ async def test_platform_setup_and_discovery(
|
||||
@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 without dpcode - state should not change, last_reported stays
|
||||
# at available_reported
|
||||
({"battery_percentage": 50}, "open", "2024-01-01T00:00:20+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
|
||||
|
||||
Reference in New Issue
Block a user