mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Improve yeelight stability by moving timeout handling to upstream library (#56432)
This commit is contained in:
parent
f3ad4ca0cc
commit
9e2a29dc37
@ -164,8 +164,8 @@ UPDATE_REQUEST_PROPERTIES = [
|
||||
"active_mode",
|
||||
]
|
||||
|
||||
BULB_NETWORK_EXCEPTIONS = (socket.error, asyncio.TimeoutError)
|
||||
BULB_EXCEPTIONS = (BulbException, *BULB_NETWORK_EXCEPTIONS)
|
||||
BULB_NETWORK_EXCEPTIONS = (socket.error,)
|
||||
BULB_EXCEPTIONS = (BulbException, asyncio.TimeoutError, *BULB_NETWORK_EXCEPTIONS)
|
||||
|
||||
|
||||
PLATFORMS = ["binary_sensor", "light"]
|
||||
@ -612,9 +612,6 @@ class YeelightDevice:
|
||||
@property
|
||||
def is_nightlight_enabled(self) -> bool:
|
||||
"""Return true / false if nightlight is currently enabled."""
|
||||
if self.bulb is None:
|
||||
return False
|
||||
|
||||
# Only ceiling lights have active_mode, from SDK docs:
|
||||
# active_mode 0: daylight mode / 1: moonlight mode (ceiling light only)
|
||||
if self._active_mode is not None:
|
||||
@ -652,23 +649,22 @@ class YeelightDevice:
|
||||
|
||||
async def _async_update_properties(self):
|
||||
"""Read new properties from the device."""
|
||||
if not self.bulb:
|
||||
return
|
||||
|
||||
try:
|
||||
await self.bulb.async_get_properties(UPDATE_REQUEST_PROPERTIES)
|
||||
self._available = True
|
||||
if not self._initialized:
|
||||
self._initialized = True
|
||||
async_dispatcher_send(self._hass, DEVICE_INITIALIZED.format(self._host))
|
||||
except BULB_EXCEPTIONS as ex:
|
||||
except BULB_NETWORK_EXCEPTIONS as ex:
|
||||
if self._available: # just inform once
|
||||
_LOGGER.error(
|
||||
"Unable to update device %s, %s: %s", self._host, self.name, ex
|
||||
)
|
||||
self._available = False
|
||||
|
||||
return self._available
|
||||
except BULB_EXCEPTIONS as ex:
|
||||
_LOGGER.debug(
|
||||
"Unable to update device %s, %s: %s", self._host, self.name, ex
|
||||
)
|
||||
|
||||
async def async_setup(self):
|
||||
"""Fetch capabilities and setup name if available."""
|
||||
|
@ -6,7 +6,7 @@ import math
|
||||
|
||||
import voluptuous as vol
|
||||
import yeelight
|
||||
from yeelight import Bulb, BulbException, Flow, RGBTransition, SleepTransition, flows
|
||||
from yeelight import Bulb, Flow, RGBTransition, SleepTransition, flows
|
||||
from yeelight.enums import BulbType, LightType, PowerMode, SceneClass
|
||||
|
||||
from homeassistant.components.light import (
|
||||
@ -50,6 +50,7 @@ from . import (
|
||||
ATTR_COUNT,
|
||||
ATTR_MODE_MUSIC,
|
||||
ATTR_TRANSITIONS,
|
||||
BULB_EXCEPTIONS,
|
||||
BULB_NETWORK_EXCEPTIONS,
|
||||
CONF_FLOW_PARAMS,
|
||||
CONF_MODE_MUSIC,
|
||||
@ -250,7 +251,7 @@ def _async_cmd(func):
|
||||
raise HomeAssistantError(
|
||||
f"Error when calling {func.__name__} for bulb {self.device.name} at {self.device.host}: {ex}"
|
||||
) from ex
|
||||
except BulbException as ex:
|
||||
except BULB_EXCEPTIONS as ex:
|
||||
# The bulb likely responded but had an error
|
||||
raise HomeAssistantError(
|
||||
f"Error when calling {func.__name__} for bulb {self.device.name} at {self.device.host}: {ex}"
|
||||
|
@ -2,7 +2,7 @@
|
||||
"domain": "yeelight",
|
||||
"name": "Yeelight",
|
||||
"documentation": "https://www.home-assistant.io/integrations/yeelight",
|
||||
"requirements": ["yeelight==0.7.4", "async-upnp-client==0.21.2"],
|
||||
"requirements": ["yeelight==0.7.5", "async-upnp-client==0.21.2"],
|
||||
"codeowners": ["@rytilahti", "@zewelor", "@shenxn", "@starkillerOG"],
|
||||
"config_flow": true,
|
||||
"dependencies": ["network"],
|
||||
|
@ -2448,7 +2448,7 @@ yalesmartalarmclient==0.3.4
|
||||
yalexs==1.1.13
|
||||
|
||||
# homeassistant.components.yeelight
|
||||
yeelight==0.7.4
|
||||
yeelight==0.7.5
|
||||
|
||||
# homeassistant.components.yeelightsunflower
|
||||
yeelightsunflower==0.0.10
|
||||
|
@ -1389,7 +1389,7 @@ yalesmartalarmclient==0.3.4
|
||||
yalexs==1.1.13
|
||||
|
||||
# homeassistant.components.yeelight
|
||||
yeelight==0.7.4
|
||||
yeelight==0.7.5
|
||||
|
||||
# homeassistant.components.youless
|
||||
youless-api==0.12
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""Test the Yeelight light."""
|
||||
import asyncio
|
||||
import logging
|
||||
import socket
|
||||
from unittest.mock import ANY, AsyncMock, MagicMock, call, patch
|
||||
|
||||
import pytest
|
||||
@ -505,6 +506,64 @@ async def test_services(hass: HomeAssistant, caplog):
|
||||
{ATTR_ENTITY_ID: ENTITY_LIGHT, ATTR_BRIGHTNESS: 55},
|
||||
blocking=True,
|
||||
)
|
||||
assert hass.states.get(ENTITY_LIGHT).state == STATE_OFF
|
||||
|
||||
mocked_bulb.async_set_brightness = AsyncMock(side_effect=socket.error)
|
||||
with pytest.raises(HomeAssistantError):
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: ENTITY_LIGHT, ATTR_BRIGHTNESS: 55},
|
||||
blocking=True,
|
||||
)
|
||||
assert hass.states.get(ENTITY_LIGHT).state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_update_errors(hass: HomeAssistant, caplog):
|
||||
"""Test update errors."""
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
**CONFIG_ENTRY_DATA,
|
||||
CONF_MODE_MUSIC: True,
|
||||
CONF_SAVE_ON_CHANGE: True,
|
||||
CONF_NIGHTLIGHT_SWITCH: True,
|
||||
},
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
mocked_bulb = _mocked_bulb()
|
||||
with _patch_discovery(), _patch_discovery_interval(), patch(
|
||||
f"{MODULE}.AsyncBulb", return_value=mocked_bulb
|
||||
):
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get(ENTITY_LIGHT).state == STATE_ON
|
||||
assert hass.states.get(ENTITY_NIGHTLIGHT).state == STATE_OFF
|
||||
|
||||
# Timeout usually means the bulb is overloaded with commands
|
||||
# but will still respond eventually.
|
||||
mocked_bulb.async_get_properties = AsyncMock(side_effect=asyncio.TimeoutError)
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: ENTITY_LIGHT},
|
||||
blocking=True,
|
||||
)
|
||||
assert hass.states.get(ENTITY_LIGHT).state == STATE_ON
|
||||
|
||||
# socket.error usually means the bulb dropped the connection
|
||||
# or lost wifi, then came back online and forced the existing
|
||||
# connection closed with a TCP RST
|
||||
mocked_bulb.async_get_properties = AsyncMock(side_effect=socket.error)
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: ENTITY_LIGHT},
|
||||
blocking=True,
|
||||
)
|
||||
assert hass.states.get(ENTITY_LIGHT).state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user