Merge pull request #72107 from home-assistant/rc

pull/72477/head 2022.5.5
Paulus Schoutsen 2022-05-18 13:23:10 -07:00 committed by GitHub
commit 204762d23a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 222 additions and 67 deletions

View File

@ -3,7 +3,7 @@
"name": "Brother Printer",
"documentation": "https://www.home-assistant.io/integrations/brother",
"codeowners": ["@bieniu"],
"requirements": ["brother==1.2.0"],
"requirements": ["brother==1.1.0"],
"zeroconf": [
{
"type": "_printer._tcp.local.",

View File

@ -23,6 +23,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import FIBARO_DEVICES, FibaroDevice
from .const import DOMAIN
PARALLEL_UPDATES = 2
def scaleto255(value: int | None) -> int:
"""Scale the input value from 0-100 to 0-255."""

View File

@ -132,7 +132,7 @@ class FileSizeCoordinator(DataUpdateCoordinator):
async def _async_update_data(self) -> dict[str, float | int | datetime]:
"""Fetch file information."""
try:
statinfo = os.stat(self._path)
statinfo = await self.hass.async_add_executor_job(os.stat, self._path)
except OSError as error:
raise UpdateFailed(f"Can not retrieve file statistics {error}") from error

View File

@ -19,7 +19,7 @@ class HistoryStatsState:
"""The current stats of the history stats."""
hours_matched: float | None
changes_to_match_state: int | None
match_count: int | None
period: tuple[datetime.datetime, datetime.datetime]
@ -121,14 +121,12 @@ class HistoryStats:
self._state = HistoryStatsState(None, None, self._period)
return self._state
hours_matched, changes_to_match_state = self._async_compute_hours_and_changes(
hours_matched, match_count = self._async_compute_hours_and_changes(
now_timestamp,
current_period_start_timestamp,
current_period_end_timestamp,
)
self._state = HistoryStatsState(
hours_matched, changes_to_match_state, self._period
)
self._state = HistoryStatsState(hours_matched, match_count, self._period)
return self._state
def _update_from_database(
@ -156,7 +154,7 @@ class HistoryStats:
)
last_state_change_timestamp = start_timestamp
elapsed = 0.0
changes_to_match_state = 0
match_count = 1 if previous_state_matches else 0
# Make calculations
for item in self._history_current_period:
@ -166,7 +164,7 @@ class HistoryStats:
if previous_state_matches:
elapsed += state_change_timestamp - last_state_change_timestamp
elif current_state_matches:
changes_to_match_state += 1
match_count += 1
previous_state_matches = current_state_matches
last_state_change_timestamp = state_change_timestamp
@ -178,4 +176,4 @@ class HistoryStats:
# Save value in hours
hours_matched = elapsed / 3600
return hours_matched, changes_to_match_state
return hours_matched, match_count

View File

@ -166,4 +166,4 @@ class HistoryStatsSensor(HistoryStatsSensorBase):
elif self._type == CONF_TYPE_RATIO:
self._attr_native_value = pretty_ratio(state.hours_matched, state.period)
elif self._type == CONF_TYPE_COUNT:
self._attr_native_value = state.changes_to_match_state
self._attr_native_value = state.match_count

View File

@ -419,6 +419,8 @@ class LIFXManager:
if color_resp is None or version_resp is None:
_LOGGER.error("Failed to connect to %s", bulb.ip_addr)
bulb.registered = False
if bulb.mac_addr in self.discoveries_inflight:
self.discoveries_inflight.pop(bulb.mac_addr)
else:
bulb.timeout = MESSAGE_TIMEOUT
bulb.retry_count = MESSAGE_RETRIES

View File

@ -3,7 +3,6 @@ from __future__ import annotations
import asyncio
from collections.abc import Coroutine, Sequence
import contextlib
from datetime import datetime, timedelta
from typing import Any
@ -12,9 +11,11 @@ from async_upnp_client.client import UpnpDevice, UpnpService, UpnpStateVariable
from async_upnp_client.client_factory import UpnpFactory
from async_upnp_client.exceptions import (
UpnpActionResponseError,
UpnpCommunicationError,
UpnpConnectionError,
UpnpError,
UpnpResponseError,
UpnpXmlContentError,
)
from async_upnp_client.profiles.dlna import DmrDevice
from async_upnp_client.utils import async_get_local_ip
@ -270,11 +271,12 @@ class SamsungTVDevice(MediaPlayerEntity):
# NETWORK,NONE
upnp_factory = UpnpFactory(upnp_requester, non_strict=True)
upnp_device: UpnpDevice | None = None
with contextlib.suppress(UpnpConnectionError, UpnpResponseError):
try:
upnp_device = await upnp_factory.async_create_device(
self._ssdp_rendering_control_location
)
if not upnp_device:
except (UpnpConnectionError, UpnpResponseError, UpnpXmlContentError) as err:
LOGGER.debug("Unable to create Upnp DMR device: %r", err, exc_info=True)
return
_, event_ip = await async_get_local_ip(
self._ssdp_rendering_control_location, self.hass.loop
@ -307,8 +309,10 @@ class SamsungTVDevice(MediaPlayerEntity):
async def _async_resubscribe_dmr(self) -> None:
assert self._dmr_device
with contextlib.suppress(UpnpConnectionError):
try:
await self._dmr_device.async_subscribe_services(auto_resubscribe=True)
except UpnpCommunicationError as err:
LOGGER.debug("Device rejected re-subscription: %r", err, exc_info=True)
async def _async_shutdown_dmr(self) -> None:
"""Handle removal."""

View File

@ -812,7 +812,7 @@ class RpcPollingWrapper(update_coordinator.DataUpdateCoordinator):
LOGGER.debug("Polling Shelly RPC Device - %s", self.name)
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
await self.device.update_status()
except OSError as err:
except (OSError, aioshelly.exceptions.RPCTimeout) as err:
raise update_coordinator.UpdateFailed("Device disconnected") from err
@property

View File

@ -119,6 +119,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
)
except HTTP_CONNECT_ERRORS:
errors["base"] = "cannot_connect"
except KeyError:
errors["base"] = "firmware_not_fully_provisioned"
except Exception: # pylint: disable=broad-except
LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
@ -160,6 +162,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
errors["base"] = "cannot_connect"
except aioshelly.exceptions.JSONRPCError:
errors["base"] = "cannot_connect"
except KeyError:
errors["base"] = "firmware_not_fully_provisioned"
except Exception: # pylint: disable=broad-except
LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
@ -219,6 +223,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
try:
self.device_info = await validate_input(self.hass, self.host, self.info, {})
except KeyError:
LOGGER.debug("Shelly host %s firmware not fully provisioned", self.host)
except HTTP_CONNECT_ERRORS:
return self.async_abort(reason="cannot_connect")
@ -229,18 +235,21 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
) -> FlowResult:
"""Handle discovery confirm."""
errors: dict[str, str] = {}
if user_input is not None:
return self.async_create_entry(
title=self.device_info["title"],
data={
"host": self.host,
CONF_SLEEP_PERIOD: self.device_info[CONF_SLEEP_PERIOD],
"model": self.device_info["model"],
"gen": self.device_info["gen"],
},
)
self._set_confirm_only()
try:
if user_input is not None:
return self.async_create_entry(
title=self.device_info["title"],
data={
"host": self.host,
CONF_SLEEP_PERIOD: self.device_info[CONF_SLEEP_PERIOD],
"model": self.device_info["model"],
"gen": self.device_info["gen"],
},
)
except KeyError:
errors["base"] = "firmware_not_fully_provisioned"
else:
self._set_confirm_only()
return self.async_show_form(
step_id="confirm_discovery",

View File

@ -21,7 +21,8 @@
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
"unknown": "[%key:common::config_flow::error::unknown%]",
"firmware_not_fully_provisioned": "Device not fully provisioned. Please contact Shelly support"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",

View File

@ -6,6 +6,7 @@
},
"error": {
"cannot_connect": "Failed to connect",
"firmware_not_fully_provisioned": "Device not fully provisioned. Please contact Shelly support",
"invalid_auth": "Invalid authentication",
"unknown": "Unexpected error"
},

View File

@ -297,6 +297,9 @@ def get_rpc_key_instances(keys_dict: dict[str, Any], key: str) -> list[str]:
if key in keys_dict:
return [key]
if key == "switch" and "cover:0" in keys_dict:
key = "cover"
keys_list: list[str] = []
for i in range(MAX_RPC_KEY_INSTANCES):
key_inst = f"{key}:{i}"

View File

@ -2,7 +2,7 @@
"domain": "snmp",
"name": "SNMP",
"documentation": "https://www.home-assistant.io/integrations/snmp",
"requirements": ["pysnmplib==5.0.10"],
"requirements": ["pysnmp==4.4.12"],
"codeowners": [],
"iot_class": "local_polling",
"loggers": ["pyasn1", "pysmi", "pysnmp"]

View File

@ -28,6 +28,7 @@ from homeassistant.helpers.device_registry import (
DeviceEntry,
async_get_registry as get_dev_reg,
)
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .common import SynoApi
@ -41,6 +42,7 @@ from .const import (
EXCEPTION_DETAILS,
EXCEPTION_UNKNOWN,
PLATFORMS,
SIGNAL_CAMERA_SOURCE_CHANGED,
SYNO_API,
SYSTEM_LOADED,
UNDO_UPDATE_LISTENER,
@ -128,6 +130,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return None
surveillance_station = api.surveillance_station
current_data: dict[str, SynoCamera] = {
camera.id: camera for camera in surveillance_station.get_all_cameras()
}
try:
async with async_timeout.timeout(30):
@ -135,12 +140,23 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
except SynologyDSMAPIErrorException as err:
raise UpdateFailed(f"Error communicating with API: {err}") from err
return {
"cameras": {
camera.id: camera for camera in surveillance_station.get_all_cameras()
}
new_data: dict[str, SynoCamera] = {
camera.id: camera for camera in surveillance_station.get_all_cameras()
}
for cam_id, cam_data_new in new_data.items():
if (
(cam_data_current := current_data.get(cam_id)) is not None
and cam_data_current.live_view.rtsp != cam_data_new.live_view.rtsp
):
async_dispatcher_send(
hass,
f"{SIGNAL_CAMERA_SOURCE_CHANGED}_{entry.entry_id}_{cam_id}",
cam_data_new.live_view.rtsp,
)
return {"cameras": new_data}
async def async_coordinator_update_data_central() -> None:
"""Fetch all device and sensor data from api."""
try:

View File

@ -16,7 +16,8 @@ from homeassistant.components.camera import (
CameraEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
@ -27,6 +28,7 @@ from .const import (
COORDINATOR_CAMERAS,
DEFAULT_SNAPSHOT_QUALITY,
DOMAIN,
SIGNAL_CAMERA_SOURCE_CHANGED,
SYNO_API,
)
from .entity import SynologyDSMBaseEntity, SynologyDSMEntityDescription
@ -130,6 +132,29 @@ class SynoDSMCamera(SynologyDSMBaseEntity, Camera):
"""Return the camera motion detection status."""
return self.camera_data.is_motion_detection_enabled # type: ignore[no-any-return]
def _listen_source_updates(self) -> None:
"""Listen for camera source changed events."""
@callback
def _handle_signal(url: str) -> None:
if self.stream:
_LOGGER.debug("Update stream URL for camera %s", self.camera_data.name)
self.stream.update_source(url)
assert self.platform
assert self.platform.config_entry
self.async_on_remove(
async_dispatcher_connect(
self.hass,
f"{SIGNAL_CAMERA_SOURCE_CHANGED}_{self.platform.config_entry.entry_id}_{self.camera_data.id}",
_handle_signal,
)
)
async def async_added_to_hass(self) -> None:
"""Subscribe to signal."""
self._listen_source_updates()
def camera_image(
self, width: int | None = None, height: int | None = None
) -> bytes | None:
@ -162,6 +187,7 @@ class SynoDSMCamera(SynologyDSMBaseEntity, Camera):
)
if not self.available:
return None
return self.camera_data.live_view.rtsp # type: ignore[no-any-return]
def enable_motion_detection(self) -> None:

View File

@ -43,6 +43,9 @@ DEFAULT_SNAPSHOT_QUALITY = SNAPSHOT_PROFILE_BALANCED
ENTITY_UNIT_LOAD = "load"
# Signals
SIGNAL_CAMERA_SOURCE_CHANGED = "synology_dsm.camera_stream_source_changed"
# Services
SERVICE_REBOOT = "reboot"
SERVICE_SHUTDOWN = "shutdown"

View File

@ -143,7 +143,9 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
user_input[CONF_VERIFY_SSL] = False
nvr_data, errors = await self._async_get_nvr_data(user_input)
if nvr_data and not errors:
return self._async_create_entry(nvr_data.name, user_input)
return self._async_create_entry(
nvr_data.name or nvr_data.type, user_input
)
placeholders = {
"name": discovery_info["hostname"]
@ -289,7 +291,9 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
await self.async_set_unique_id(nvr_data.mac)
self._abort_if_unique_id_configured()
return self._async_create_entry(nvr_data.name, user_input)
return self._async_create_entry(
nvr_data.name or nvr_data.type, user_input
)
user_input = user_input or {}
return self.async_show_form(

View File

@ -3,7 +3,7 @@
"name": "UniFi Protect",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/unifiprotect",
"requirements": ["pyunifiprotect==3.4.1", "unifi-discovery==1.1.2"],
"requirements": ["pyunifiprotect==3.5.1", "unifi-discovery==1.1.2"],
"dependencies": ["http"],
"codeowners": ["@briis", "@AngellusMortis", "@bdraco"],
"quality_scale": "platinum",

View File

@ -140,7 +140,7 @@ def _get_doorbell_options(api: ProtectApiClient) -> list[dict[str, Any]]:
def _get_paired_camera_options(api: ProtectApiClient) -> list[dict[str, Any]]:
options = [{"id": TYPE_EMPTY_VALUE, "name": "Not Paired"}]
for camera in api.bootstrap.cameras.values():
options.append({"id": camera.id, "name": camera.name})
options.append({"id": camera.id, "name": camera.name or camera.type})
return options

View File

@ -159,6 +159,15 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ufp_value="is_face_detection_on",
ufp_set_method="set_face_detection",
),
ProtectSwitchEntityDescription(
key="smart_package",
name="Detections: Package",
icon="mdi:package-variant-closed",
entity_category=EntityCategory.CONFIG,
ufp_required_field="can_detect_package",
ufp_value="is_package_detection_on",
ufp_set_method="set_package_detection",
),
)
SENSE_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (

View File

@ -171,8 +171,8 @@ class VeSyncFanHA(VeSyncDevice, FanEntity):
if hasattr(self.smartfan, "night_light"):
attr["night_light"] = self.smartfan.night_light
if hasattr(self.smartfan, "air_quality"):
attr["air_quality"] = self.smartfan.air_quality
if self.smartfan.details.get("air_quality_value") is not None:
attr["air_quality"] = self.smartfan.details["air_quality_value"]
if hasattr(self.smartfan, "mode"):
attr["mode"] = self.smartfan.mode

View File

@ -3,7 +3,7 @@
"name": "VeSync",
"documentation": "https://www.home-assistant.io/integrations/vesync",
"codeowners": ["@markperdue", "@webdjoe", "@thegardenmonkey"],
"requirements": ["pyvesync==2.0.2"],
"requirements": ["pyvesync==2.0.3"],
"config_flow": true,
"iot_class": "cloud_polling",
"loggers": ["pyvesync"]

View File

@ -7,7 +7,7 @@ from .backports.enum import StrEnum
MAJOR_VERSION: Final = 2022
MINOR_VERSION: Final = 5
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, 9, 0)

View File

@ -436,7 +436,7 @@ bravia-tv==1.0.11
broadlink==0.18.1
# homeassistant.components.brother
brother==1.2.0
brother==1.1.0
# homeassistant.components.brottsplatskartan
brottsplatskartan==0.0.1
@ -1829,7 +1829,7 @@ pysmarty==0.8
pysml==0.0.7
# homeassistant.components.snmp
pysnmplib==5.0.10
pysnmp==4.4.12
# homeassistant.components.soma
pysoma==0.0.10
@ -1981,7 +1981,7 @@ pytrafikverket==0.1.6.2
pyudev==0.22.0
# homeassistant.components.unifiprotect
pyunifiprotect==3.4.1
pyunifiprotect==3.5.1
# homeassistant.components.uptimerobot
pyuptimerobot==22.2.0
@ -1996,7 +1996,7 @@ pyvera==0.3.13
pyversasense==0.0.6
# homeassistant.components.vesync
pyvesync==2.0.2
pyvesync==2.0.3
# homeassistant.components.vizio
pyvizio==0.1.57

View File

@ -327,7 +327,7 @@ bravia-tv==1.0.11
broadlink==0.18.1
# homeassistant.components.brother
brother==1.2.0
brother==1.1.0
# homeassistant.components.brunt
brunt==1.2.0
@ -1304,7 +1304,7 @@ pytrafikverket==0.1.6.2
pyudev==0.22.0
# homeassistant.components.unifiprotect
pyunifiprotect==3.4.1
pyunifiprotect==3.5.1
# homeassistant.components.uptimerobot
pyuptimerobot==22.2.0
@ -1313,7 +1313,7 @@ pyuptimerobot==22.2.0
pyvera==0.3.13
# homeassistant.components.vesync
pyvesync==2.0.2
pyvesync==2.0.3
# homeassistant.components.vizio
pyvizio==0.1.57

View File

@ -1,6 +1,6 @@
[metadata]
name = homeassistant
version = 2022.5.4
version = 2022.5.5
author = The Home Assistant Authors
author_email = hello@home-assistant.io
license = Apache-2.0

View File

@ -438,7 +438,7 @@ async def test_measure(hass, recorder_mock):
assert hass.states.get("sensor.sensor1").state == "0.83"
assert hass.states.get("sensor.sensor2").state == "0.83"
assert hass.states.get("sensor.sensor3").state == "1"
assert hass.states.get("sensor.sensor3").state == "2"
assert hass.states.get("sensor.sensor4").state == "83.3"
@ -519,7 +519,7 @@ async def test_async_on_entire_period(hass, recorder_mock):
assert hass.states.get("sensor.on_sensor1").state == "1.0"
assert hass.states.get("sensor.on_sensor2").state == "1.0"
assert hass.states.get("sensor.on_sensor3").state == "0"
assert hass.states.get("sensor.on_sensor3").state == "1"
assert hass.states.get("sensor.on_sensor4").state == "100.0"
@ -886,7 +886,7 @@ async def test_async_start_from_history_and_switch_to_watching_state_changes_mul
assert hass.states.get("sensor.sensor1").state == "0.0"
assert hass.states.get("sensor.sensor2").state == "0.0"
assert hass.states.get("sensor.sensor3").state == "0"
assert hass.states.get("sensor.sensor3").state == "1"
assert hass.states.get("sensor.sensor4").state == "0.0"
one_hour_in = start_time + timedelta(minutes=60)
@ -896,7 +896,7 @@ async def test_async_start_from_history_and_switch_to_watching_state_changes_mul
assert hass.states.get("sensor.sensor1").state == "1.0"
assert hass.states.get("sensor.sensor2").state == "1.0"
assert hass.states.get("sensor.sensor3").state == "0"
assert hass.states.get("sensor.sensor3").state == "1"
assert hass.states.get("sensor.sensor4").state == "50.0"
turn_off_time = start_time + timedelta(minutes=90)
@ -908,7 +908,7 @@ async def test_async_start_from_history_and_switch_to_watching_state_changes_mul
assert hass.states.get("sensor.sensor1").state == "1.5"
assert hass.states.get("sensor.sensor2").state == "1.5"
assert hass.states.get("sensor.sensor3").state == "0"
assert hass.states.get("sensor.sensor3").state == "1"
assert hass.states.get("sensor.sensor4").state == "75.0"
turn_back_on_time = start_time + timedelta(minutes=105)
@ -918,7 +918,7 @@ async def test_async_start_from_history_and_switch_to_watching_state_changes_mul
assert hass.states.get("sensor.sensor1").state == "1.5"
assert hass.states.get("sensor.sensor2").state == "1.5"
assert hass.states.get("sensor.sensor3").state == "0"
assert hass.states.get("sensor.sensor3").state == "1"
assert hass.states.get("sensor.sensor4").state == "75.0"
with freeze_time(turn_back_on_time):
@ -927,7 +927,7 @@ async def test_async_start_from_history_and_switch_to_watching_state_changes_mul
assert hass.states.get("sensor.sensor1").state == "1.5"
assert hass.states.get("sensor.sensor2").state == "1.5"
assert hass.states.get("sensor.sensor3").state == "1"
assert hass.states.get("sensor.sensor3").state == "2"
assert hass.states.get("sensor.sensor4").state == "75.0"
end_time = start_time + timedelta(minutes=120)
@ -937,7 +937,7 @@ async def test_async_start_from_history_and_switch_to_watching_state_changes_mul
assert hass.states.get("sensor.sensor1").state == "1.75"
assert hass.states.get("sensor.sensor2").state == "1.75"
assert hass.states.get("sensor.sensor3").state == "1"
assert hass.states.get("sensor.sensor3").state == "2"
assert hass.states.get("sensor.sensor4").state == "87.5"
@ -1198,7 +1198,7 @@ async def test_measure_sliding_window(hass, recorder_mock):
assert hass.states.get("sensor.sensor1").state == "0.83"
assert hass.states.get("sensor.sensor2").state == "0.83"
assert hass.states.get("sensor.sensor3").state == "1"
assert hass.states.get("sensor.sensor3").state == "2"
assert hass.states.get("sensor.sensor4").state == "41.7"
past_next_update = start_time + timedelta(minutes=30)
@ -1211,7 +1211,7 @@ async def test_measure_sliding_window(hass, recorder_mock):
assert hass.states.get("sensor.sensor1").state == "0.83"
assert hass.states.get("sensor.sensor2").state == "0.83"
assert hass.states.get("sensor.sensor3").state == "1"
assert hass.states.get("sensor.sensor3").state == "2"
assert hass.states.get("sensor.sensor4").state == "41.7"
@ -1291,7 +1291,7 @@ async def test_measure_from_end_going_backwards(hass, recorder_mock):
assert hass.states.get("sensor.sensor1").state == "0.83"
assert hass.states.get("sensor.sensor2").state == "0.83"
assert hass.states.get("sensor.sensor3").state == "1"
assert hass.states.get("sensor.sensor3").state == "2"
assert hass.states.get("sensor.sensor4").state == "83.3"
past_next_update = start_time + timedelta(minutes=30)
@ -1304,7 +1304,7 @@ async def test_measure_from_end_going_backwards(hass, recorder_mock):
assert hass.states.get("sensor.sensor1").state == "0.83"
assert hass.states.get("sensor.sensor2").state == "0.83"
assert hass.states.get("sensor.sensor3").state == "1"
assert hass.states.get("sensor.sensor3").state == "2"
assert hass.states.get("sensor.sensor4").state == "83.3"
@ -1385,7 +1385,7 @@ async def test_measure_cet(hass, recorder_mock):
assert hass.states.get("sensor.sensor1").state == "0.83"
assert hass.states.get("sensor.sensor2").state == "0.83"
assert hass.states.get("sensor.sensor3").state == "1"
assert hass.states.get("sensor.sensor3").state == "2"
assert hass.states.get("sensor.sensor4").state == "83.3"

View File

@ -6,6 +6,8 @@ from unittest.mock import DEFAULT as DEFAULT_MOCK, AsyncMock, Mock, call, patch
from async_upnp_client.exceptions import (
UpnpActionResponseError,
UpnpCommunicationError,
UpnpConnectionError,
UpnpError,
UpnpResponseError,
)
@ -1368,6 +1370,7 @@ async def test_upnp_not_available(
) -> None:
"""Test for volume control when Upnp is not available."""
await setup_samsungtv_entry(hass, MOCK_ENTRY_WS)
assert "Unable to create Upnp DMR device" in caplog.text
# Upnp action fails
assert await hass.services.async_call(
@ -1385,6 +1388,7 @@ async def test_upnp_missing_service(
) -> None:
"""Test for volume control when Upnp is not available."""
await setup_samsungtv_entry(hass, MOCK_ENTRY_WS)
assert "Unable to create Upnp DMR device" in caplog.text
# Upnp action fails
assert await hass.services.async_call(
@ -1505,3 +1509,49 @@ async def test_upnp_re_subscribe_events(
assert state.state == STATE_ON
assert dmr_device.async_subscribe_services.call_count == 2
assert dmr_device.async_unsubscribe_services.call_count == 1
@pytest.mark.usefixtures("rest_api", "upnp_notify_server")
@pytest.mark.parametrize(
"error",
{UpnpConnectionError(), UpnpCommunicationError(), UpnpResponseError(status=400)},
)
async def test_upnp_failed_re_subscribe_events(
hass: HomeAssistant,
remotews: Mock,
dmr_device: Mock,
mock_now: datetime,
caplog: pytest.LogCaptureFixture,
error: Exception,
) -> None:
"""Test for Upnp event feedback."""
await setup_samsungtv_entry(hass, MOCK_ENTRY_WS)
state = hass.states.get(ENTITY_ID)
assert state.state == STATE_ON
assert dmr_device.async_subscribe_services.call_count == 1
assert dmr_device.async_unsubscribe_services.call_count == 0
with patch.object(
remotews, "start_listening", side_effect=WebSocketException("Boom")
), patch.object(remotews, "is_alive", return_value=False):
next_update = mock_now + timedelta(minutes=5)
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_ID)
assert state.state == STATE_OFF
assert dmr_device.async_subscribe_services.call_count == 1
assert dmr_device.async_unsubscribe_services.call_count == 1
next_update = mock_now + timedelta(minutes=10)
with patch("homeassistant.util.dt.utcnow", return_value=next_update), patch.object(
dmr_device, "async_subscribe_services", side_effect=error
):
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_ID)
assert state.state == STATE_ON
assert "Device rejected re-subscription" in caplog.text

View File

@ -225,6 +225,29 @@ async def test_form_errors_get_info(hass, error):
assert result2["errors"] == {"base": base_error}
@pytest.mark.parametrize("error", [(KeyError, "firmware_not_fully_provisioned")])
async def test_form_missing_key_get_info(hass, error):
"""Test we handle missing key."""
exc, base_error = error
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"aioshelly.common.get_info",
return_value={"mac": "test-mac", "type": "SHSW-1", "auth": False, "gen": "2"},
), patch(
"homeassistant.components.shelly.config_flow.validate_input",
side_effect=KeyError,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"host": "1.1.1.1"},
)
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result2["errors"] == {"base": base_error}
@pytest.mark.parametrize(
"error", [(asyncio.TimeoutError, "cannot_connect"), (ValueError, "unknown")]
)

View File

@ -26,9 +26,13 @@ from .conftest import (
ids_from_device_description,
)
CAMERA_SWITCHES_NO_FACE = [d for d in CAMERA_SWITCHES if d.name != "Detections: Face"]
CAMERA_SWITCHES_BASIC = [
d
for d in CAMERA_SWITCHES
if d.name != "Detections: Face" and d.name != "Detections: Package"
]
CAMERA_SWITCHES_NO_EXTRA = [
d for d in CAMERA_SWITCHES_NO_FACE if d.name not in ("High FPS", "Privacy Mode")
d for d in CAMERA_SWITCHES_BASIC if d.name not in ("High FPS", "Privacy Mode")
]
@ -253,7 +257,7 @@ async def test_switch_setup_camera_all(
entity_registry = er.async_get(hass)
for description in CAMERA_SWITCHES_NO_FACE:
for description in CAMERA_SWITCHES_BASIC:
unique_id, entity_id = ids_from_device_description(
Platform.SWITCH, camera, description
)