Merge pull request #57793 from home-assistant/rc

This commit is contained in:
Paulus Schoutsen 2021-10-15 13:57:41 -07:00 committed by GitHub
commit eb30fb1f3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 388 additions and 64 deletions

View File

@ -404,8 +404,8 @@ class DlnaDmrEntity(MediaPlayerEntity):
try:
do_ping = self.poll_availability or self.check_available
await self._device.async_update(do_ping=do_ping)
except UpnpError:
_LOGGER.debug("Device unavailable")
except UpnpError as err:
_LOGGER.debug("Device unavailable: %r", err)
await self._device_disconnect()
return
finally:

View File

@ -1,7 +1,7 @@
"""Support for testing internet speed via Fast.com."""
from __future__ import annotations
from datetime import timedelta
from datetime import datetime, timedelta
import logging
from typing import Any
@ -67,7 +67,7 @@ class SpeedtestData:
self.data: dict[str, Any] | None = None
self._hass = hass
def update(self) -> None:
def update(self, now: datetime | None = None) -> None:
"""Get the latest data from fast.com."""
_LOGGER.debug("Executing fast.com speedtest")

View File

@ -2,7 +2,7 @@
"domain": "mill",
"name": "Mill",
"documentation": "https://www.home-assistant.io/integrations/mill",
"requirements": ["millheater==0.6.1"],
"requirements": ["millheater==0.6.2"],
"codeowners": ["@danielhiversen"],
"config_flow": true,
"iot_class": "cloud_polling"

View File

@ -151,7 +151,7 @@ class NotionEntity(CoordinatorEntity):
"identifiers": {(DOMAIN, sensor["hardware_id"])},
"manufacturer": "Silicon Labs",
"model": sensor["hardware_revision"],
"name": sensor["name"],
"name": str(sensor["name"]),
"sw_version": sensor["firmware_version"],
"via_device": (DOMAIN, bridge.get("hardware_id")),
}

View File

@ -311,7 +311,9 @@ def setup_connection_for_dialect(
result = query_on_connection(dbapi_connection, "SELECT VERSION()")
version = result[0][0]
major, minor, _patch = version.split(".", 2)
if int(major) == 5 and int(minor) < 8:
if (int(major) == 5 and int(minor) < 8) or (
int(major) == 10 and int(minor) < 2
):
instance._db_supports_row_number = ( # pylint: disable=[protected-access]
False
)

View File

@ -182,7 +182,7 @@ SENSORS: Final = {
value=lambda value: round(value, 1),
device_class=sensor.DEVICE_CLASS_HUMIDITY,
state_class=sensor.STATE_CLASS_MEASUREMENT,
available=lambda block: cast(int, block.extTemp) != 999,
available=lambda block: cast(int, block.humidity) != 999,
),
("sensor", "luminosity"): BlockAttributeDescription(
name="Luminosity",

View File

@ -3,7 +3,7 @@
"name": "SimpliSafe",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/simplisafe",
"requirements": ["simplisafe-python==11.0.6"],
"requirements": ["simplisafe-python==11.0.7"],
"codeowners": ["@bachya"],
"iot_class": "cloud_polling"
}

View File

@ -2,7 +2,7 @@
"domain": "spider",
"name": "Itho Daalderop Spider",
"documentation": "https://www.home-assistant.io/integrations/spider",
"requirements": ["spiderpy==1.4.2"],
"requirements": ["spiderpy==1.4.3"],
"codeowners": ["@peternijssen"],
"config_flow": true,
"iot_class": "cloud_polling"

View File

@ -264,5 +264,5 @@ class UpnpEntity(CoordinatorEntity):
def available(self) -> bool:
"""Return if entity is available."""
return super().available and (
self.coordinator.data.get(self.entity_description.key) or False
self.coordinator.data.get(self.entity_description.key) is not None
)

View File

@ -190,7 +190,10 @@ class DerivedUpnpSensor(UpnpSensor):
# Calculate derivative.
delta_value = current_value - self._last_value
if self.entity_description.native_unit_of_measurement == DATA_BYTES:
if (
self.entity_description.native_unit_of_measurement
== DATA_RATE_KIBIBYTES_PER_SECOND
):
delta_value /= KIBIBYTE
delta_time = current_timestamp - self._last_timestamp
if delta_time.total_seconds() == 0:

View File

@ -6,7 +6,6 @@ import contextlib
from datetime import timedelta
from ipaddress import IPv4Address, IPv6Address
import logging
import socket
from urllib.parse import urlparse
from async_upnp_client.search import SsdpSearchListener
@ -163,9 +162,6 @@ UPDATE_REQUEST_PROPERTIES = [
"active_mode",
]
BULB_NETWORK_EXCEPTIONS = (socket.error,)
BULB_EXCEPTIONS = (BulbException, asyncio.TimeoutError, *BULB_NETWORK_EXCEPTIONS)
PLATFORMS = ["binary_sensor", "light"]
@ -270,7 +266,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
try:
device = await _async_get_device(hass, entry.data[CONF_HOST], entry)
await _async_initialize(hass, entry, device)
except BULB_EXCEPTIONS as ex:
except (asyncio.TimeoutError, OSError, BulbException) as ex:
raise ConfigEntryNotReady from ex
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
@ -594,13 +590,20 @@ class YeelightDevice:
self._available = True
if not self._initialized:
self._initialized = True
except BULB_NETWORK_EXCEPTIONS as ex:
except OSError 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
except BULB_EXCEPTIONS as ex:
except asyncio.TimeoutError as ex:
_LOGGER.debug(
"timed out while trying to update device %s, %s: %s",
self._host,
self.name,
ex,
)
except BulbException as ex:
_LOGGER.debug(
"Unable to update device %s, %s: %s", self._host, self.name, ex
)

View File

@ -1,6 +1,7 @@
"""Light platform support for yeelight."""
from __future__ import annotations
import asyncio
import logging
import math
@ -8,6 +9,7 @@ import voluptuous as vol
import yeelight
from yeelight import Bulb, Flow, RGBTransition, SleepTransition, flows
from yeelight.enums import BulbType, LightType, PowerMode, SceneClass
from yeelight.main import BulbException
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
@ -51,8 +53,6 @@ from . import (
ATTR_COUNT,
ATTR_MODE_MUSIC,
ATTR_TRANSITIONS,
BULB_EXCEPTIONS,
BULB_NETWORK_EXCEPTIONS,
CONF_FLOW_PARAMS,
CONF_MODE_MUSIC,
CONF_NIGHTLIGHT_SWITCH,
@ -243,23 +243,33 @@ def _async_cmd(func):
"""Define a wrapper to catch exceptions from the bulb."""
async def _async_wrap(self, *args, **kwargs):
try:
_LOGGER.debug("Calling %s with %s %s", func, args, kwargs)
return await func(self, *args, **kwargs)
except BULB_NETWORK_EXCEPTIONS as ex:
# A network error happened, the bulb is likely offline now
self.device.async_mark_unavailable()
self.async_state_changed()
exc_message = str(ex) or type(ex)
raise HomeAssistantError(
f"Error when calling {func.__name__} for bulb {self.device.name} at {self.device.host}: {exc_message}"
) from ex
except BULB_EXCEPTIONS as ex:
# The bulb likely responded but had an error
exc_message = str(ex) or type(ex)
raise HomeAssistantError(
f"Error when calling {func.__name__} for bulb {self.device.name} at {self.device.host}: {exc_message}"
) from ex
for attempts in range(2):
try:
_LOGGER.debug("Calling %s with %s %s", func, args, kwargs)
return await func(self, *args, **kwargs)
except asyncio.TimeoutError as ex:
# The wifi likely dropped, so we want to retry once since
# python-yeelight will auto reconnect
exc_message = str(ex) or type(ex)
if attempts == 0:
continue
raise HomeAssistantError(
f"Timed out when calling {func.__name__} for bulb {self.device.name} at {self.device.host}: {exc_message}"
) from ex
except OSError as ex:
# A network error happened, the bulb is likely offline now
self.device.async_mark_unavailable()
self.async_state_changed()
exc_message = str(ex) or type(ex)
raise HomeAssistantError(
f"Error when calling {func.__name__} for bulb {self.device.name} at {self.device.host}: {exc_message}"
) from ex
except BulbException as ex:
# The bulb likely responded but had an error
exc_message = str(ex) or type(ex)
raise HomeAssistantError(
f"Error when calling {func.__name__} for bulb {self.device.name} at {self.device.host}: {exc_message}"
) from ex
return _async_wrap
@ -621,7 +631,11 @@ class YeelightGenericLight(YeelightEntity, LightEntity):
"""Set bulb's color."""
if not hs_color or COLOR_MODE_HS not in self.supported_color_modes:
return
if self.color_mode == COLOR_MODE_HS and self.hs_color == hs_color:
if (
not self.device.is_color_flow_enabled
and self.color_mode == COLOR_MODE_HS
and self.hs_color == hs_color
):
_LOGGER.debug("HS already set to: %s", hs_color)
# Already set, and since we get pushed updates
# we avoid setting it again to ensure we do not
@ -638,7 +652,11 @@ class YeelightGenericLight(YeelightEntity, LightEntity):
"""Set bulb's color."""
if not rgb or COLOR_MODE_RGB not in self.supported_color_modes:
return
if self.color_mode == COLOR_MODE_RGB and self.rgb_color == rgb:
if (
not self.device.is_color_flow_enabled
and self.color_mode == COLOR_MODE_RGB
and self.rgb_color == rgb
):
_LOGGER.debug("RGB already set to: %s", rgb)
# Already set, and since we get pushed updates
# we avoid setting it again to ensure we do not
@ -657,7 +675,11 @@ class YeelightGenericLight(YeelightEntity, LightEntity):
return
temp_in_k = mired_to_kelvin(colortemp)
if self.color_mode == COLOR_MODE_COLOR_TEMP and self.color_temp == colortemp:
if (
not self.device.is_color_flow_enabled
and self.color_mode == COLOR_MODE_COLOR_TEMP
and self.color_temp == colortemp
):
_LOGGER.debug("Color temp already set to: %s", temp_in_k)
# Already set, and since we get pushed updates
# we avoid setting it again to ensure we do not

View File

@ -2,7 +2,7 @@
"domain": "yeelight",
"name": "Yeelight",
"documentation": "https://www.home-assistant.io/integrations/yeelight",
"requirements": ["yeelight==0.7.7", "async-upnp-client==0.22.8"],
"requirements": ["yeelight==0.7.8", "async-upnp-client==0.22.8"],
"codeowners": ["@rytilahti", "@zewelor", "@shenxn", "@starkillerOG"],
"config_flow": true,
"dependencies": ["network"],

View File

@ -5,6 +5,7 @@ from youless_api.youless_sensor import YoulessSensor
from homeassistant.components.sensor import (
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL,
STATE_CLASS_TOTAL_INCREASING,
SensorEntity,
)
@ -40,9 +41,9 @@ async def async_setup_entry(
async_add_entities(
[
GasSensor(coordinator, device),
PowerMeterSensor(coordinator, device, "low"),
PowerMeterSensor(coordinator, device, "high"),
PowerMeterSensor(coordinator, device, "total"),
PowerMeterSensor(coordinator, device, "low", STATE_CLASS_TOTAL_INCREASING),
PowerMeterSensor(coordinator, device, "high", STATE_CLASS_TOTAL_INCREASING),
PowerMeterSensor(coordinator, device, "total", STATE_CLASS_TOTAL),
CurrentPowerSensor(coordinator, device),
DeliveryMeterSensor(coordinator, device, "low"),
DeliveryMeterSensor(coordinator, device, "high"),
@ -168,7 +169,11 @@ class PowerMeterSensor(YoulessBaseSensor):
_attr_state_class = STATE_CLASS_TOTAL_INCREASING
def __init__(
self, coordinator: DataUpdateCoordinator, device: str, dev_type: str
self,
coordinator: DataUpdateCoordinator,
device: str,
dev_type: str,
state_class: str,
) -> None:
"""Instantiate a power meter sensor."""
super().__init__(
@ -177,6 +182,7 @@ class PowerMeterSensor(YoulessBaseSensor):
self._device = device
self._type = dev_type
self._attr_name = f"Power {dev_type}"
self._attr_state_class = state_class
@property
def get_sensor(self) -> YoulessSensor | None:

View File

@ -5,7 +5,7 @@ from typing import Final
MAJOR_VERSION: Final = 2021
MINOR_VERSION: Final = 10
PATCH_VERSION: Final = "4"
PATCH_VERSION: Final = "5"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0)

View File

@ -1005,7 +1005,7 @@ micloud==0.3
miflora==0.7.0
# homeassistant.components.mill
millheater==0.6.1
millheater==0.6.2
# homeassistant.components.minio
minio==4.0.9
@ -2149,7 +2149,7 @@ simplehound==0.3
simplepush==1.1.4
# homeassistant.components.simplisafe
simplisafe-python==11.0.6
simplisafe-python==11.0.7
# homeassistant.components.sisyphus
sisyphus-control==3.0
@ -2214,7 +2214,7 @@ speak2mary==1.4.0
speedtest-cli==2.1.3
# homeassistant.components.spider
spiderpy==1.4.2
spiderpy==1.4.3
# homeassistant.components.spotify
spotipy==2.18.0
@ -2459,7 +2459,7 @@ yalesmartalarmclient==0.3.4
yalexs==1.1.13
# homeassistant.components.yeelight
yeelight==0.7.7
yeelight==0.7.8
# homeassistant.components.yeelightsunflower
yeelightsunflower==0.0.10

View File

@ -585,7 +585,7 @@ mficlient==0.3.0
micloud==0.3
# homeassistant.components.mill
millheater==0.6.1
millheater==0.6.2
# homeassistant.components.minio
minio==4.0.9
@ -1224,7 +1224,7 @@ sharkiqpy==0.1.8
simplehound==0.3
# homeassistant.components.simplisafe
simplisafe-python==11.0.6
simplisafe-python==11.0.7
# homeassistant.components.slack
slackclient==2.5.0
@ -1263,7 +1263,7 @@ speak2mary==1.4.0
speedtest-cli==2.1.3
# homeassistant.components.spider
spiderpy==1.4.2
spiderpy==1.4.3
# homeassistant.components.spotify
spotipy==2.18.0
@ -1403,7 +1403,7 @@ yalesmartalarmclient==0.3.4
yalexs==1.1.13
# homeassistant.components.yeelight
yeelight==0.7.7
yeelight==0.7.8
# homeassistant.components.youless
youless-api==0.14

View File

@ -125,7 +125,8 @@ async def test_last_run_was_recently_clean(hass):
@pytest.mark.parametrize(
"mysql_version, db_supports_row_number",
[
("10.0.0", True),
("10.2.0", True),
("10.1.0", False),
("5.8.0", True),
("5.7.0", False),
],

View File

@ -9,6 +9,9 @@ from homeassistant.components import ssdp
from homeassistant.components.upnp.const import (
BYTES_RECEIVED,
BYTES_SENT,
CONFIG_ENTRY_ST,
CONFIG_ENTRY_UDN,
DOMAIN,
PACKETS_RECEIVED,
PACKETS_SENT,
ROUTER_IP,
@ -19,6 +22,8 @@ from homeassistant.components.upnp.const import (
from homeassistant.core import HomeAssistant
from homeassistant.util import dt
from tests.common import MockConfigEntry
TEST_UDN = "uuid:device"
TEST_ST = "urn:schemas-upnp-org:device:InternetGatewayDevice:1"
TEST_USN = f"{TEST_UDN}::{TEST_ST}"
@ -115,8 +120,8 @@ class MockDevice:
self.status_times_polled += 1
return {
WAN_STATUS: "Connected",
ROUTER_UPTIME: 0,
ROUTER_IP: "192.168.0.1",
ROUTER_UPTIME: 10,
ROUTER_IP: "8.9.10.11",
}
@ -185,3 +190,24 @@ async def ssdp_no_discovery():
return_value=[],
) as mock_get_info:
yield (mock_register, mock_get_info)
@pytest.fixture
async def setup_integration(
hass: HomeAssistant, mock_get_source_ip, ssdp_instant_discovery, mock_upnp_device
):
"""Create an initialized integration."""
entry = MockConfigEntry(
domain=DOMAIN,
data={
CONFIG_ENTRY_UDN: TEST_UDN,
CONFIG_ENTRY_ST: TEST_ST,
},
)
# Load config_entry.
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
yield entry

View File

@ -0,0 +1,42 @@
"""Tests for UPnP/IGD binary_sensor."""
from datetime import timedelta
from unittest.mock import AsyncMock
from homeassistant.components.upnp.const import (
DOMAIN,
ROUTER_IP,
ROUTER_UPTIME,
WAN_STATUS,
)
from homeassistant.core import HomeAssistant
import homeassistant.util.dt as dt_util
from .conftest import MockDevice
from tests.common import MockConfigEntry, async_fire_time_changed
async def test_upnp_binary_sensors(
hass: HomeAssistant, setup_integration: MockConfigEntry
):
"""Test normal sensors."""
mock_device: MockDevice = hass.data[DOMAIN][setup_integration.entry_id].device
# First poll.
wan_status_state = hass.states.get("binary_sensor.mock_name_wan_status")
assert wan_status_state.state == "on"
# Second poll.
mock_device.async_get_status = AsyncMock(
return_value={
WAN_STATUS: "Disconnected",
ROUTER_UPTIME: 100,
ROUTER_IP: "",
}
)
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=31))
await hass.async_block_till_done()
wan_status_state = hass.states.get("binary_sensor.mock_name_wan_status")
assert wan_status_state.state == "off"

View File

@ -25,6 +25,7 @@ from .conftest import (
TEST_ST,
TEST_UDN,
TEST_USN,
MockDevice,
)
from tests.common import MockConfigEntry, async_fire_time_changed
@ -196,7 +197,7 @@ async def test_options_flow(hass: HomeAssistant):
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id) is True
await hass.async_block_till_done()
mock_device = hass.data[DOMAIN][config_entry.entry_id].device
mock_device: MockDevice = hass.data[DOMAIN][config_entry.entry_id].device
# Reset.
mock_device.traffic_times_polled = 0

View File

@ -9,7 +9,6 @@ from homeassistant.components.upnp.const import (
DOMAIN,
)
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from .conftest import TEST_ST, TEST_UDN
@ -28,10 +27,6 @@ async def test_async_setup_entry_default(hass: HomeAssistant):
},
)
# Initialisation of component, no device discovered.
await async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()
# Load config_entry.
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id) is True

View File

@ -0,0 +1,114 @@
"""Tests for UPnP/IGD sensor."""
from datetime import timedelta
from unittest.mock import AsyncMock
from homeassistant.components.upnp.const import (
BYTES_RECEIVED,
BYTES_SENT,
DOMAIN,
PACKETS_RECEIVED,
PACKETS_SENT,
ROUTER_IP,
ROUTER_UPTIME,
TIMESTAMP,
UPDATE_INTERVAL,
WAN_STATUS,
)
from homeassistant.core import HomeAssistant
import homeassistant.util.dt as dt_util
from .conftest import MockDevice
from tests.common import MockConfigEntry, async_fire_time_changed
async def test_upnp_sensors(hass: HomeAssistant, setup_integration: MockConfigEntry):
"""Test normal sensors."""
mock_device: MockDevice = hass.data[DOMAIN][setup_integration.entry_id].device
# First poll.
b_received_state = hass.states.get("sensor.mock_name_b_received")
b_sent_state = hass.states.get("sensor.mock_name_b_sent")
packets_received_state = hass.states.get("sensor.mock_name_packets_received")
packets_sent_state = hass.states.get("sensor.mock_name_packets_sent")
external_ip_state = hass.states.get("sensor.mock_name_external_ip")
wan_status_state = hass.states.get("sensor.mock_name_wan_status")
assert b_received_state.state == "0"
assert b_sent_state.state == "0"
assert packets_received_state.state == "0"
assert packets_sent_state.state == "0"
assert external_ip_state.state == "8.9.10.11"
assert wan_status_state.state == "Connected"
# Second poll.
mock_device.async_get_traffic_data = AsyncMock(
return_value={
TIMESTAMP: dt_util.utcnow() + UPDATE_INTERVAL,
BYTES_RECEIVED: 10240,
BYTES_SENT: 20480,
PACKETS_RECEIVED: 30,
PACKETS_SENT: 40,
}
)
mock_device.async_get_status = AsyncMock(
return_value={
WAN_STATUS: "Disconnected",
ROUTER_UPTIME: 100,
ROUTER_IP: "",
}
)
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=31))
await hass.async_block_till_done()
b_received_state = hass.states.get("sensor.mock_name_b_received")
b_sent_state = hass.states.get("sensor.mock_name_b_sent")
packets_received_state = hass.states.get("sensor.mock_name_packets_received")
packets_sent_state = hass.states.get("sensor.mock_name_packets_sent")
external_ip_state = hass.states.get("sensor.mock_name_external_ip")
wan_status_state = hass.states.get("sensor.mock_name_wan_status")
assert b_received_state.state == "10240"
assert b_sent_state.state == "20480"
assert packets_received_state.state == "30"
assert packets_sent_state.state == "40"
assert external_ip_state.state == ""
assert wan_status_state.state == "Disconnected"
async def test_derived_upnp_sensors(
hass: HomeAssistant, setup_integration: MockConfigEntry
):
"""Test derived sensors."""
mock_device: MockDevice = hass.data[DOMAIN][setup_integration.entry_id].device
# First poll.
kib_s_received_state = hass.states.get("sensor.mock_name_kib_s_received")
kib_s_sent_state = hass.states.get("sensor.mock_name_kib_s_sent")
packets_s_received_state = hass.states.get("sensor.mock_name_packets_s_received")
packets_s_sent_state = hass.states.get("sensor.mock_name_packets_s_sent")
assert kib_s_received_state.state == "unknown"
assert kib_s_sent_state.state == "unknown"
assert packets_s_received_state.state == "unknown"
assert packets_s_sent_state.state == "unknown"
# Second poll.
mock_device.async_get_traffic_data = AsyncMock(
return_value={
TIMESTAMP: dt_util.utcnow() + UPDATE_INTERVAL,
BYTES_RECEIVED: int(10240 * UPDATE_INTERVAL.total_seconds()),
BYTES_SENT: int(20480 * UPDATE_INTERVAL.total_seconds()),
PACKETS_RECEIVED: int(30 * UPDATE_INTERVAL.total_seconds()),
PACKETS_SENT: int(40 * UPDATE_INTERVAL.total_seconds()),
}
)
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=31))
await hass.async_block_till_done()
kib_s_received_state = hass.states.get("sensor.mock_name_kib_s_received")
kib_s_sent_state = hass.states.get("sensor.mock_name_kib_s_sent")
packets_s_received_state = hass.states.get("sensor.mock_name_packets_s_received")
packets_s_sent_state = hass.states.get("sensor.mock_name_packets_s_sent")
assert kib_s_received_state.state == "10.0"
assert kib_s_sent_state.state == "20.0"
assert packets_s_received_state.state == "30.0"
assert packets_s_sent_state.state == "40.0"

View File

@ -1,7 +1,9 @@
"""Test Yeelight."""
import asyncio
from datetime import timedelta
from unittest.mock import AsyncMock, patch
import pytest
from yeelight import BulbException, BulbType
from yeelight.aio import KEY_CONNECTED
@ -507,3 +509,51 @@ async def test_connection_dropped_resyncs_properties(hass: HomeAssistant):
)
await hass.async_block_till_done()
assert len(mocked_bulb.async_get_properties.mock_calls) == 2
async def test_oserror_on_first_update_results_in_unavailable(hass: HomeAssistant):
"""Test that an OSError on first update results in unavailable."""
config_entry = MockConfigEntry(
domain=DOMAIN,
unique_id=ID,
data={CONF_HOST: "127.0.0.1"},
options={CONF_NAME: "Test name"},
)
config_entry.add_to_hass(hass)
mocked_bulb = _mocked_bulb()
mocked_bulb.async_get_properties = AsyncMock(side_effect=OSError)
with _patch_discovery(), _patch_discovery_timeout(), _patch_discovery_interval(), patch(
f"{MODULE}.AsyncBulb", return_value=mocked_bulb
):
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get("light.test_name").state == STATE_UNAVAILABLE
@pytest.mark.parametrize("exception", [BulbException, asyncio.TimeoutError])
async def test_non_oserror_exception_on_first_update(
hass: HomeAssistant, exception: Exception
):
"""Test that an exceptions other than OSError on first update do not result in unavailable.
The unavailable state will come as a push update in this case
"""
config_entry = MockConfigEntry(
domain=DOMAIN,
unique_id=ID,
data={CONF_HOST: "127.0.0.1"},
options={CONF_NAME: "Test name"},
)
config_entry.add_to_hass(hass)
mocked_bulb = _mocked_bulb()
mocked_bulb.async_get_properties = AsyncMock(side_effect=exception)
with _patch_discovery(), _patch_discovery_timeout(), _patch_discovery_interval(), patch(
f"{MODULE}.AsyncBulb", return_value=mocked_bulb
):
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get("light.test_name").state != STATE_UNAVAILABLE

View File

@ -625,6 +625,22 @@ async def test_state_already_set_avoid_ratelimit(hass: HomeAssistant):
assert mocked_bulb.async_set_brightness.mock_calls == []
mocked_bulb.async_set_rgb.reset_mock()
mocked_bulb.last_properties["flowing"] = "1"
await hass.services.async_call(
"light",
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: ENTITY_LIGHT, ATTR_RGB_COLOR: (red, green, blue)},
blocking=True,
)
assert mocked_bulb.async_set_hsv.mock_calls == []
assert mocked_bulb.async_set_rgb.mock_calls == [
call(255, 0, 0, duration=350, light_type=ANY)
]
assert mocked_bulb.async_set_color_temp.mock_calls == []
assert mocked_bulb.async_set_brightness.mock_calls == []
mocked_bulb.async_set_rgb.reset_mock()
mocked_bulb.last_properties["flowing"] = "0"
await hass.services.async_call(
"light",
SERVICE_TURN_ON,
@ -666,6 +682,22 @@ async def test_state_already_set_avoid_ratelimit(hass: HomeAssistant):
assert mocked_bulb.async_set_color_temp.mock_calls == []
assert mocked_bulb.async_set_brightness.mock_calls == []
mocked_bulb.last_properties["flowing"] = "1"
await hass.services.async_call(
"light",
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: ENTITY_LIGHT, ATTR_COLOR_TEMP: 250},
blocking=True,
)
assert mocked_bulb.async_set_hsv.mock_calls == []
assert mocked_bulb.async_set_rgb.mock_calls == []
assert mocked_bulb.async_set_color_temp.mock_calls == [
call(4000, duration=350, light_type=ANY)
]
assert mocked_bulb.async_set_brightness.mock_calls == []
mocked_bulb.async_set_color_temp.reset_mock()
mocked_bulb.last_properties["flowing"] = "0"
mocked_bulb.last_properties["color_mode"] = 3
# This last change should generate a call even though
# the color mode is the same since the HSV has changed
@ -681,6 +713,33 @@ async def test_state_already_set_avoid_ratelimit(hass: HomeAssistant):
assert mocked_bulb.async_set_rgb.mock_calls == []
assert mocked_bulb.async_set_color_temp.mock_calls == []
assert mocked_bulb.async_set_brightness.mock_calls == []
mocked_bulb.async_set_hsv.reset_mock()
await hass.services.async_call(
"light",
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: ENTITY_LIGHT, ATTR_HS_COLOR: (100, 35)},
blocking=True,
)
assert mocked_bulb.async_set_hsv.mock_calls == []
assert mocked_bulb.async_set_rgb.mock_calls == []
assert mocked_bulb.async_set_color_temp.mock_calls == []
assert mocked_bulb.async_set_brightness.mock_calls == []
mocked_bulb.last_properties["flowing"] = "1"
await hass.services.async_call(
"light",
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: ENTITY_LIGHT, ATTR_HS_COLOR: (100, 35)},
blocking=True,
)
assert mocked_bulb.async_set_hsv.mock_calls == [
call(100.0, 35.0, duration=350, light_type=ANY)
]
assert mocked_bulb.async_set_rgb.mock_calls == []
assert mocked_bulb.async_set_color_temp.mock_calls == []
assert mocked_bulb.async_set_brightness.mock_calls == []
mocked_bulb.last_properties["flowing"] = "0"
async def test_device_types(hass: HomeAssistant, caplog):