mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 05:37:44 +00:00
Merge pull request #57793 from home-assistant/rc
This commit is contained in:
commit
eb30fb1f3b
@ -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:
|
||||
|
@ -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")
|
||||
|
@ -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"
|
||||
|
@ -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")),
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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",
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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"],
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
],
|
||||
|
@ -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
|
||||
|
42
tests/components/upnp/test_binary_sensor.py
Normal file
42
tests/components/upnp/test_binary_sensor.py
Normal 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"
|
@ -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
|
||||
|
@ -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
|
||||
|
114
tests/components/upnp/test_sensor.py
Normal file
114
tests/components/upnp/test_sensor.py
Normal 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"
|
@ -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
|
||||
|
@ -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):
|
||||
|
Loading…
x
Reference in New Issue
Block a user