mirror of
https://github.com/elisspace/core.git
synced 2026-10-04 08:16:20 +00:00
Merge branch 'dev' into webrtc
This commit is contained in:
@@ -330,6 +330,7 @@ omit =
|
||||
homeassistant/components/ezviz/entity.py
|
||||
homeassistant/components/ezviz/sensor.py
|
||||
homeassistant/components/ezviz/switch.py
|
||||
homeassistant/components/ezviz/update.py
|
||||
homeassistant/components/faa_delays/__init__.py
|
||||
homeassistant/components/faa_delays/binary_sensor.py
|
||||
homeassistant/components/familyhub/camera.py
|
||||
|
||||
@@ -5,11 +5,16 @@ from typing import Any, Final
|
||||
|
||||
from aioairzone.common import OperationAction, OperationMode
|
||||
from aioairzone.const import (
|
||||
API_COOL_SET_POINT,
|
||||
API_HEAT_SET_POINT,
|
||||
API_MODE,
|
||||
API_ON,
|
||||
API_SET_POINT,
|
||||
API_SPEED,
|
||||
AZD_ACTION,
|
||||
AZD_COOL_TEMP_SET,
|
||||
AZD_DOUBLE_SET_POINT,
|
||||
AZD_HEAT_TEMP_SET,
|
||||
AZD_HUMIDITY,
|
||||
AZD_MASTER,
|
||||
AZD_MODE,
|
||||
@@ -27,6 +32,8 @@ from aioairzone.const import (
|
||||
)
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
ATTR_TARGET_TEMP_HIGH,
|
||||
ATTR_TARGET_TEMP_LOW,
|
||||
FAN_AUTO,
|
||||
FAN_HIGH,
|
||||
FAN_LOW,
|
||||
@@ -137,6 +144,10 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
|
||||
and self.get_airzone_value(AZD_SPEEDS) is not None
|
||||
):
|
||||
self._set_fan_speeds()
|
||||
if self.get_airzone_value(AZD_DOUBLE_SET_POINT):
|
||||
self._attr_supported_features |= (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
|
||||
)
|
||||
|
||||
self._async_update_attrs()
|
||||
|
||||
@@ -201,9 +212,12 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
|
||||
|
||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set new target temperature."""
|
||||
params = {
|
||||
API_SET_POINT: kwargs.get(ATTR_TEMPERATURE),
|
||||
}
|
||||
params = {}
|
||||
if ATTR_TEMPERATURE in kwargs:
|
||||
params[API_SET_POINT] = kwargs[ATTR_TEMPERATURE]
|
||||
if ATTR_TARGET_TEMP_LOW in kwargs and ATTR_TARGET_TEMP_HIGH in kwargs:
|
||||
params[API_COOL_SET_POINT] = kwargs[ATTR_TARGET_TEMP_LOW]
|
||||
params[API_HEAT_SET_POINT] = kwargs[ATTR_TARGET_TEMP_HIGH]
|
||||
await self._async_update_hvac_params(params)
|
||||
|
||||
@callback
|
||||
@@ -229,3 +243,10 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
|
||||
self._attr_target_temperature = self.get_airzone_value(AZD_TEMP_SET)
|
||||
if self.supported_features & ClimateEntityFeature.FAN_MODE:
|
||||
self._attr_fan_mode = self._speeds.get(self.get_airzone_value(AZD_SPEED))
|
||||
if self.supported_features & ClimateEntityFeature.TARGET_TEMPERATURE_RANGE:
|
||||
self._attr_target_temperature_high = self.get_airzone_value(
|
||||
AZD_HEAT_TEMP_SET
|
||||
)
|
||||
self._attr_target_temperature_low = self.get_airzone_value(
|
||||
AZD_COOL_TEMP_SET
|
||||
)
|
||||
|
||||
@@ -44,6 +44,7 @@ PLATFORMS = [
|
||||
Platform.NUMBER,
|
||||
Platform.SELECT,
|
||||
Platform.SENSOR,
|
||||
Platform.SWITCH,
|
||||
]
|
||||
|
||||
SERVICE_UPDATE_STATE = "update_state"
|
||||
|
||||
@@ -53,12 +53,6 @@ BUTTON_TYPES: tuple[BMWButtonEntityDescription, ...] = (
|
||||
name="Activate air conditioning",
|
||||
remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_air_conditioning(),
|
||||
),
|
||||
BMWButtonEntityDescription(
|
||||
key="deactivate_air_conditioning",
|
||||
icon="mdi:hvac-off",
|
||||
name="Deactivate air conditioning",
|
||||
remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_air_conditioning_stop(),
|
||||
),
|
||||
BMWButtonEntityDescription(
|
||||
key="find_vehicle",
|
||||
icon="mdi:crosshairs-question",
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
"""Switch platform for BMW."""
|
||||
|
||||
from collections.abc import Callable, Coroutine
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from bimmer_connected.models import MyBMWAPIError
|
||||
from bimmer_connected.vehicle import MyBMWVehicle
|
||||
|
||||
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import BMWBaseEntity
|
||||
from .const import DOMAIN
|
||||
from .coordinator import BMWDataUpdateCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class BMWRequiredKeysMixin:
|
||||
"""Mixin for required keys."""
|
||||
|
||||
value_fn: Callable[[MyBMWVehicle], bool]
|
||||
remote_service_on: Callable[[MyBMWVehicle], Coroutine[Any, Any, Any]]
|
||||
remote_service_off: Callable[[MyBMWVehicle], Coroutine[Any, Any, Any]]
|
||||
|
||||
|
||||
@dataclass
|
||||
class BMWSwitchEntityDescription(SwitchEntityDescription, BMWRequiredKeysMixin):
|
||||
"""Describes BMW switch entity."""
|
||||
|
||||
is_available: Callable[[MyBMWVehicle], bool] = lambda _: False
|
||||
dynamic_options: Callable[[MyBMWVehicle], list[str]] | None = None
|
||||
|
||||
|
||||
NUMBER_TYPES: list[BMWSwitchEntityDescription] = [
|
||||
BMWSwitchEntityDescription(
|
||||
key="climate",
|
||||
name="Climate",
|
||||
is_available=lambda v: v.is_remote_climate_stop_enabled,
|
||||
value_fn=lambda v: v.climate.is_climate_on,
|
||||
remote_service_on=lambda v: v.remote_services.trigger_remote_air_conditioning(),
|
||||
remote_service_off=lambda v: v.remote_services.trigger_remote_air_conditioning_stop(),
|
||||
icon="mdi:fan",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the MyBMW switch from config entry."""
|
||||
coordinator: BMWDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||
|
||||
entities: list[BMWSwitch] = []
|
||||
|
||||
for vehicle in coordinator.account.vehicles:
|
||||
if not coordinator.read_only:
|
||||
entities.extend(
|
||||
[
|
||||
BMWSwitch(coordinator, vehicle, description)
|
||||
for description in NUMBER_TYPES
|
||||
if description.is_available(vehicle)
|
||||
]
|
||||
)
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class BMWSwitch(BMWBaseEntity, SwitchEntity):
|
||||
"""Representation of BMW Switch entity."""
|
||||
|
||||
entity_description: BMWSwitchEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: BMWDataUpdateCoordinator,
|
||||
vehicle: MyBMWVehicle,
|
||||
description: BMWSwitchEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize an BMW Switch."""
|
||||
super().__init__(coordinator, vehicle)
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{vehicle.vin}-{description.key}"
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return the entity value to represent the entity state."""
|
||||
return self.entity_description.value_fn(self.vehicle)
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the switch on."""
|
||||
try:
|
||||
await self.entity_description.remote_service_on(self.vehicle)
|
||||
except MyBMWAPIError as ex:
|
||||
raise HomeAssistantError(ex) from ex
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the switch off."""
|
||||
try:
|
||||
await self.entity_description.remote_service_off(self.vehicle)
|
||||
except MyBMWAPIError as ex:
|
||||
raise HomeAssistantError(ex) from ex
|
||||
@@ -41,8 +41,9 @@ async def async_setup(hass):
|
||||
hass.http.register_view(OptionManagerFlowIndexView(hass.config_entries.options))
|
||||
hass.http.register_view(OptionManagerFlowResourceView(hass.config_entries.options))
|
||||
|
||||
websocket_api.async_register_command(hass, config_entries_get)
|
||||
websocket_api.async_register_command(hass, config_entries_get_matching)
|
||||
websocket_api.async_register_command(hass, config_entry_disable)
|
||||
websocket_api.async_register_command(hass, config_entry_get)
|
||||
websocket_api.async_register_command(hass, config_entry_update)
|
||||
websocket_api.async_register_command(hass, config_entries_subscribe)
|
||||
websocket_api.async_register_command(hass, config_entries_progress)
|
||||
@@ -284,6 +285,28 @@ def get_entry(
|
||||
return entry
|
||||
|
||||
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
"type": "config_entries/get",
|
||||
"entry_id": str,
|
||||
}
|
||||
)
|
||||
@websocket_api.async_response
|
||||
async def config_entry_get(
|
||||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
) -> None:
|
||||
"""Update config entry."""
|
||||
entry = get_entry(hass, connection, msg["entry_id"], msg["id"])
|
||||
if entry is None:
|
||||
return
|
||||
|
||||
result = {"config_entry": entry_json(entry)}
|
||||
connection.send_result(msg["id"], result)
|
||||
|
||||
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
@@ -409,13 +432,13 @@ async def ignore_config_flow(
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required("type"): "config_entries/get",
|
||||
vol.Required("type"): "config_entries/get_matching",
|
||||
vol.Optional("type_filter"): vol.All(cv.ensure_list, [str]),
|
||||
vol.Optional("domain"): str,
|
||||
}
|
||||
)
|
||||
@websocket_api.async_response
|
||||
async def config_entries_get(
|
||||
async def config_entries_get_matching(
|
||||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
|
||||
@@ -37,6 +37,7 @@ PLATFORMS_BY_TYPE: dict[str, list] = {
|
||||
Platform.CAMERA,
|
||||
Platform.SENSOR,
|
||||
Platform.SWITCH,
|
||||
Platform.UPDATE,
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -25,10 +25,6 @@ BINARY_SENSOR_TYPES: dict[str, BinarySensorEntityDescription] = {
|
||||
key="alarm_schedules_enabled"
|
||||
),
|
||||
"encrypted": BinarySensorEntityDescription(key="encrypted"),
|
||||
"upgrade_available": BinarySensorEntityDescription(
|
||||
key="upgrade_available",
|
||||
device_class=BinarySensorDeviceClass.UPDATE,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/ezviz",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["paho_mqtt", "pyezviz"],
|
||||
"requirements": ["pyezviz==0.2.0.9"]
|
||||
"requirements": ["pyezviz==0.2.0.12"]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
"""Support for EZVIZ sensors."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pyezviz import HTTPError, PyEzvizError
|
||||
|
||||
from homeassistant.components.update import (
|
||||
UpdateDeviceClass,
|
||||
UpdateEntity,
|
||||
UpdateEntityDescription,
|
||||
UpdateEntityFeature,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DATA_COORDINATOR, DOMAIN
|
||||
from .coordinator import EzvizDataUpdateCoordinator
|
||||
from .entity import EzvizEntity
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
UPDATE_ENTITY_TYPES = UpdateEntityDescription(
|
||||
key="version",
|
||||
name="Firmware update",
|
||||
device_class=UpdateDeviceClass.FIRMWARE,
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up EZVIZ sensors based on a config entry."""
|
||||
coordinator: EzvizDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][
|
||||
DATA_COORDINATOR
|
||||
]
|
||||
|
||||
async_add_entities(
|
||||
EzvizUpdateEntity(coordinator, camera, sensor, UPDATE_ENTITY_TYPES)
|
||||
for camera in coordinator.data
|
||||
for sensor, value in coordinator.data[camera].items()
|
||||
if sensor in UPDATE_ENTITY_TYPES.key
|
||||
if value
|
||||
)
|
||||
|
||||
|
||||
class EzvizUpdateEntity(EzvizEntity, UpdateEntity):
|
||||
"""Representation of a EZVIZ Update entity."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_supported_features = (
|
||||
UpdateEntityFeature.INSTALL
|
||||
| UpdateEntityFeature.PROGRESS
|
||||
| UpdateEntityFeature.RELEASE_NOTES
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: EzvizDataUpdateCoordinator,
|
||||
serial: str,
|
||||
sensor: str,
|
||||
description: UpdateEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(coordinator, serial)
|
||||
self._attr_unique_id = f"{serial}_{sensor}"
|
||||
self.entity_description = description
|
||||
|
||||
@property
|
||||
def installed_version(self) -> str | None:
|
||||
"""Version installed and in use."""
|
||||
return self.data["version"]
|
||||
|
||||
@property
|
||||
def in_progress(self) -> bool | int | None:
|
||||
"""Update installation progress."""
|
||||
if self.data["upgrade_in_progress"]:
|
||||
return self.data["upgrade_percent"]
|
||||
return False
|
||||
|
||||
@property
|
||||
def latest_version(self) -> str | None:
|
||||
"""Latest version available for install."""
|
||||
if self.data["upgrade_available"]:
|
||||
return self.data["latest_firmware_info"]["version"]
|
||||
|
||||
return self.installed_version
|
||||
|
||||
def release_notes(self) -> str | None:
|
||||
"""Return full release notes."""
|
||||
if self.data["latest_firmware_info"]:
|
||||
return self.data["latest_firmware_info"].get("desc")
|
||||
return None
|
||||
|
||||
async def async_install(
|
||||
self, version: str | None, backup: bool, **kwargs: Any
|
||||
) -> None:
|
||||
"""Install an update."""
|
||||
try:
|
||||
await self.hass.async_add_executor_job(
|
||||
self.coordinator.ezviz_client.upgrade_device, self._serial
|
||||
)
|
||||
|
||||
except (HTTPError, PyEzvizError) as err:
|
||||
raise HomeAssistantError(
|
||||
f"Failed to update firmware on {self.name}"
|
||||
) from err
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/glances",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["glances_api"],
|
||||
"requirements": ["glances_api==0.4.1"]
|
||||
"requirements": ["glances_api==0.4.2"]
|
||||
}
|
||||
|
||||
@@ -197,25 +197,23 @@ class IntegrationSensor(RestoreEntity, SensorEntity):
|
||||
old_state: State | None = event.data.get("old_state")
|
||||
new_state: State | None = event.data.get("new_state")
|
||||
|
||||
if (
|
||||
source_state := self.hass.states.get(self._sensor_source_id)
|
||||
) is None or source_state.state == STATE_UNAVAILABLE:
|
||||
self._attr_available = False
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
|
||||
self._attr_available = True
|
||||
|
||||
if new_state is None or new_state.state in (
|
||||
STATE_UNKNOWN,
|
||||
STATE_UNAVAILABLE,
|
||||
):
|
||||
return
|
||||
|
||||
# We may want to update our state before an early return,
|
||||
# based on the source sensor's unit_of_measurement
|
||||
# or device_class.
|
||||
update_state = False
|
||||
|
||||
if (
|
||||
source_state := self.hass.states.get(self._sensor_source_id)
|
||||
) is None or source_state.state == STATE_UNAVAILABLE:
|
||||
self._attr_available = False
|
||||
update_state = True
|
||||
else:
|
||||
self._attr_available = True
|
||||
|
||||
if old_state is None or new_state is None:
|
||||
# we can't calculate the elapsed time, so we can't calculate the integral
|
||||
return
|
||||
|
||||
unit = new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||
if unit is not None:
|
||||
new_unit_of_measurement = self._unit(unit)
|
||||
@@ -235,31 +233,53 @@ class IntegrationSensor(RestoreEntity, SensorEntity):
|
||||
if update_state:
|
||||
self.async_write_ha_state()
|
||||
|
||||
if old_state is None or old_state.state in (
|
||||
STATE_UNKNOWN,
|
||||
STATE_UNAVAILABLE,
|
||||
):
|
||||
return
|
||||
|
||||
try:
|
||||
# integration as the Riemann integral of previous measures.
|
||||
area = Decimal(0)
|
||||
elapsed_time = (
|
||||
new_state.last_updated - old_state.last_updated
|
||||
).total_seconds()
|
||||
|
||||
if self._method == METHOD_TRAPEZOIDAL:
|
||||
if (
|
||||
self._method == METHOD_TRAPEZOIDAL
|
||||
and new_state.state
|
||||
not in (
|
||||
STATE_UNKNOWN,
|
||||
STATE_UNAVAILABLE,
|
||||
)
|
||||
and old_state.state
|
||||
not in (
|
||||
STATE_UNKNOWN,
|
||||
STATE_UNAVAILABLE,
|
||||
)
|
||||
):
|
||||
area = (
|
||||
(Decimal(new_state.state) + Decimal(old_state.state))
|
||||
* Decimal(elapsed_time)
|
||||
/ 2
|
||||
)
|
||||
elif self._method == METHOD_LEFT:
|
||||
elif self._method == METHOD_LEFT and old_state.state not in (
|
||||
STATE_UNKNOWN,
|
||||
STATE_UNAVAILABLE,
|
||||
):
|
||||
area = Decimal(old_state.state) * Decimal(elapsed_time)
|
||||
elif self._method == METHOD_RIGHT:
|
||||
elif self._method == METHOD_RIGHT and new_state.state not in (
|
||||
STATE_UNKNOWN,
|
||||
STATE_UNAVAILABLE,
|
||||
):
|
||||
area = Decimal(new_state.state) * Decimal(elapsed_time)
|
||||
else:
|
||||
_LOGGER.debug(
|
||||
"Could not apply method %s to %s -> %s",
|
||||
self._method,
|
||||
old_state.state,
|
||||
new_state.state,
|
||||
)
|
||||
return
|
||||
|
||||
integral = area / (self._unit_prefix * self._unit_time)
|
||||
_LOGGER.debug(
|
||||
"area = %s, integral = %s state = %s", area, integral, self._state
|
||||
)
|
||||
assert isinstance(integral, Decimal)
|
||||
except ValueError as err:
|
||||
_LOGGER.warning("While calculating integration: %s", err)
|
||||
|
||||
@@ -12,6 +12,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DOMAIN
|
||||
from .helper import get_hostname_id
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -32,9 +33,10 @@ async def test_connection(hass: HomeAssistant, data) -> str:
|
||||
session = async_get_clientsession(hass)
|
||||
async with ApiClient(session, data["host"]) as client:
|
||||
await client.login(data["password"])
|
||||
values = await client.get_setting_values("scb:network", "Hostname")
|
||||
hostname_id = await get_hostname_id(client)
|
||||
values = await client.get_setting_values("scb:network", hostname_id)
|
||||
|
||||
return values["scb:network"]["Hostname"]
|
||||
return values["scb:network"][hostname_id]
|
||||
|
||||
|
||||
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
||||
@@ -23,6 +23,7 @@ from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
_DataT = TypeVar("_DataT")
|
||||
_KNOWN_HOSTNAME_IDS = ("Network:Hostname", "Hostname")
|
||||
|
||||
|
||||
class Plenticore:
|
||||
@@ -69,6 +70,7 @@ class Plenticore:
|
||||
)
|
||||
|
||||
# get some device meta data
|
||||
hostname_id = await get_hostname_id(self._client)
|
||||
settings = await self._client.get_setting_values(
|
||||
{
|
||||
"devices:local": [
|
||||
@@ -78,7 +80,7 @@ class Plenticore:
|
||||
"Properties:VersionIOC",
|
||||
"Properties:VersionMC",
|
||||
],
|
||||
"scb:network": ["Hostname"],
|
||||
"scb:network": [hostname_id],
|
||||
}
|
||||
)
|
||||
|
||||
@@ -91,7 +93,7 @@ class Plenticore:
|
||||
identifiers={(DOMAIN, device_local["Properties:SerialNo"])},
|
||||
manufacturer="Kostal",
|
||||
model=f"{prod1} {prod2}",
|
||||
name=settings["scb:network"]["Hostname"],
|
||||
name=settings["scb:network"][hostname_id],
|
||||
sw_version=f'IOC: {device_local["Properties:VersionIOC"]}'
|
||||
+ f' MC: {device_local["Properties:VersionMC"]}',
|
||||
)
|
||||
@@ -403,3 +405,12 @@ class PlenticoreDataFormatter:
|
||||
return state
|
||||
|
||||
return PlenticoreDataFormatter.EM_STATES.get(value)
|
||||
|
||||
|
||||
async def get_hostname_id(client: ApiClient) -> str:
|
||||
"""Check for known existing hostname ids."""
|
||||
all_settings = await client.get_settings()
|
||||
for entry in all_settings["scb:network"]:
|
||||
if entry.id in _KNOWN_HOSTNAME_IDS:
|
||||
return entry.id
|
||||
raise ApiException("Hostname identifier not found in KNOWN_HOSTNAME_IDS")
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/mystrom",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pymystrom"],
|
||||
"requirements": ["python-mystrom==1.1.2"]
|
||||
"requirements": ["python-mystrom==2.2.0"]
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ class PlugwiseBinarySensorEntity(PlugwiseEntity, BinarySensorEntity):
|
||||
self._attr_unique_id = f"{device_id}-{description.key}"
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool | None:
|
||||
def is_on(self) -> bool:
|
||||
"""Return true if the binary sensor is on."""
|
||||
return self.entity_description.value_fn(self.device["binary_sensors"])
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
"""Plugwise Switch component for HomeAssistant."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from plugwise import SmileSwitches
|
||||
|
||||
from homeassistant.components.switch import (
|
||||
SwitchDeviceClass,
|
||||
SwitchEntity,
|
||||
@@ -18,29 +22,46 @@ from .coordinator import PlugwiseDataUpdateCoordinator
|
||||
from .entity import PlugwiseEntity
|
||||
from .util import plugwise_command
|
||||
|
||||
SWITCHES: tuple[SwitchEntityDescription, ...] = (
|
||||
SwitchEntityDescription(
|
||||
|
||||
@dataclass
|
||||
class PlugwiseSwitchBaseMixin:
|
||||
"""Mixin for required Plugwise switch description keys."""
|
||||
|
||||
value_fn: Callable[[SmileSwitches], bool]
|
||||
|
||||
|
||||
@dataclass
|
||||
class PlugwiseSwitchEntityDescription(SwitchEntityDescription, PlugwiseSwitchBaseMixin):
|
||||
"""Describes Plugwise switch entity."""
|
||||
|
||||
|
||||
SWITCHES: tuple[PlugwiseSwitchEntityDescription, ...] = (
|
||||
PlugwiseSwitchEntityDescription(
|
||||
key="dhw_cm_switch",
|
||||
translation_key="dhw_cm_switch",
|
||||
icon="mdi:water-plus",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
value_fn=lambda data: data["dhw_cm_switch"],
|
||||
),
|
||||
SwitchEntityDescription(
|
||||
PlugwiseSwitchEntityDescription(
|
||||
key="lock",
|
||||
translation_key="lock",
|
||||
icon="mdi:lock",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
value_fn=lambda data: data["lock"],
|
||||
),
|
||||
SwitchEntityDescription(
|
||||
PlugwiseSwitchEntityDescription(
|
||||
key="relay",
|
||||
translation_key="relay",
|
||||
device_class=SwitchDeviceClass.SWITCH,
|
||||
value_fn=lambda data: data["relay"],
|
||||
),
|
||||
SwitchEntityDescription(
|
||||
PlugwiseSwitchEntityDescription(
|
||||
key="cooling_ena_switch",
|
||||
name="Cooling",
|
||||
icon="mdi:snowflake-thermometer",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
value_fn=lambda data: data["cooling_ena_switch"],
|
||||
),
|
||||
)
|
||||
|
||||
@@ -54,8 +75,10 @@ async def async_setup_entry(
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||
entities: list[PlugwiseSwitchEntity] = []
|
||||
for device_id, device in coordinator.data.devices.items():
|
||||
if not (switches := device.get("switches")):
|
||||
continue
|
||||
for description in SWITCHES:
|
||||
if "switches" not in device or description.key not in device["switches"]:
|
||||
if description.key not in switches:
|
||||
continue
|
||||
entities.append(PlugwiseSwitchEntity(coordinator, device_id, description))
|
||||
async_add_entities(entities)
|
||||
@@ -64,11 +87,13 @@ async def async_setup_entry(
|
||||
class PlugwiseSwitchEntity(PlugwiseEntity, SwitchEntity):
|
||||
"""Representation of a Plugwise plug."""
|
||||
|
||||
entity_description: PlugwiseSwitchEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: PlugwiseDataUpdateCoordinator,
|
||||
device_id: str,
|
||||
description: SwitchEntityDescription,
|
||||
description: PlugwiseSwitchEntityDescription,
|
||||
) -> None:
|
||||
"""Set up the Plugwise API."""
|
||||
super().__init__(coordinator, device_id)
|
||||
@@ -76,9 +101,9 @@ class PlugwiseSwitchEntity(PlugwiseEntity, SwitchEntity):
|
||||
self._attr_unique_id = f"{device_id}-{description.key}"
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool | None:
|
||||
def is_on(self) -> bool:
|
||||
"""Return True if entity is on."""
|
||||
return self.device["switches"].get(self.entity_description.key)
|
||||
return self.entity_description.value_fn(self.device["switches"])
|
||||
|
||||
@plugwise_command
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
|
||||
@@ -3,14 +3,11 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Coroutine, Mapping
|
||||
from functools import partial
|
||||
import socket
|
||||
from typing import Any
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import getmac
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import ssdp
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
@@ -18,7 +15,6 @@ from homeassistant.const import (
|
||||
CONF_MAC,
|
||||
CONF_METHOD,
|
||||
CONF_MODEL,
|
||||
CONF_NAME,
|
||||
CONF_PORT,
|
||||
CONF_TOKEN,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
@@ -30,10 +26,8 @@ from homeassistant.helpers import (
|
||||
config_validation as cv,
|
||||
device_registry as dr,
|
||||
entity_registry as er,
|
||||
issue_registry as ir,
|
||||
)
|
||||
from homeassistant.helpers.debounce import Debouncer
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .bridge import (
|
||||
SamsungTVBridge,
|
||||
@@ -42,11 +36,9 @@ from .bridge import (
|
||||
model_requires_encryption,
|
||||
)
|
||||
from .const import (
|
||||
CONF_ON_ACTION,
|
||||
CONF_SESSION_ID,
|
||||
CONF_SSDP_MAIN_TV_AGENT_LOCATION,
|
||||
CONF_SSDP_RENDERING_CONTROL_LOCATION,
|
||||
DEFAULT_NAME,
|
||||
DOMAIN,
|
||||
ENTRY_RELOAD_COOLDOWN,
|
||||
LEGACY_PORT,
|
||||
@@ -57,73 +49,9 @@ from .const import (
|
||||
UPNP_SVC_RENDERING_CONTROL,
|
||||
)
|
||||
|
||||
|
||||
def ensure_unique_hosts(value: dict[Any, Any]) -> dict[Any, Any]:
|
||||
"""Validate that all configs have a unique host."""
|
||||
vol.Schema(vol.Unique("duplicate host entries found"))(
|
||||
[entry[CONF_HOST] for entry in value]
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
PLATFORMS = [Platform.MEDIA_PLAYER]
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
DOMAIN: vol.All(
|
||||
cv.ensure_list,
|
||||
[
|
||||
cv.deprecated(CONF_PORT),
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_PORT): cv.port,
|
||||
vol.Optional(CONF_ON_ACTION): cv.SCRIPT_SCHEMA,
|
||||
}
|
||||
),
|
||||
],
|
||||
ensure_unique_hosts,
|
||||
)
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the Samsung TV integration."""
|
||||
hass.data[DOMAIN] = {}
|
||||
if DOMAIN not in config:
|
||||
return True
|
||||
|
||||
ir.async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"deprecated_yaml",
|
||||
breaks_in_ha_version="2023.6.0",
|
||||
is_fixable=False,
|
||||
severity=ir.IssueSeverity.WARNING,
|
||||
translation_key="deprecated_yaml",
|
||||
translation_placeholders={
|
||||
"on_action_url": "https://www.home-assistant.io/integrations/samsungtv/#turn-on-action"
|
||||
},
|
||||
learn_more_url="https://www.home-assistant.io/integrations/samsungtv/#turn-on-action",
|
||||
)
|
||||
for entry_config in config[DOMAIN]:
|
||||
ip_address = await hass.async_add_executor_job(
|
||||
socket.gethostbyname, entry_config[CONF_HOST]
|
||||
)
|
||||
hass.data[DOMAIN][ip_address] = {
|
||||
CONF_ON_ACTION: entry_config.get(CONF_ON_ACTION)
|
||||
}
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data=entry_config,
|
||||
)
|
||||
)
|
||||
return True
|
||||
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
|
||||
|
||||
|
||||
@callback
|
||||
@@ -195,6 +123,7 @@ async def _async_update_ssdp_locations(hass: HomeAssistant, entry: ConfigEntry)
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up the Samsung TV platform."""
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
|
||||
# Initialize bridge
|
||||
if entry.data.get(CONF_METHOD) == METHOD_ENCRYPTED_WEBSOCKET:
|
||||
|
||||
@@ -35,12 +35,9 @@ from .const import (
|
||||
CONF_SSDP_RENDERING_CONTROL_LOCATION,
|
||||
DEFAULT_MANUFACTURER,
|
||||
DOMAIN,
|
||||
ENCRYPTED_WEBSOCKET_PORT,
|
||||
LEGACY_PORT,
|
||||
LOGGER,
|
||||
METHOD_ENCRYPTED_WEBSOCKET,
|
||||
METHOD_LEGACY,
|
||||
METHOD_WEBSOCKET,
|
||||
RESULT_AUTH_MISSING,
|
||||
RESULT_CANNOT_CONNECT,
|
||||
RESULT_INVALID_PIN,
|
||||
@@ -50,7 +47,6 @@ from .const import (
|
||||
SUCCESSFUL_RESULTS,
|
||||
UPNP_SVC_MAIN_TV_AGENT,
|
||||
UPNP_SVC_RENDERING_CONTROL,
|
||||
WEBSOCKET_PORTS,
|
||||
)
|
||||
|
||||
DATA_SCHEMA = vol.Schema({vol.Required(CONF_HOST): str, vol.Required(CONF_NAME): str})
|
||||
@@ -231,25 +227,6 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
self._mac = mac
|
||||
return True
|
||||
|
||||
async def async_step_import(self, user_input: dict[str, Any]) -> FlowResult:
|
||||
"""Handle configuration by yaml file."""
|
||||
# We need to import even if we cannot validate
|
||||
# since the TV may be off at startup
|
||||
await self._async_set_name_host_from_input(user_input)
|
||||
self._async_abort_entries_match({CONF_HOST: self._host})
|
||||
port = user_input.get(CONF_PORT)
|
||||
if port in WEBSOCKET_PORTS:
|
||||
user_input[CONF_METHOD] = METHOD_WEBSOCKET
|
||||
elif port == ENCRYPTED_WEBSOCKET_PORT:
|
||||
user_input[CONF_METHOD] = METHOD_ENCRYPTED_WEBSOCKET
|
||||
elif port == LEGACY_PORT:
|
||||
user_input[CONF_METHOD] = METHOD_LEGACY
|
||||
user_input[CONF_MANUFACTURER] = DEFAULT_MANUFACTURER
|
||||
return self.async_create_entry(
|
||||
title=self._title,
|
||||
data=user_input,
|
||||
)
|
||||
|
||||
async def _async_set_name_host_from_input(self, user_input: dict[str, Any]) -> None:
|
||||
try:
|
||||
self._host = await self.hass.async_add_executor_job(
|
||||
|
||||
@@ -6,7 +6,6 @@ DOMAIN = "samsungtv"
|
||||
|
||||
ATTR_PROPERTIES = "properties"
|
||||
|
||||
DEFAULT_NAME = "Samsung TV"
|
||||
DEFAULT_MANUFACTURER = "Samsung"
|
||||
|
||||
VALUE_CONF_NAME = "HomeAssistant"
|
||||
@@ -16,7 +15,6 @@ CONF_DESCRIPTION = "description"
|
||||
CONF_MANUFACTURER = "manufacturer"
|
||||
CONF_SSDP_RENDERING_CONTROL_LOCATION = "ssdp_rendering_control_location"
|
||||
CONF_SSDP_MAIN_TV_AGENT_LOCATION = "ssdp_main_tv_agent_location"
|
||||
CONF_ON_ACTION = "turn_on_action"
|
||||
CONF_SESSION_ID = "session_id"
|
||||
|
||||
RESULT_AUTH_MISSING = "auth_missing"
|
||||
|
||||
@@ -41,16 +41,13 @@ from homeassistant.helpers import (
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.script import Script
|
||||
from homeassistant.helpers.trigger import PluggableAction
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .bridge import SamsungTVBridge, SamsungTVWSBridge
|
||||
from .const import (
|
||||
CONF_MANUFACTURER,
|
||||
CONF_ON_ACTION,
|
||||
CONF_SSDP_RENDERING_CONTROL_LOCATION,
|
||||
DEFAULT_NAME,
|
||||
DOMAIN,
|
||||
LOGGER,
|
||||
)
|
||||
@@ -86,16 +83,7 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Set up the Samsung TV from a config entry."""
|
||||
bridge = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
host = entry.data[CONF_HOST]
|
||||
on_script = None
|
||||
data = hass.data[DOMAIN]
|
||||
if turn_on_action := data.get(host, {}).get(CONF_ON_ACTION):
|
||||
on_script = Script(
|
||||
hass, turn_on_action, entry.data.get(CONF_NAME, DEFAULT_NAME), DOMAIN
|
||||
)
|
||||
|
||||
async_add_entities([SamsungTVDevice(bridge, entry, on_script)], True)
|
||||
async_add_entities([SamsungTVDevice(bridge, entry)], True)
|
||||
|
||||
|
||||
class SamsungTVDevice(MediaPlayerEntity):
|
||||
@@ -107,7 +95,6 @@ class SamsungTVDevice(MediaPlayerEntity):
|
||||
self,
|
||||
bridge: SamsungTVBridge,
|
||||
config_entry: ConfigEntry,
|
||||
on_script: Script | None,
|
||||
) -> None:
|
||||
"""Initialize the Samsung device."""
|
||||
self._config_entry = config_entry
|
||||
@@ -117,7 +104,6 @@ class SamsungTVDevice(MediaPlayerEntity):
|
||||
CONF_SSDP_RENDERING_CONTROL_LOCATION
|
||||
)
|
||||
self._turn_on = PluggableAction(self.async_write_ha_state)
|
||||
self._on_script = on_script
|
||||
# Assume that the TV is in Play mode
|
||||
self._playing: bool = True
|
||||
|
||||
@@ -130,8 +116,8 @@ class SamsungTVDevice(MediaPlayerEntity):
|
||||
self._app_list_event: asyncio.Event = asyncio.Event()
|
||||
|
||||
self._attr_supported_features = SUPPORT_SAMSUNGTV
|
||||
if self._on_script or self._mac:
|
||||
# (deprecated) add turn-on if on_script YAML or mac is available
|
||||
if self._mac:
|
||||
# (deprecated) add turn-on if mac is available
|
||||
# Triggers have not yet been registered so this is adjusted in the property
|
||||
self._attr_supported_features |= MediaPlayerEntityFeature.TURN_ON
|
||||
if self._ssdp_rendering_control_location:
|
||||
@@ -375,7 +361,6 @@ class SamsungTVDevice(MediaPlayerEntity):
|
||||
return (
|
||||
self.state == MediaPlayerState.ON
|
||||
or bool(self._turn_on)
|
||||
or self._on_script is not None
|
||||
or self._mac is not None
|
||||
or self._power_off_in_progress()
|
||||
)
|
||||
@@ -477,9 +462,6 @@ class SamsungTVDevice(MediaPlayerEntity):
|
||||
"""Turn the media player on."""
|
||||
if self._turn_on:
|
||||
await self._turn_on.async_run(self.hass, self._context)
|
||||
elif self._on_script:
|
||||
# YAML on_script is deprecated - replaced by turn_on trigger
|
||||
await self._on_script.async_run(context=self._context)
|
||||
elif self._mac:
|
||||
await self.hass.async_add_executor_job(self._wake_on_lan)
|
||||
|
||||
|
||||
@@ -44,11 +44,5 @@
|
||||
"trigger_type": {
|
||||
"samsungtv.turn_on": "Device is requested to turn on"
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"deprecated_yaml": {
|
||||
"title": "The SamsungTV YAML configuration is being removed",
|
||||
"description": "Configuring SamsungTV using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the SamsungTV YAML configuration from your `configuration.yaml` file and restart Home Assistant to fix this issue.\n\nPlease note that previously configured `turn_on_action` needs to be manually converted to use the `turn_on` trigger ([documentation]({on_action_url}))."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"codeowners": ["@luar123"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/snapcast",
|
||||
"iot_class": "local_polling",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["construct", "snapcast"],
|
||||
"requirements": ["snapcast==2.3.3"]
|
||||
}
|
||||
|
||||
@@ -5097,7 +5097,7 @@
|
||||
"name": "Snapcast",
|
||||
"integration_type": "hub",
|
||||
"config_flow": true,
|
||||
"iot_class": "local_polling"
|
||||
"iot_class": "local_push"
|
||||
},
|
||||
"snips": {
|
||||
"name": "Snips",
|
||||
|
||||
@@ -156,7 +156,7 @@ pyOpenSSL>=23.1.0
|
||||
|
||||
# protobuf must be in package constraints for the wheel
|
||||
# builder to build binary wheels
|
||||
protobuf==4.22.3
|
||||
protobuf==4.23.1
|
||||
|
||||
# faust-cchardet: Ensure we have a version we can build wheels
|
||||
# 2.1.18 is the first version that works with our wheel builder
|
||||
|
||||
@@ -802,7 +802,7 @@ gios==3.1.0
|
||||
gitterpy==0.1.7
|
||||
|
||||
# homeassistant.components.glances
|
||||
glances_api==0.4.1
|
||||
glances_api==0.4.2
|
||||
|
||||
# homeassistant.components.goalzero
|
||||
goalzero==0.2.1
|
||||
@@ -1629,7 +1629,7 @@ pyeverlights==0.1.0
|
||||
pyevilgenius==2.0.0
|
||||
|
||||
# homeassistant.components.ezviz
|
||||
pyezviz==0.2.0.9
|
||||
pyezviz==0.2.0.12
|
||||
|
||||
# homeassistant.components.fibaro
|
||||
pyfibaro==0.7.1
|
||||
@@ -2098,7 +2098,7 @@ python-miio==0.5.12
|
||||
python-mpd2==3.0.5
|
||||
|
||||
# homeassistant.components.mystrom
|
||||
python-mystrom==1.1.2
|
||||
python-mystrom==2.2.0
|
||||
|
||||
# homeassistant.components.nest
|
||||
python-nest==4.2.0
|
||||
|
||||
@@ -624,7 +624,7 @@ getmac==0.8.2
|
||||
gios==3.1.0
|
||||
|
||||
# homeassistant.components.glances
|
||||
glances_api==0.4.1
|
||||
glances_api==0.4.2
|
||||
|
||||
# homeassistant.components.goalzero
|
||||
goalzero==0.2.1
|
||||
@@ -1190,7 +1190,7 @@ pyeverlights==0.1.0
|
||||
pyevilgenius==2.0.0
|
||||
|
||||
# homeassistant.components.ezviz
|
||||
pyezviz==0.2.0.9
|
||||
pyezviz==0.2.0.12
|
||||
|
||||
# homeassistant.components.fibaro
|
||||
pyfibaro==0.7.1
|
||||
|
||||
@@ -158,7 +158,7 @@ pyOpenSSL>=23.1.0
|
||||
|
||||
# protobuf must be in package constraints for the wheel
|
||||
# builder to build binary wheels
|
||||
protobuf==4.22.3
|
||||
protobuf==4.23.1
|
||||
|
||||
# faust-cchardet: Ensure we have a version we can build wheels
|
||||
# 2.1.18 is the first version that works with our wheel builder
|
||||
|
||||
@@ -3,7 +3,9 @@ from unittest.mock import patch
|
||||
|
||||
from aioairzone.common import OperationMode
|
||||
from aioairzone.const import (
|
||||
API_COOL_SET_POINT,
|
||||
API_DATA,
|
||||
API_HEAT_SET_POINT,
|
||||
API_MODE,
|
||||
API_ON,
|
||||
API_SET_POINT,
|
||||
@@ -25,6 +27,8 @@ from homeassistant.components.climate import (
|
||||
ATTR_HVAC_MODES,
|
||||
ATTR_MAX_TEMP,
|
||||
ATTR_MIN_TEMP,
|
||||
ATTR_TARGET_TEMP_HIGH,
|
||||
ATTR_TARGET_TEMP_LOW,
|
||||
ATTR_TARGET_TEMP_STEP,
|
||||
DOMAIN as CLIMATE_DOMAIN,
|
||||
FAN_AUTO,
|
||||
@@ -494,3 +498,39 @@ async def test_airzone_climate_set_temp_error(hass: HomeAssistant) -> None:
|
||||
|
||||
state = hass.states.get("climate.dorm_2")
|
||||
assert state.attributes.get(ATTR_TEMPERATURE) == 19.5
|
||||
|
||||
|
||||
async def test_airzone_climate_set_temp_range(hass: HomeAssistant) -> None:
|
||||
"""Test setting the target temperature range."""
|
||||
|
||||
HVAC_MOCK = {
|
||||
API_DATA: [
|
||||
{
|
||||
API_SYSTEM_ID: 3,
|
||||
API_ZONE_ID: 1,
|
||||
API_COOL_SET_POINT: 68.0,
|
||||
API_HEAT_SET_POINT: 77.0,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
await async_init_integration(hass)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.airzone.AirzoneLocalApi.put_hvac",
|
||||
return_value=HVAC_MOCK,
|
||||
):
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
{
|
||||
ATTR_ENTITY_ID: "climate.dkn_plus",
|
||||
ATTR_TARGET_TEMP_HIGH: 25.0,
|
||||
ATTR_TARGET_TEMP_LOW: 20.0,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get("climate.dkn_plus")
|
||||
assert state.attributes.get(ATTR_TARGET_TEMP_HIGH) == 25.0
|
||||
assert state.attributes.get(ATTR_TARGET_TEMP_LOW) == 20.0
|
||||
|
||||
@@ -36,7 +36,7 @@ async def test_options_flow_disabled_not_setup(
|
||||
await ws_client.send_json(
|
||||
{
|
||||
"id": 5,
|
||||
"type": "config_entries/get",
|
||||
"type": "config_entries/get_matching",
|
||||
"domain": "bluetooth",
|
||||
}
|
||||
)
|
||||
@@ -370,7 +370,7 @@ async def test_options_flow_disabled_macos(
|
||||
await ws_client.send_json(
|
||||
{
|
||||
"id": 5,
|
||||
"type": "config_entries/get",
|
||||
"type": "config_entries/get_matching",
|
||||
"domain": "bluetooth",
|
||||
}
|
||||
)
|
||||
@@ -403,7 +403,7 @@ async def test_options_flow_enabled_linux(
|
||||
await ws_client.send_json(
|
||||
{
|
||||
"id": 5,
|
||||
"type": "config_entries/get",
|
||||
"type": "config_entries/get_matching",
|
||||
"domain": "bluetooth",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# serializer version: 1
|
||||
# name: test_entity_state_attrs
|
||||
list([
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by MyBMW',
|
||||
'friendly_name': 'i4 eDrive40 Climate',
|
||||
'icon': 'mdi:fan',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.i4_edrive40_climate',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
@@ -0,0 +1,100 @@
|
||||
"""Test BMW switches."""
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from bimmer_connected.models import MyBMWAPIError, MyBMWRemoteServiceError
|
||||
from bimmer_connected.vehicle.remote_services import RemoteServices
|
||||
import pytest
|
||||
import respx
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from . import setup_mocked_integration
|
||||
|
||||
|
||||
async def test_entity_state_attrs(
|
||||
hass: HomeAssistant,
|
||||
bmw_fixture: respx.Router,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test switch options and values.."""
|
||||
|
||||
# Setup component
|
||||
assert await setup_mocked_integration(hass)
|
||||
|
||||
# Get all switch entities
|
||||
assert hass.states.async_all("switch") == snapshot
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("entity_id", "value"),
|
||||
[
|
||||
("switch.i4_edrive40_climate", "ON"),
|
||||
("switch.i4_edrive40_climate", "OFF"),
|
||||
],
|
||||
)
|
||||
async def test_update_triggers_success(
|
||||
hass: HomeAssistant,
|
||||
entity_id: str,
|
||||
value: str,
|
||||
bmw_fixture: respx.Router,
|
||||
) -> None:
|
||||
"""Test allowed values for switch inputs."""
|
||||
|
||||
# Setup component
|
||||
assert await setup_mocked_integration(hass)
|
||||
|
||||
# Test
|
||||
await hass.services.async_call(
|
||||
"switch",
|
||||
f"turn_{value.lower()}",
|
||||
blocking=True,
|
||||
target={"entity_id": entity_id},
|
||||
)
|
||||
assert RemoteServices.trigger_remote_service.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("raised", "expected"),
|
||||
[
|
||||
(MyBMWRemoteServiceError, HomeAssistantError),
|
||||
(MyBMWAPIError, HomeAssistantError),
|
||||
(ValueError, ValueError),
|
||||
],
|
||||
)
|
||||
async def test_update_triggers_exceptions(
|
||||
hass: HomeAssistant,
|
||||
raised: Exception,
|
||||
expected: Exception,
|
||||
bmw_fixture: respx.Router,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""Test not allowed values for switch inputs."""
|
||||
|
||||
# Setup component
|
||||
assert await setup_mocked_integration(hass)
|
||||
|
||||
# Setup exception
|
||||
monkeypatch.setattr(
|
||||
RemoteServices,
|
||||
"trigger_remote_service",
|
||||
AsyncMock(side_effect=raised),
|
||||
)
|
||||
|
||||
# Test
|
||||
with pytest.raises(expected):
|
||||
await hass.services.async_call(
|
||||
"switch",
|
||||
"turn_on",
|
||||
blocking=True,
|
||||
target={"entity_id": "switch.i4_edrive40_climate"},
|
||||
)
|
||||
with pytest.raises(expected):
|
||||
await hass.services.async_call(
|
||||
"switch",
|
||||
"turn_off",
|
||||
blocking=True,
|
||||
target={"entity_id": "switch.i4_edrive40_climate"},
|
||||
)
|
||||
assert RemoteServices.trigger_remote_service.call_count == 2
|
||||
@@ -965,6 +965,55 @@ async def test_options_flow_with_invalid_data(hass: HomeAssistant, client) -> No
|
||||
}
|
||||
|
||||
|
||||
async def test_get(hass: HomeAssistant, hass_ws_client: WebSocketGenerator) -> None:
|
||||
"""Test that we can get a config entry."""
|
||||
assert await async_setup_component(hass, "config", {})
|
||||
ws_client = await hass_ws_client(hass)
|
||||
|
||||
entry = MockConfigEntry(domain="demo", state=core_ce.ConfigEntryState.LOADED)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
assert entry.pref_disable_new_entities is False
|
||||
assert entry.pref_disable_polling is False
|
||||
|
||||
await ws_client.send_json_auto_id(
|
||||
{
|
||||
"type": "config_entries/get",
|
||||
"entry_id": entry.entry_id,
|
||||
}
|
||||
)
|
||||
response = await ws_client.receive_json()
|
||||
|
||||
assert response["success"]
|
||||
assert response["result"]["config_entry"] == {
|
||||
"disabled_by": None,
|
||||
"domain": "demo",
|
||||
"entry_id": entry.entry_id,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "user",
|
||||
"state": "loaded",
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
"title": "Mock Title",
|
||||
}
|
||||
|
||||
await ws_client.send_json_auto_id(
|
||||
{
|
||||
"type": "config_entries/get",
|
||||
"entry_id": "blah",
|
||||
}
|
||||
)
|
||||
response = await ws_client.receive_json()
|
||||
assert not response["success"]
|
||||
assert response["error"] == {
|
||||
"code": "not_found",
|
||||
"message": "Config entry not found",
|
||||
}
|
||||
|
||||
|
||||
async def test_update_prefrences(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
@@ -1209,7 +1258,7 @@ async def test_ignore_flow_nonexisting(
|
||||
assert response["error"]["code"] == "not_found"
|
||||
|
||||
|
||||
async def test_get_entries_ws(
|
||||
async def test_get_matching_entries_ws(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator, clear_handlers
|
||||
) -> None:
|
||||
"""Test get entries with the websocket api."""
|
||||
@@ -1260,14 +1309,8 @@ async def test_get_entries_ws(
|
||||
|
||||
ws_client = await hass_ws_client(hass)
|
||||
|
||||
await ws_client.send_json(
|
||||
{
|
||||
"id": 5,
|
||||
"type": "config_entries/get",
|
||||
}
|
||||
)
|
||||
await ws_client.send_json_auto_id({"type": "config_entries/get_matching"})
|
||||
response = await ws_client.receive_json()
|
||||
assert response["id"] == 5
|
||||
assert response["result"] == [
|
||||
{
|
||||
"disabled_by": None,
|
||||
@@ -1341,16 +1384,14 @@ async def test_get_entries_ws(
|
||||
},
|
||||
]
|
||||
|
||||
await ws_client.send_json(
|
||||
await ws_client.send_json_auto_id(
|
||||
{
|
||||
"id": 6,
|
||||
"type": "config_entries/get",
|
||||
"type": "config_entries/get_matching",
|
||||
"domain": "comp1",
|
||||
"type_filter": "hub",
|
||||
}
|
||||
)
|
||||
response = await ws_client.receive_json()
|
||||
assert response["id"] == 6
|
||||
assert response["result"] == [
|
||||
{
|
||||
"disabled_by": None,
|
||||
@@ -1368,15 +1409,13 @@ async def test_get_entries_ws(
|
||||
}
|
||||
]
|
||||
|
||||
await ws_client.send_json(
|
||||
await ws_client.send_json_auto_id(
|
||||
{
|
||||
"id": 7,
|
||||
"type": "config_entries/get",
|
||||
"type": "config_entries/get_matching",
|
||||
"type_filter": ["service", "device"],
|
||||
}
|
||||
)
|
||||
response = await ws_client.receive_json()
|
||||
assert response["id"] == 7
|
||||
assert response["result"] == [
|
||||
{
|
||||
"disabled_by": None,
|
||||
@@ -1408,15 +1447,13 @@ async def test_get_entries_ws(
|
||||
},
|
||||
]
|
||||
|
||||
await ws_client.send_json(
|
||||
await ws_client.send_json_auto_id(
|
||||
{
|
||||
"id": 8,
|
||||
"type": "config_entries/get",
|
||||
"type": "config_entries/get_matching",
|
||||
"type_filter": "hub",
|
||||
}
|
||||
)
|
||||
response = await ws_client.receive_json()
|
||||
assert response["id"] == 8
|
||||
assert response["result"] == [
|
||||
{
|
||||
"disabled_by": None,
|
||||
@@ -1453,16 +1490,14 @@ async def test_get_entries_ws(
|
||||
"homeassistant.components.config.config_entries.async_get_integrations",
|
||||
return_value={"any": IntegrationNotFound("any")},
|
||||
):
|
||||
await ws_client.send_json(
|
||||
await ws_client.send_json_auto_id(
|
||||
{
|
||||
"id": 9,
|
||||
"type": "config_entries/get",
|
||||
"type": "config_entries/get_matching",
|
||||
"type_filter": "hub",
|
||||
}
|
||||
)
|
||||
response = await ws_client.receive_json()
|
||||
|
||||
assert response["id"] == 9
|
||||
assert response["result"] == [
|
||||
{
|
||||
"disabled_by": None,
|
||||
@@ -1541,16 +1576,14 @@ async def test_get_entries_ws(
|
||||
"homeassistant.components.config.config_entries.async_get_integrations",
|
||||
return_value={"any": IntegrationNotFound("any")},
|
||||
):
|
||||
await ws_client.send_json(
|
||||
await ws_client.send_json_auto_id(
|
||||
{
|
||||
"id": 10,
|
||||
"type": "config_entries/get",
|
||||
"type": "config_entries/get_matching",
|
||||
"type_filter": ["helper"],
|
||||
}
|
||||
)
|
||||
response = await ws_client.receive_json()
|
||||
|
||||
assert response["id"] == 10
|
||||
assert response["result"] == []
|
||||
|
||||
# Verify we raise if something really goes wrong
|
||||
@@ -1559,16 +1592,14 @@ async def test_get_entries_ws(
|
||||
"homeassistant.components.config.config_entries.async_get_integrations",
|
||||
return_value={"any": Exception()},
|
||||
):
|
||||
await ws_client.send_json(
|
||||
await ws_client.send_json_auto_id(
|
||||
{
|
||||
"id": 11,
|
||||
"type": "config_entries/get",
|
||||
"type": "config_entries/get_matching",
|
||||
"type_filter": ["device", "hub", "service"],
|
||||
}
|
||||
)
|
||||
response = await ws_client.receive_json()
|
||||
|
||||
assert response["id"] == 11
|
||||
assert response["success"] is False
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
from datetime import timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass
|
||||
from homeassistant.const import (
|
||||
ATTR_UNIT_OF_MEASUREMENT,
|
||||
@@ -20,7 +22,8 @@ import homeassistant.util.dt as dt_util
|
||||
from tests.common import mock_restore_cache
|
||||
|
||||
|
||||
async def test_state(hass: HomeAssistant) -> None:
|
||||
@pytest.mark.parametrize("method", ["trapezoidal", "left", "right"])
|
||||
async def test_state(hass: HomeAssistant, method) -> None:
|
||||
"""Test integration sensor state."""
|
||||
config = {
|
||||
"sensor": {
|
||||
@@ -28,6 +31,7 @@ async def test_state(hass: HomeAssistant) -> None:
|
||||
"name": "integration",
|
||||
"source": "sensor.power",
|
||||
"round": 2,
|
||||
"method": method,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,8 +50,8 @@ async def test_state(hass: HomeAssistant) -> None:
|
||||
assert state.attributes.get("state_class") is SensorStateClass.TOTAL
|
||||
assert "device_class" not in state.attributes
|
||||
|
||||
future_now = dt_util.utcnow() + timedelta(seconds=3600)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=future_now):
|
||||
now += timedelta(seconds=3600)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=now):
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
1,
|
||||
@@ -69,6 +73,62 @@ async def test_state(hass: HomeAssistant) -> None:
|
||||
assert state.attributes.get("device_class") == SensorDeviceClass.ENERGY
|
||||
assert state.attributes.get("state_class") is SensorStateClass.TOTAL
|
||||
|
||||
# 1 hour after last update, power sensor is unavailable
|
||||
now += timedelta(seconds=3600)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=now):
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
STATE_UNAVAILABLE,
|
||||
{
|
||||
"device_class": SensorDeviceClass.POWER,
|
||||
ATTR_UNIT_OF_MEASUREMENT: UnitOfPower.KILO_WATT,
|
||||
},
|
||||
force_update=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("sensor.integration")
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
# 1 hour after last update, power sensor is back to normal at 2 KiloWatts and stays for 1 hour += 2kWh
|
||||
now += timedelta(seconds=3600)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=now):
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
2,
|
||||
{
|
||||
"device_class": SensorDeviceClass.POWER,
|
||||
ATTR_UNIT_OF_MEASUREMENT: UnitOfPower.KILO_WATT,
|
||||
},
|
||||
force_update=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("sensor.integration")
|
||||
assert (
|
||||
round(float(state.state), config["sensor"]["round"]) == 3.0
|
||||
if method == "right"
|
||||
else 1.0
|
||||
)
|
||||
|
||||
now += timedelta(seconds=3600)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=now):
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
2,
|
||||
{
|
||||
"device_class": SensorDeviceClass.POWER,
|
||||
ATTR_UNIT_OF_MEASUREMENT: UnitOfPower.KILO_WATT,
|
||||
},
|
||||
force_update=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("sensor.integration")
|
||||
assert (
|
||||
round(float(state.state), config["sensor"]["round"]) == 5.0
|
||||
if method == "right"
|
||||
else 3.0
|
||||
)
|
||||
|
||||
|
||||
async def test_restore_state(hass: HomeAssistant) -> None:
|
||||
"""Test integration sensor state is restored correctly."""
|
||||
@@ -416,13 +476,15 @@ async def test_units(hass: HomeAssistant) -> None:
|
||||
assert new_state.state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_device_class(hass: HomeAssistant) -> None:
|
||||
@pytest.mark.parametrize("method", ["trapezoidal", "left", "right"])
|
||||
async def test_device_class(hass: HomeAssistant, method) -> None:
|
||||
"""Test integration sensor units using a power source."""
|
||||
config = {
|
||||
"sensor": {
|
||||
"platform": "integration",
|
||||
"name": "integration",
|
||||
"source": "sensor.power",
|
||||
"method": method,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,13 +527,15 @@ async def test_device_class(hass: HomeAssistant) -> None:
|
||||
assert state.attributes.get("device_class") == SensorDeviceClass.ENERGY
|
||||
|
||||
|
||||
async def test_calc_errors(hass: HomeAssistant) -> None:
|
||||
@pytest.mark.parametrize("method", ["trapezoidal", "left", "right"])
|
||||
async def test_calc_errors(hass: HomeAssistant, method) -> None:
|
||||
"""Test integration sensor units using a power source."""
|
||||
config = {
|
||||
"sensor": {
|
||||
"platform": "integration",
|
||||
"name": "integration",
|
||||
"source": "sensor.power",
|
||||
"method": method,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,6 +543,7 @@ async def test_calc_errors(hass: HomeAssistant) -> None:
|
||||
|
||||
entity_id = config["sensor"]["source"]
|
||||
|
||||
now = dt_util.utcnow()
|
||||
hass.states.async_set(entity_id, None, {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -489,19 +554,25 @@ async def test_calc_errors(hass: HomeAssistant) -> None:
|
||||
assert state.state == STATE_UNKNOWN
|
||||
|
||||
# Moving from an unknown state to a value is a calc error and should
|
||||
# not change the value of the Reimann sensor.
|
||||
hass.states.async_set(entity_id, 0, {"device_class": None})
|
||||
# not change the value of the Reimann sensor, unless the method used is "right".
|
||||
now += timedelta(seconds=3600)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=now):
|
||||
hass.states.async_set(entity_id, 0, {"device_class": None})
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("sensor.integration")
|
||||
assert state is not None
|
||||
assert state.state == STATE_UNKNOWN
|
||||
assert state.state == STATE_UNKNOWN if method != "right" else "0.000"
|
||||
|
||||
# With the source sensor updated successfully, the Reimann sensor
|
||||
# should have a zero (known) value.
|
||||
hass.states.async_set(entity_id, 1, {"device_class": None})
|
||||
now += timedelta(seconds=3600)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=now):
|
||||
hass.states.async_set(entity_id, 1, {"device_class": None})
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("sensor.integration")
|
||||
assert state is not None
|
||||
assert round(float(state.state)) == 0
|
||||
assert round(float(state.state)) == 0 if method != "right" else 1
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
"""Test the Kostal Plenticore Solar Inverter config flow."""
|
||||
import asyncio
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import ANY, AsyncMock, MagicMock, patch
|
||||
|
||||
from pykoplenti import AuthenticationException
|
||||
from pykoplenti import ApiClient, AuthenticationException, SettingsData
|
||||
import pytest
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.kostal_plenticore.const import DOMAIN
|
||||
@@ -11,8 +13,33 @@ from homeassistant.core import HomeAssistant
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_formx(hass: HomeAssistant) -> None:
|
||||
"""Test we get the form."""
|
||||
@pytest.fixture
|
||||
def mock_apiclient() -> ApiClient:
|
||||
"""Return a mocked ApiClient instance."""
|
||||
apiclient = MagicMock(spec=ApiClient)
|
||||
apiclient.__aenter__.return_value = apiclient
|
||||
apiclient.__aexit__ = AsyncMock()
|
||||
|
||||
return apiclient
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_apiclient_class(mock_apiclient) -> Generator[type[ApiClient], None, None]:
|
||||
"""Return a mocked ApiClient class."""
|
||||
with patch(
|
||||
"homeassistant.components.kostal_plenticore.config_flow.ApiClient",
|
||||
autospec=True,
|
||||
) as mock_api_class:
|
||||
mock_api_class.return_value = mock_apiclient
|
||||
yield mock_api_class
|
||||
|
||||
|
||||
async def test_form_g1(
|
||||
hass: HomeAssistant,
|
||||
mock_apiclient_class: type[ApiClient],
|
||||
mock_apiclient: ApiClient,
|
||||
) -> None:
|
||||
"""Test the config flow for G1 models."""
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
@@ -21,25 +48,19 @@ async def test_formx(hass: HomeAssistant) -> None:
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.kostal_plenticore.config_flow.ApiClient"
|
||||
) as mock_api_class, patch(
|
||||
"homeassistant.components.kostal_plenticore.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
# mock of the context manager instance
|
||||
mock_api_ctx = MagicMock()
|
||||
mock_api_ctx.login = AsyncMock()
|
||||
mock_api_ctx.get_setting_values = AsyncMock(
|
||||
mock_apiclient.login = AsyncMock()
|
||||
mock_apiclient.get_settings = AsyncMock(
|
||||
return_value={"scb:network": [SettingsData({"id": "Hostname"})]}
|
||||
)
|
||||
mock_apiclient.get_setting_values = AsyncMock(
|
||||
# G1 model has the entry id "Hostname"
|
||||
return_value={"scb:network": {"Hostname": "scb"}}
|
||||
)
|
||||
|
||||
# mock of the return instance of ApiClient
|
||||
mock_api = MagicMock()
|
||||
mock_api.__aenter__.return_value = mock_api_ctx
|
||||
mock_api.__aexit__ = AsyncMock()
|
||||
|
||||
mock_api_class.return_value = mock_api
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
@@ -49,11 +70,68 @@ async def test_formx(hass: HomeAssistant) -> None:
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mock_api_class.assert_called_once_with(ANY, "1.1.1.1")
|
||||
mock_api.__aenter__.assert_called_once()
|
||||
mock_api.__aexit__.assert_called_once()
|
||||
mock_api_ctx.login.assert_called_once_with("test-password")
|
||||
mock_api_ctx.get_setting_values.assert_called_once()
|
||||
mock_apiclient_class.assert_called_once_with(ANY, "1.1.1.1")
|
||||
mock_apiclient.__aenter__.assert_called_once()
|
||||
mock_apiclient.__aexit__.assert_called_once()
|
||||
mock_apiclient.login.assert_called_once_with("test-password")
|
||||
mock_apiclient.get_settings.assert_called_once()
|
||||
mock_apiclient.get_setting_values.assert_called_once_with(
|
||||
"scb:network", "Hostname"
|
||||
)
|
||||
|
||||
assert result2["type"] == "create_entry"
|
||||
assert result2["title"] == "scb"
|
||||
assert result2["data"] == {
|
||||
"host": "1.1.1.1",
|
||||
"password": "test-password",
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_form_g2(
|
||||
hass: HomeAssistant,
|
||||
mock_apiclient_class: type[ApiClient],
|
||||
mock_apiclient: ApiClient,
|
||||
) -> None:
|
||||
"""Test the config flow for G2 models."""
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == "form"
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.kostal_plenticore.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
# mock of the context manager instance
|
||||
mock_apiclient.login = AsyncMock()
|
||||
mock_apiclient.get_settings = AsyncMock(
|
||||
return_value={"scb:network": [SettingsData({"id": "Network:Hostname"})]}
|
||||
)
|
||||
mock_apiclient.get_setting_values = AsyncMock(
|
||||
# G1 model has the entry id "Hostname"
|
||||
return_value={"scb:network": {"Network:Hostname": "scb"}}
|
||||
)
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
"host": "1.1.1.1",
|
||||
"password": "test-password",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mock_apiclient_class.assert_called_once_with(ANY, "1.1.1.1")
|
||||
mock_apiclient.__aenter__.assert_called_once()
|
||||
mock_apiclient.__aexit__.assert_called_once()
|
||||
mock_apiclient.login.assert_called_once_with("test-password")
|
||||
mock_apiclient.get_settings.assert_called_once()
|
||||
mock_apiclient.get_setting_values.assert_called_once_with(
|
||||
"scb:network", "Network:Hostname"
|
||||
)
|
||||
|
||||
assert result2["type"] == "create_entry"
|
||||
assert result2["title"] == "scb"
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
"""Test Kostal Plenticore helper."""
|
||||
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from pykoplenti import ApiClient, SettingsData
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.kostal_plenticore.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_apiclient() -> Generator[ApiClient, None, None]:
|
||||
"""Return a mocked ApiClient class."""
|
||||
with patch(
|
||||
"homeassistant.components.kostal_plenticore.helper.ApiClient",
|
||||
autospec=True,
|
||||
) as mock_api_class:
|
||||
apiclient = MagicMock(spec=ApiClient)
|
||||
apiclient.__aenter__.return_value = apiclient
|
||||
apiclient.__aexit__ = AsyncMock()
|
||||
mock_api_class.return_value = apiclient
|
||||
yield apiclient
|
||||
|
||||
|
||||
async def test_plenticore_async_setup_g1(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_apiclient: ApiClient,
|
||||
) -> None:
|
||||
"""Tests the async_setup() method of the Plenticore class for G1 models."""
|
||||
mock_apiclient.get_settings = AsyncMock(
|
||||
return_value={"scb:network": [SettingsData({"id": "Hostname"})]}
|
||||
)
|
||||
mock_apiclient.get_setting_values = AsyncMock(
|
||||
# G1 model has the entry id "Hostname"
|
||||
return_value={
|
||||
"devices:local": {
|
||||
"Properties:SerialNo": "12345",
|
||||
"Branding:ProductName1": "PLENTICORE",
|
||||
"Branding:ProductName2": "plus 10",
|
||||
"Properties:VersionIOC": "01.45",
|
||||
"Properties:VersionMC": "01.46",
|
||||
},
|
||||
"scb:network": {"Hostname": "scb"},
|
||||
}
|
||||
)
|
||||
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
plenticore = hass.data[DOMAIN][mock_config_entry.entry_id]
|
||||
|
||||
assert plenticore.device_info == DeviceInfo(
|
||||
configuration_url="http://192.168.1.2",
|
||||
identifiers={(DOMAIN, "12345")},
|
||||
manufacturer="Kostal",
|
||||
model="PLENTICORE plus 10",
|
||||
name="scb",
|
||||
sw_version="IOC: 01.45 MC: 01.46",
|
||||
)
|
||||
|
||||
|
||||
async def test_plenticore_async_setup_g2(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_apiclient: ApiClient,
|
||||
) -> None:
|
||||
"""Tests the async_setup() method of the Plenticore class for G2 models."""
|
||||
mock_apiclient.get_settings = AsyncMock(
|
||||
return_value={"scb:network": [SettingsData({"id": "Network:Hostname"})]}
|
||||
)
|
||||
mock_apiclient.get_setting_values = AsyncMock(
|
||||
# G1 model has the entry id "Hostname"
|
||||
return_value={
|
||||
"devices:local": {
|
||||
"Properties:SerialNo": "12345",
|
||||
"Branding:ProductName1": "PLENTICORE",
|
||||
"Branding:ProductName2": "plus 10",
|
||||
"Properties:VersionIOC": "01.45",
|
||||
"Properties:VersionMC": "01.46",
|
||||
},
|
||||
"scb:network": {"Network:Hostname": "scb"},
|
||||
}
|
||||
)
|
||||
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
plenticore = hass.data[DOMAIN][mock_config_entry.entry_id]
|
||||
|
||||
assert plenticore.device_info == DeviceInfo(
|
||||
configuration_url="http://192.168.1.2",
|
||||
identifiers={(DOMAIN, "12345")},
|
||||
manufacturer="Kostal",
|
||||
model="PLENTICORE plus 10",
|
||||
name="scb",
|
||||
sw_version="IOC: 01.45 MC: 01.46",
|
||||
)
|
||||
@@ -62,7 +62,20 @@ def mock_get_setting_values(mock_plenticore_client: ApiClient) -> list:
|
||||
"id": "Battery:MinHomeComsumption",
|
||||
}
|
||||
),
|
||||
]
|
||||
],
|
||||
"scb:network": [
|
||||
SettingsData(
|
||||
{
|
||||
"min": "1",
|
||||
"default": None,
|
||||
"access": "readwrite",
|
||||
"unit": None,
|
||||
"id": "Hostname",
|
||||
"type": "string",
|
||||
"max": "63",
|
||||
}
|
||||
)
|
||||
],
|
||||
}
|
||||
|
||||
# this values are always retrieved by the integration on startup
|
||||
@@ -112,7 +125,22 @@ async def test_setup_no_entries(
|
||||
) -> None:
|
||||
"""Test that no entries are setup if Plenticore does not provide data."""
|
||||
|
||||
mock_plenticore_client.get_settings.return_value = []
|
||||
# remove all settings except hostname which is used during setup
|
||||
mock_plenticore_client.get_settings.return_value = {
|
||||
"scb:network": [
|
||||
SettingsData(
|
||||
{
|
||||
"min": "1",
|
||||
"default": None,
|
||||
"access": "readwrite",
|
||||
"unit": None,
|
||||
"id": "Hostname",
|
||||
"type": "string",
|
||||
"max": "63",
|
||||
}
|
||||
)
|
||||
],
|
||||
}
|
||||
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ from homeassistant.components.samsungtv.const import DOMAIN, ENTRY_RELOAD_COOLDO
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
@@ -29,7 +28,7 @@ async def setup_samsungtv_entry(hass: HomeAssistant, data: ConfigType) -> Config
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
await async_setup_component(hass, DOMAIN, {})
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
return entry
|
||||
|
||||
@@ -290,15 +290,6 @@ def remoteencws_fixture() -> Mock:
|
||||
yield remoteencws
|
||||
|
||||
|
||||
@pytest.fixture(name="delay")
|
||||
def delay_fixture() -> Mock:
|
||||
"""Patch the delay script function."""
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.media_player.Script.async_run"
|
||||
) as delay:
|
||||
yield delay
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_now() -> datetime:
|
||||
"""Fixture for dtutil.now."""
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
# serializer version: 1
|
||||
# name: test_setup
|
||||
IssueRegistryItemSnapshot({
|
||||
'created': <ANY>,
|
||||
'dismissed_version': None,
|
||||
'domain': 'samsungtv',
|
||||
'is_persistent': False,
|
||||
'issue_id': 'deprecated_yaml',
|
||||
})
|
||||
# ---
|
||||
@@ -1,5 +1,4 @@
|
||||
"""Tests for Samsung TV config flow."""
|
||||
import socket
|
||||
from unittest.mock import ANY, AsyncMock, Mock, call, patch
|
||||
|
||||
import pytest
|
||||
@@ -28,13 +27,9 @@ from homeassistant.components.samsungtv.const import (
|
||||
DEFAULT_MANUFACTURER,
|
||||
DOMAIN,
|
||||
LEGACY_PORT,
|
||||
METHOD_ENCRYPTED_WEBSOCKET,
|
||||
METHOD_LEGACY,
|
||||
METHOD_WEBSOCKET,
|
||||
RESULT_AUTH_MISSING,
|
||||
RESULT_CANNOT_CONNECT,
|
||||
RESULT_NOT_SUPPORTED,
|
||||
RESULT_UNKNOWN_HOST,
|
||||
TIMEOUT_REQUEST,
|
||||
TIMEOUT_WEBSOCKET,
|
||||
)
|
||||
@@ -61,7 +56,6 @@ from homeassistant.data_entry_flow import FlowResultType
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .const import (
|
||||
MOCK_CONFIG_ENCRYPTED_WS,
|
||||
MOCK_ENTRYDATA_ENCRYPTED_WS,
|
||||
MOCK_ENTRYDATA_WS,
|
||||
MOCK_SSDP_DATA_MAIN_TV_AGENT_ST,
|
||||
@@ -907,136 +901,6 @@ async def test_ssdp_already_configured(hass: HomeAssistant) -> None:
|
||||
assert entry.unique_id == "123"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("remote")
|
||||
async def test_import_legacy(hass: HomeAssistant) -> None:
|
||||
"""Test importing from yaml with hostname."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data=MOCK_IMPORT_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == "fake"
|
||||
assert result["data"][CONF_HOST] == "fake_host"
|
||||
assert result["data"][CONF_NAME] == "fake"
|
||||
assert result["data"][CONF_MANUFACTURER] == "Samsung"
|
||||
assert result["result"].unique_id is None
|
||||
|
||||
entries = hass.config_entries.async_entries(DOMAIN)
|
||||
assert len(entries) == 1
|
||||
assert entries[0].data[CONF_METHOD] == METHOD_LEGACY
|
||||
assert entries[0].data[CONF_PORT] == LEGACY_PORT
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("remote", "remotews", "rest_api_failing")
|
||||
async def test_import_legacy_without_name(
|
||||
hass: HomeAssistant, mock_setup_entry: AsyncMock
|
||||
) -> None:
|
||||
"""Test importing from yaml without a name."""
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.bridge.SamsungTVEncryptedWSAsyncRemote.start_listening",
|
||||
side_effect=WebSocketProtocolError("Boom"),
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data=MOCK_IMPORT_DATA_WITHOUT_NAME,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == "fake_host"
|
||||
assert result["data"][CONF_HOST] == "fake_host"
|
||||
assert result["data"][CONF_MANUFACTURER] == "Samsung"
|
||||
assert result["result"].unique_id is None
|
||||
|
||||
mock_setup_entry.assert_called_once()
|
||||
entries = hass.config_entries.async_entries(DOMAIN)
|
||||
assert len(entries) == 1
|
||||
# METHOD / PORT failed during import
|
||||
# They will get checked/set on setup
|
||||
assert CONF_METHOD not in entries[0].data
|
||||
assert CONF_PORT not in entries[0].data
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("remotews", "rest_api")
|
||||
async def test_import_websocket(hass: HomeAssistant) -> None:
|
||||
"""Test importing from yaml with hostname."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data=MOCK_IMPORT_WSDATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == "fake"
|
||||
assert result["data"][CONF_METHOD] == METHOD_WEBSOCKET
|
||||
assert result["data"][CONF_PORT] == 8002
|
||||
assert result["data"][CONF_HOST] == "fake_host"
|
||||
assert result["data"][CONF_NAME] == "fake"
|
||||
assert result["data"][CONF_MANUFACTURER] == "Samsung"
|
||||
assert result["result"].unique_id is None
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("remoteencws")
|
||||
async def test_import_websocket_encrypted(hass: HomeAssistant) -> None:
|
||||
"""Test importing from yaml with hostname."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data=MOCK_CONFIG_ENCRYPTED_WS,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == "fake"
|
||||
assert result["data"][CONF_METHOD] == METHOD_ENCRYPTED_WEBSOCKET
|
||||
assert result["data"][CONF_PORT] == 8000
|
||||
assert result["data"][CONF_HOST] == "fake_host"
|
||||
assert result["data"][CONF_NAME] == "fake"
|
||||
assert result["data"][CONF_MANUFACTURER] == "Samsung"
|
||||
assert result["result"].unique_id is None
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("remotews", "rest_api")
|
||||
async def test_import_websocket_without_port(hass: HomeAssistant) -> None:
|
||||
"""Test importing from yaml with hostname by no port."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data=MOCK_IMPORT_WSDATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == "fake"
|
||||
assert result["data"][CONF_HOST] == "fake_host"
|
||||
assert result["data"][CONF_NAME] == "fake"
|
||||
assert result["data"][CONF_MANUFACTURER] == "Samsung"
|
||||
assert result["result"].unique_id is None
|
||||
|
||||
entries = hass.config_entries.async_entries(DOMAIN)
|
||||
assert len(entries) == 1
|
||||
assert entries[0].data[CONF_METHOD] == METHOD_WEBSOCKET
|
||||
assert entries[0].data[CONF_PORT] == 8002
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("remotews")
|
||||
async def test_import_unknown_host(hass: HomeAssistant) -> None:
|
||||
"""Test importing from yaml with hostname that does not resolve."""
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.config_flow.socket.gethostbyname",
|
||||
side_effect=socket.gaierror,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data=MOCK_IMPORT_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == RESULT_UNKNOWN_HOST
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("remotews", "rest_api_non_ssl_only", "remoteencws_failing")
|
||||
async def test_dhcp_wireless(hass: HomeAssistant) -> None:
|
||||
"""Test starting a flow from dhcp."""
|
||||
@@ -1389,18 +1253,14 @@ async def test_update_missing_mac_unique_id_added_from_dhcp(
|
||||
"""Test missing mac and unique id added."""
|
||||
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_OLD_ENTRY, unique_id=None)
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.async_setup",
|
||||
return_value=True,
|
||||
) as mock_setup:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_DHCP},
|
||||
data=MOCK_DHCP_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_DHCP},
|
||||
data=MOCK_DHCP_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "already_configured"
|
||||
@@ -1415,18 +1275,15 @@ async def test_update_missing_mac_unique_id_added_from_zeroconf(
|
||||
"""Test missing mac and unique id added."""
|
||||
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_OLD_ENTRY, unique_id=None)
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.async_setup",
|
||||
return_value=True,
|
||||
) as mock_setup:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||
data=MOCK_ZEROCONF_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||
data=MOCK_ZEROCONF_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "already_configured"
|
||||
assert entry.data[CONF_MAC] == "aa:bb:zz:ee:rr:oo"
|
||||
@@ -1444,18 +1301,14 @@ async def test_update_missing_model_added_from_ssdp(
|
||||
unique_id=None,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.async_setup",
|
||||
return_value=True,
|
||||
) as mock_setup:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_SSDP_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_SSDP_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "already_configured"
|
||||
@@ -1469,18 +1322,14 @@ async def test_update_missing_mac_unique_id_ssdp_location_added_from_ssdp(
|
||||
"""Test missing mac, ssdp_location, and unique id added via ssdp."""
|
||||
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_OLD_ENTRY, unique_id=None)
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.async_setup",
|
||||
return_value=True,
|
||||
) as mock_setup:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_SSDP_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_SSDP_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "already_configured"
|
||||
@@ -1529,18 +1378,14 @@ async def test_update_missing_mac_unique_id_added_ssdp_location_updated_from_ssd
|
||||
unique_id=None,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.async_setup",
|
||||
return_value=True,
|
||||
) as mock_setup:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_SSDP_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_SSDP_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "already_configured"
|
||||
@@ -1566,18 +1411,14 @@ async def test_update_missing_mac_unique_id_added_ssdp_location_rendering_st_upd
|
||||
unique_id=None,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.async_setup",
|
||||
return_value=True,
|
||||
) as mock_setup:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_SSDP_DATA_RENDERING_CONTROL_ST,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_SSDP_DATA_RENDERING_CONTROL_ST,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "already_configured"
|
||||
@@ -1605,18 +1446,14 @@ async def test_update_missing_mac_unique_id_added_ssdp_location_main_tv_agent_st
|
||||
unique_id=None,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.async_setup",
|
||||
return_value=True,
|
||||
) as mock_setup:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_SSDP_DATA_MAIN_TV_AGENT_ST,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_SSDP_DATA_MAIN_TV_AGENT_ST,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "already_configured"
|
||||
@@ -1644,18 +1481,14 @@ async def test_update_ssdp_location_rendering_st_updated_from_ssdp(
|
||||
unique_id="be9554b9-c9fb-41f4-8920-22da015376a4",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.async_setup",
|
||||
return_value=True,
|
||||
) as mock_setup:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_SSDP_DATA_RENDERING_CONTROL_ST,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_SSDP_DATA_RENDERING_CONTROL_ST,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "already_configured"
|
||||
@@ -1679,18 +1512,14 @@ async def test_update_main_tv_ssdp_location_rendering_st_updated_from_ssdp(
|
||||
unique_id="be9554b9-c9fb-41f4-8920-22da015376a4",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.async_setup",
|
||||
return_value=True,
|
||||
) as mock_setup:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_SSDP_DATA_MAIN_TV_AGENT_ST,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_SSDP_DATA_MAIN_TV_AGENT_ST,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "already_configured"
|
||||
@@ -1714,18 +1543,15 @@ async def test_update_missing_mac_added_unique_id_preserved_from_zeroconf(
|
||||
unique_id="0d1cef00-00dc-1000-9c80-4844f7b172de",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.async_setup",
|
||||
return_value=True,
|
||||
) as mock_setup:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||
data=MOCK_ZEROCONF_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||
data=MOCK_ZEROCONF_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "already_configured"
|
||||
assert entry.data[CONF_MAC] == "aa:bb:zz:ee:rr:oo"
|
||||
@@ -1743,20 +1569,17 @@ async def test_update_legacy_missing_mac_from_dhcp(
|
||||
unique_id="0d1cef00-00dc-1000-9c80-4844f7b172de",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.async_setup",
|
||||
return_value=True,
|
||||
) as mock_setup:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_DHCP},
|
||||
data=dhcp.DhcpServiceInfo(
|
||||
ip=EXISTING_IP, macaddress="aa:bb:cc:dd:ee:ff", hostname="fake_hostname"
|
||||
),
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_DHCP},
|
||||
data=dhcp.DhcpServiceInfo(
|
||||
ip=EXISTING_IP, macaddress="aa:bb:cc:dd:ee:ff", hostname="fake_hostname"
|
||||
),
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "already_configured"
|
||||
assert entry.data[CONF_MAC] == "aa:bb:cc:dd:ee:ff"
|
||||
@@ -1780,10 +1603,7 @@ async def test_update_legacy_missing_mac_from_dhcp_no_unique_id(
|
||||
), patch(
|
||||
"homeassistant.components.samsungtv.bridge.SamsungTVEncryptedWSAsyncRemote.start_listening",
|
||||
side_effect=WebSocketProtocolError("Boom"),
|
||||
), patch(
|
||||
"homeassistant.components.samsungtv.async_setup",
|
||||
return_value=True,
|
||||
) as mock_setup:
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_DHCP},
|
||||
@@ -1792,8 +1612,8 @@ async def test_update_legacy_missing_mac_from_dhcp_no_unique_id(
|
||||
),
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "not_supported"
|
||||
assert entry.data[CONF_MAC] == "aa:bb:cc:dd:ee:ff"
|
||||
@@ -1811,18 +1631,14 @@ async def test_update_ssdp_location_unique_id_added_from_ssdp(
|
||||
unique_id=None,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.async_setup",
|
||||
return_value=True,
|
||||
) as mock_setup:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_SSDP_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_SSDP_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "already_configured"
|
||||
@@ -1843,18 +1659,14 @@ async def test_update_ssdp_location_unique_id_added_from_ssdp_with_rendering_con
|
||||
unique_id=None,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.async_setup",
|
||||
return_value=True,
|
||||
) as mock_setup:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_SSDP_DATA_RENDERING_CONTROL_ST,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_SSDP_DATA_RENDERING_CONTROL_ST,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "already_configured"
|
||||
@@ -2060,18 +1872,14 @@ async def test_update_incorrect_udn_matching_upnp_udn_unique_id_added_from_ssdp(
|
||||
unique_id="0d1cef00-00dc-1000-9c80-4844f7b172de",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.async_setup",
|
||||
return_value=True,
|
||||
) as mock_setup:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_SSDP_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_SSDP_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "already_configured"
|
||||
@@ -2090,18 +1898,14 @@ async def test_update_incorrect_udn_matching_mac_unique_id_added_from_ssdp(
|
||||
unique_id=None,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.async_setup",
|
||||
return_value=True,
|
||||
) as mock_setup:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_SSDP_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_SSDP_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "already_configured"
|
||||
@@ -2121,18 +1925,14 @@ async def test_update_incorrect_udn_matching_mac_from_dhcp(
|
||||
unique_id="0d1cef00-00dc-1000-9c80-4844f7b172de",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.async_setup",
|
||||
return_value=True,
|
||||
) as mock_setup:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_DHCP},
|
||||
data=MOCK_DHCP_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_DHCP},
|
||||
data=MOCK_DHCP_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "already_configured"
|
||||
@@ -2152,18 +1952,14 @@ async def test_no_update_incorrect_udn_not_matching_mac_from_dhcp(
|
||||
unique_id="0d1cef00-00dc-1000-9c80-4844f7b172de",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.async_setup",
|
||||
return_value=True,
|
||||
) as mock_setup:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_DHCP},
|
||||
data=MOCK_DHCP_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup.mock_calls) == 0
|
||||
assert len(mock_setup_entry.mock_calls) == 0
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_DHCP},
|
||||
data=MOCK_DHCP_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup_entry.mock_calls) == 0
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "confirm"
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.media_player import DOMAIN, MediaPlayerEntityFeature
|
||||
from homeassistant.components.samsungtv.const import (
|
||||
CONF_MANUFACTURER,
|
||||
CONF_ON_ACTION,
|
||||
CONF_SESSION_ID,
|
||||
CONF_SSDP_MAIN_TV_AGENT_LOCATION,
|
||||
CONF_SSDP_RENDERING_CONTROL_LOCATION,
|
||||
@@ -32,8 +30,6 @@ from homeassistant.const import (
|
||||
SERVICE_VOLUME_UP,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from . import setup_samsungtv_entry
|
||||
from .const import (
|
||||
@@ -48,43 +44,16 @@ from tests.common import MockConfigEntry
|
||||
|
||||
ENTITY_ID = f"{DOMAIN}.fake_name"
|
||||
MOCK_CONFIG = {
|
||||
SAMSUNGTV_DOMAIN: [
|
||||
{
|
||||
CONF_HOST: "fake_host",
|
||||
CONF_NAME: "fake_name",
|
||||
CONF_ON_ACTION: [{"delay": "00:00:01"}],
|
||||
CONF_METHOD: METHOD_WEBSOCKET,
|
||||
}
|
||||
]
|
||||
}
|
||||
MOCK_CONFIG_WITHOUT_PORT = {
|
||||
SAMSUNGTV_DOMAIN: [
|
||||
{
|
||||
CONF_HOST: "fake_host",
|
||||
CONF_NAME: "fake",
|
||||
CONF_ON_ACTION: [{"delay": "00:00:01"}],
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
REMOTE_CALL = {
|
||||
"name": "HomeAssistant",
|
||||
"description": "HomeAssistant",
|
||||
"id": "ha.component.samsung",
|
||||
"host": MOCK_CONFIG[SAMSUNGTV_DOMAIN][0][CONF_HOST],
|
||||
"method": "legacy",
|
||||
"port": None,
|
||||
"timeout": 1,
|
||||
CONF_HOST: "fake_host",
|
||||
CONF_NAME: "fake_name",
|
||||
CONF_METHOD: METHOD_WEBSOCKET,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("remotews", "remoteencws_failing", "rest_api")
|
||||
async def test_setup(
|
||||
hass: HomeAssistant, issue_registry: ir.IssueRegistry, snapshot: SnapshotAssertion
|
||||
) -> None:
|
||||
async def test_setup(hass: HomeAssistant) -> None:
|
||||
"""Test Samsung TV integration is setup."""
|
||||
await async_setup_component(hass, SAMSUNGTV_DOMAIN, MOCK_CONFIG)
|
||||
await hass.async_block_till_done()
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
|
||||
# test name and turn_on
|
||||
@@ -100,12 +69,8 @@ async def test_setup(
|
||||
DOMAIN, SERVICE_VOLUME_UP, {ATTR_ENTITY_ID: ENTITY_ID}, True
|
||||
)
|
||||
|
||||
# ensure deprecated_yaml issue is raised
|
||||
issue = issue_registry.async_get_issue(SAMSUNGTV_DOMAIN, "deprecated_yaml")
|
||||
assert issue == snapshot
|
||||
|
||||
|
||||
async def test_setup_from_yaml_without_port_device_offline(hass: HomeAssistant) -> None:
|
||||
async def test_setup_without_port_device_offline(hass: HomeAssistant) -> None:
|
||||
"""Test import from yaml when the device is offline."""
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.bridge.Remote", side_effect=OSError
|
||||
@@ -119,8 +84,7 @@ async def test_setup_from_yaml_without_port_device_offline(hass: HomeAssistant)
|
||||
"homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.async_device_info",
|
||||
return_value=None,
|
||||
):
|
||||
await async_setup_component(hass, SAMSUNGTV_DOMAIN, MOCK_CONFIG)
|
||||
await hass.async_block_till_done()
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
|
||||
config_entries_domain = hass.config_entries.async_entries(SAMSUNGTV_DOMAIN)
|
||||
assert len(config_entries_domain) == 1
|
||||
@@ -128,52 +92,22 @@ async def test_setup_from_yaml_without_port_device_offline(hass: HomeAssistant)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("remotews", "remoteencws_failing", "rest_api")
|
||||
async def test_setup_from_yaml_without_port_device_online(hass: HomeAssistant) -> None:
|
||||
async def test_setup_without_port_device_online(hass: HomeAssistant) -> None:
|
||||
"""Test import from yaml when the device is online."""
|
||||
await async_setup_component(hass, SAMSUNGTV_DOMAIN, MOCK_CONFIG)
|
||||
await hass.async_block_till_done()
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
|
||||
config_entries_domain = hass.config_entries.async_entries(SAMSUNGTV_DOMAIN)
|
||||
assert len(config_entries_domain) == 1
|
||||
assert config_entries_domain[0].data[CONF_MAC] == "aa:bb:ww:ii:ff:ii"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("remote")
|
||||
async def test_setup_duplicate_config(
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""Test duplicate setup of platform."""
|
||||
duplicate = {
|
||||
SAMSUNGTV_DOMAIN: [
|
||||
MOCK_CONFIG[SAMSUNGTV_DOMAIN][0],
|
||||
MOCK_CONFIG[SAMSUNGTV_DOMAIN][0],
|
||||
]
|
||||
}
|
||||
await async_setup_component(hass, SAMSUNGTV_DOMAIN, duplicate)
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get(ENTITY_ID) is None
|
||||
assert len(hass.states.async_all("media_player")) == 0
|
||||
assert "duplicate host entries found" in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("remotews", "remoteencws_failing", "rest_api")
|
||||
async def test_setup_duplicate_entries(hass: HomeAssistant) -> None:
|
||||
"""Test duplicate setup of platform."""
|
||||
await async_setup_component(hass, SAMSUNGTV_DOMAIN, MOCK_CONFIG)
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get(ENTITY_ID)
|
||||
assert len(hass.states.async_all("media_player")) == 1
|
||||
await async_setup_component(hass, SAMSUNGTV_DOMAIN, MOCK_CONFIG)
|
||||
assert len(hass.states.async_all("media_player")) == 1
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("remotews", "remoteencws_failing")
|
||||
async def test_setup_h_j_model(
|
||||
hass: HomeAssistant, rest_api: Mock, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""Test Samsung TV integration is setup."""
|
||||
rest_api.rest_device_info.return_value = SAMPLE_DEVICE_INFO_UE48JU6400
|
||||
await async_setup_component(hass, SAMSUNGTV_DOMAIN, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state
|
||||
|
||||
@@ -33,15 +33,14 @@ from homeassistant.components.media_player import (
|
||||
SERVICE_PLAY_MEDIA,
|
||||
SERVICE_SELECT_SOURCE,
|
||||
MediaPlayerDeviceClass,
|
||||
MediaPlayerEntityFeature,
|
||||
MediaType,
|
||||
)
|
||||
from homeassistant.components.samsungtv.const import (
|
||||
CONF_ON_ACTION,
|
||||
CONF_SSDP_RENDERING_CONTROL_LOCATION,
|
||||
DOMAIN as SAMSUNGTV_DOMAIN,
|
||||
ENCRYPTED_WEBSOCKET_PORT,
|
||||
METHOD_ENCRYPTED_WEBSOCKET,
|
||||
METHOD_LEGACY,
|
||||
METHOD_WEBSOCKET,
|
||||
TIMEOUT_WEBSOCKET,
|
||||
)
|
||||
@@ -77,8 +76,6 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.setup import async_setup_component
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from . import async_wait_config_entry_reload, setup_samsungtv_entry
|
||||
@@ -93,25 +90,17 @@ from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
ENTITY_ID = f"{DOMAIN}.fake"
|
||||
MOCK_CONFIG = {
|
||||
SAMSUNGTV_DOMAIN: [
|
||||
{
|
||||
CONF_HOST: "fake_host",
|
||||
CONF_NAME: "fake",
|
||||
CONF_PORT: 55000,
|
||||
CONF_ON_ACTION: [{"delay": "00:00:01"}],
|
||||
}
|
||||
]
|
||||
CONF_HOST: "fake_host",
|
||||
CONF_NAME: "fake",
|
||||
CONF_PORT: 55000,
|
||||
CONF_METHOD: METHOD_LEGACY,
|
||||
}
|
||||
MOCK_CONFIGWS = {
|
||||
SAMSUNGTV_DOMAIN: [
|
||||
{
|
||||
CONF_HOST: "fake_host",
|
||||
CONF_NAME: "fake",
|
||||
CONF_PORT: 8001,
|
||||
CONF_TOKEN: "123456789",
|
||||
CONF_ON_ACTION: [{"delay": "00:00:01"}],
|
||||
}
|
||||
]
|
||||
CONF_HOST: "fake_host",
|
||||
CONF_NAME: "fake",
|
||||
CONF_PORT: 8001,
|
||||
CONF_TOKEN: "123456789",
|
||||
CONF_METHOD: METHOD_WEBSOCKET,
|
||||
}
|
||||
MOCK_CALLS_WS = {
|
||||
CONF_HOST: "fake_host",
|
||||
@@ -143,43 +132,13 @@ MOCK_ENTRY_WS_WITH_MAC = {
|
||||
}
|
||||
|
||||
|
||||
ENTITY_ID_NOTURNON = f"{DOMAIN}.fake_noturnon"
|
||||
MOCK_CONFIG_NOTURNON = {
|
||||
SAMSUNGTV_DOMAIN: [
|
||||
{CONF_HOST: "fake_noturnon", CONF_NAME: "fake_noturnon", CONF_PORT: 55000}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(name="delay")
|
||||
def delay_fixture():
|
||||
"""Patch the delay script function."""
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.media_player.Script.async_run"
|
||||
) as delay:
|
||||
yield delay
|
||||
|
||||
|
||||
async def setup_samsungtv(hass: HomeAssistant, config: ConfigType) -> None:
|
||||
"""Set up mock Samsung TV."""
|
||||
await async_setup_component(hass, SAMSUNGTV_DOMAIN, config)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("remote")
|
||||
async def test_setup_with_turnon(hass: HomeAssistant) -> None:
|
||||
async def test_setup(hass: HomeAssistant) -> None:
|
||||
"""Test setup of platform."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
assert hass.states.get(ENTITY_ID)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("remote")
|
||||
async def test_setup_without_turnon(hass: HomeAssistant) -> None:
|
||||
"""Test setup of platform."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG_NOTURNON)
|
||||
assert hass.states.get(ENTITY_ID_NOTURNON)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("remotews", "rest_api")
|
||||
async def test_setup_websocket(hass: HomeAssistant) -> None:
|
||||
"""Test setup of platform."""
|
||||
@@ -192,7 +151,7 @@ async def test_setup_websocket(hass: HomeAssistant) -> None:
|
||||
remote.token = "123456789"
|
||||
remote_class.return_value = remote
|
||||
|
||||
await setup_samsungtv(hass, MOCK_CONFIGWS)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIGWS)
|
||||
|
||||
assert remote_class.call_count == 1
|
||||
assert remote_class.call_args_list == [call(**MOCK_CALLS_WS)]
|
||||
@@ -229,7 +188,7 @@ async def test_setup_websocket_2(hass: HomeAssistant, mock_now: datetime) -> Non
|
||||
remote.__aexit__ = AsyncMock()
|
||||
remote.token = "987654321"
|
||||
remote_class.return_value = remote
|
||||
assert await async_setup_component(hass, SAMSUNGTV_DOMAIN, {})
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entries[0].data[CONF_MAC] == "aa:bb:ww:ii:ff:ii"
|
||||
@@ -272,7 +231,7 @@ async def test_setup_encrypted_websocket(
|
||||
@pytest.mark.usefixtures("remote")
|
||||
async def test_update_on(hass: HomeAssistant, mock_now: datetime) -> None:
|
||||
"""Testing update tv on."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||
@@ -286,7 +245,7 @@ async def test_update_on(hass: HomeAssistant, mock_now: datetime) -> None:
|
||||
@pytest.mark.usefixtures("remote")
|
||||
async def test_update_off(hass: HomeAssistant, mock_now: datetime) -> None:
|
||||
"""Testing update tv off."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.bridge.Remote",
|
||||
@@ -298,14 +257,14 @@ async def test_update_off(hass: HomeAssistant, mock_now: datetime) -> None:
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state.state == STATE_OFF
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_update_off_ws_no_power_state(
|
||||
hass: HomeAssistant, remotews: Mock, rest_api: Mock, mock_now: datetime
|
||||
) -> None:
|
||||
"""Testing update tv off."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIGWS)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIGWS)
|
||||
# device_info should only get called once, as part of the setup
|
||||
rest_api.rest_device_info.assert_called_once()
|
||||
rest_api.rest_device_info.reset_mock()
|
||||
@@ -336,13 +295,13 @@ async def test_update_off_ws_with_power_state(
|
||||
) as mock_device_info, patch.object(
|
||||
remotews, "start_listening", side_effect=WebSocketException("Boom")
|
||||
) as mock_start_listening:
|
||||
await setup_samsungtv(hass, MOCK_CONFIGWS)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIGWS)
|
||||
|
||||
mock_device_info.assert_called_once()
|
||||
mock_start_listening.assert_called_once()
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state.state == STATE_OFF
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
# First update uses start_listening once, and initialises device_info
|
||||
device_info = deepcopy(SAMPLE_DEVICE_INFO_WIFI)
|
||||
@@ -385,7 +344,7 @@ async def test_update_off_ws_with_power_state(
|
||||
rest_api.rest_device_info.assert_called_once()
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state.state == STATE_OFF
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
remotews.start_listening.assert_not_called()
|
||||
|
||||
@@ -417,7 +376,7 @@ async def test_update_off_encryptedws(
|
||||
@pytest.mark.usefixtures("remote")
|
||||
async def test_update_access_denied(hass: HomeAssistant, mock_now: datetime) -> None:
|
||||
"""Testing update tv access denied exception."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.bridge.Remote",
|
||||
@@ -449,7 +408,7 @@ async def test_update_ws_connection_failure(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Testing update tv connection failure exception."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIGWS)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIGWS)
|
||||
|
||||
with patch.object(
|
||||
remotews,
|
||||
@@ -476,7 +435,7 @@ async def test_update_ws_connection_closed(
|
||||
hass: HomeAssistant, mock_now: datetime, remotews: Mock
|
||||
) -> None:
|
||||
"""Testing update tv connection failure exception."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIGWS)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIGWS)
|
||||
|
||||
with patch.object(
|
||||
remotews, "start_listening", side_effect=ConnectionClosedError(None, None)
|
||||
@@ -495,7 +454,7 @@ async def test_update_ws_unauthorized_error(
|
||||
hass: HomeAssistant, mock_now: datetime, remotews: Mock
|
||||
) -> None:
|
||||
"""Testing update tv unauthorized failure exception."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIGWS)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIGWS)
|
||||
|
||||
with patch.object(
|
||||
remotews, "start_listening", side_effect=UnauthorizedError
|
||||
@@ -519,7 +478,7 @@ async def test_update_unhandled_response(
|
||||
hass: HomeAssistant, mock_now: datetime
|
||||
) -> None:
|
||||
"""Testing update tv unhandled response exception."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.bridge.Remote",
|
||||
@@ -539,7 +498,7 @@ async def test_connection_closed_during_update_can_recover(
|
||||
hass: HomeAssistant, mock_now: datetime
|
||||
) -> None:
|
||||
"""Testing update tv connection closed exception can recover."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.bridge.Remote",
|
||||
@@ -551,7 +510,7 @@ async def test_connection_closed_during_update_can_recover(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state.state == STATE_OFF
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
next_update = mock_now + timedelta(minutes=10)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||
@@ -564,7 +523,7 @@ async def test_connection_closed_during_update_can_recover(
|
||||
|
||||
async def test_send_key(hass: HomeAssistant, remote: Mock) -> None:
|
||||
"""Test for send key."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN, SERVICE_VOLUME_UP, {ATTR_ENTITY_ID: ENTITY_ID}, True
|
||||
)
|
||||
@@ -579,7 +538,7 @@ async def test_send_key(hass: HomeAssistant, remote: Mock) -> None:
|
||||
|
||||
async def test_send_key_broken_pipe(hass: HomeAssistant, remote: Mock) -> None:
|
||||
"""Testing broken pipe Exception."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
remote.control = Mock(side_effect=BrokenPipeError("Boom"))
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN, SERVICE_VOLUME_UP, {ATTR_ENTITY_ID: ENTITY_ID}, True
|
||||
@@ -592,7 +551,7 @@ async def test_send_key_connection_closed_retry_succeed(
|
||||
hass: HomeAssistant, remote: Mock
|
||||
) -> None:
|
||||
"""Test retry on connection closed."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
remote.control = Mock(
|
||||
side_effect=[exceptions.ConnectionClosed("Boom"), DEFAULT_MOCK, DEFAULT_MOCK]
|
||||
)
|
||||
@@ -613,7 +572,7 @@ async def test_send_key_connection_closed_retry_succeed(
|
||||
|
||||
async def test_send_key_unhandled_response(hass: HomeAssistant, remote: Mock) -> None:
|
||||
"""Testing unhandled response exception."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
remote.control = Mock(side_effect=exceptions.UnhandledResponse("Boom"))
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN, SERVICE_VOLUME_UP, {ATTR_ENTITY_ID: ENTITY_ID}, True
|
||||
@@ -625,7 +584,7 @@ async def test_send_key_unhandled_response(hass: HomeAssistant, remote: Mock) ->
|
||||
@pytest.mark.usefixtures("rest_api")
|
||||
async def test_send_key_websocketexception(hass: HomeAssistant, remotews: Mock) -> None:
|
||||
"""Testing unhandled response exception."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIGWS)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIGWS)
|
||||
remotews.send_commands = Mock(side_effect=WebSocketException("Boom"))
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN, SERVICE_VOLUME_UP, {ATTR_ENTITY_ID: ENTITY_ID}, True
|
||||
@@ -651,7 +610,7 @@ async def test_send_key_websocketexception_encrypted(
|
||||
@pytest.mark.usefixtures("rest_api")
|
||||
async def test_send_key_os_error_ws(hass: HomeAssistant, remotews: Mock) -> None:
|
||||
"""Testing unhandled response exception."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIGWS)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIGWS)
|
||||
remotews.send_commands = Mock(side_effect=OSError("Boom"))
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN, SERVICE_VOLUME_UP, {ATTR_ENTITY_ID: ENTITY_ID}, True
|
||||
@@ -676,7 +635,7 @@ async def test_send_key_os_error_ws_encrypted(
|
||||
|
||||
async def test_send_key_os_error(hass: HomeAssistant, remote: Mock) -> None:
|
||||
"""Testing broken pipe Exception."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
remote.control = Mock(side_effect=OSError("Boom"))
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN, SERVICE_VOLUME_UP, {ATTR_ENTITY_ID: ENTITY_ID}, True
|
||||
@@ -688,42 +647,24 @@ async def test_send_key_os_error(hass: HomeAssistant, remote: Mock) -> None:
|
||||
@pytest.mark.usefixtures("remote")
|
||||
async def test_name(hass: HomeAssistant) -> None:
|
||||
"""Test for name property."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state.attributes[ATTR_FRIENDLY_NAME] == "fake"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("remote")
|
||||
async def test_state_with_turnon(hass: HomeAssistant, delay: Mock) -> None:
|
||||
async def test_state(hass: HomeAssistant) -> None:
|
||||
"""Test for state property."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: ENTITY_ID}, True
|
||||
DOMAIN, SERVICE_VOLUME_UP, {ATTR_ENTITY_ID: ENTITY_ID}, True
|
||||
)
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state.state == STATE_ON
|
||||
assert delay.call_count == 1
|
||||
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: ENTITY_ID}, True
|
||||
)
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("remote")
|
||||
async def test_state_without_turnon(hass: HomeAssistant) -> None:
|
||||
"""Test for state property."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG_NOTURNON)
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN, SERVICE_VOLUME_UP, {ATTR_ENTITY_ID: ENTITY_ID_NOTURNON}, True
|
||||
)
|
||||
state = hass.states.get(ENTITY_ID_NOTURNON)
|
||||
assert state.state == STATE_ON
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: ENTITY_ID_NOTURNON}, True
|
||||
)
|
||||
state = hass.states.get(ENTITY_ID_NOTURNON)
|
||||
# Should be STATE_UNAVAILABLE after the timer expires
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
@@ -735,34 +676,23 @@ async def test_state_without_turnon(hass: HomeAssistant) -> None:
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_ID_NOTURNON)
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
# Should be STATE_UNAVAILABLE since there is no way to turn it back on
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("remote")
|
||||
async def test_supported_features_with_turnon(hass: HomeAssistant) -> None:
|
||||
async def test_supported_features(hass: HomeAssistant) -> None:
|
||||
"""Test for supported_features property."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert (
|
||||
state.attributes[ATTR_SUPPORTED_FEATURES]
|
||||
== SUPPORT_SAMSUNGTV | MediaPlayerEntityFeature.TURN_ON
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("remote")
|
||||
async def test_supported_features_without_turnon(hass: HomeAssistant) -> None:
|
||||
"""Test for supported_features property."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG_NOTURNON)
|
||||
state = hass.states.get(ENTITY_ID_NOTURNON)
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == SUPPORT_SAMSUNGTV
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("remote")
|
||||
async def test_device_class(hass: HomeAssistant) -> None:
|
||||
"""Test for device_class property."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state.attributes[ATTR_DEVICE_CLASS] is MediaPlayerDeviceClass.TV.value
|
||||
|
||||
@@ -777,7 +707,7 @@ async def test_turn_off_websocket(
|
||||
"homeassistant.components.samsungtv.bridge.Remote",
|
||||
side_effect=[OSError("Boom"), DEFAULT_MOCK],
|
||||
):
|
||||
await setup_samsungtv(hass, MOCK_CONFIGWS)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIGWS)
|
||||
|
||||
remotews.send_commands.reset_mock()
|
||||
|
||||
@@ -816,7 +746,7 @@ async def test_turn_off_websocket_frame(
|
||||
"homeassistant.components.samsungtv.bridge.Remote",
|
||||
side_effect=[OSError("Boom"), DEFAULT_MOCK],
|
||||
):
|
||||
await setup_samsungtv(hass, MOCK_CONFIGWS)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIGWS)
|
||||
|
||||
remotews.send_commands.reset_mock()
|
||||
|
||||
@@ -903,9 +833,9 @@ async def test_turn_off_encrypted_websocket_key_type(
|
||||
|
||||
async def test_turn_off_legacy(hass: HomeAssistant, remote: Mock) -> None:
|
||||
"""Test for turn_off."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG_NOTURNON)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: ENTITY_ID_NOTURNON}, True
|
||||
DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: ENTITY_ID}, True
|
||||
)
|
||||
# key called
|
||||
assert remote.control.call_count == 1
|
||||
@@ -917,7 +847,7 @@ async def test_turn_off_os_error(
|
||||
) -> None:
|
||||
"""Test for turn_off with OSError."""
|
||||
caplog.set_level(logging.DEBUG)
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
remote.close = Mock(side_effect=OSError("BOOM"))
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: ENTITY_ID}, True
|
||||
@@ -931,7 +861,7 @@ async def test_turn_off_ws_os_error(
|
||||
) -> None:
|
||||
"""Test for turn_off with OSError."""
|
||||
caplog.set_level(logging.DEBUG)
|
||||
await setup_samsungtv(hass, MOCK_CONFIGWS)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIGWS)
|
||||
remotews.close = Mock(side_effect=OSError("BOOM"))
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: ENTITY_ID}, True
|
||||
@@ -955,7 +885,7 @@ async def test_turn_off_encryptedws_os_error(
|
||||
|
||||
async def test_volume_up(hass: HomeAssistant, remote: Mock) -> None:
|
||||
"""Test for volume_up."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN, SERVICE_VOLUME_UP, {ATTR_ENTITY_ID: ENTITY_ID}, True
|
||||
)
|
||||
@@ -968,7 +898,7 @@ async def test_volume_up(hass: HomeAssistant, remote: Mock) -> None:
|
||||
|
||||
async def test_volume_down(hass: HomeAssistant, remote: Mock) -> None:
|
||||
"""Test for volume_down."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN, SERVICE_VOLUME_DOWN, {ATTR_ENTITY_ID: ENTITY_ID}, True
|
||||
)
|
||||
@@ -981,7 +911,7 @@ async def test_volume_down(hass: HomeAssistant, remote: Mock) -> None:
|
||||
|
||||
async def test_mute_volume(hass: HomeAssistant, remote: Mock) -> None:
|
||||
"""Test for mute_volume."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_VOLUME_MUTE,
|
||||
@@ -997,7 +927,7 @@ async def test_mute_volume(hass: HomeAssistant, remote: Mock) -> None:
|
||||
|
||||
async def test_media_play(hass: HomeAssistant, remote: Mock) -> None:
|
||||
"""Test for media_play."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN, SERVICE_MEDIA_PLAY, {ATTR_ENTITY_ID: ENTITY_ID}, True
|
||||
)
|
||||
@@ -1019,7 +949,7 @@ async def test_media_play(hass: HomeAssistant, remote: Mock) -> None:
|
||||
|
||||
async def test_media_pause(hass: HomeAssistant, remote: Mock) -> None:
|
||||
"""Test for media_pause."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN, SERVICE_MEDIA_PAUSE, {ATTR_ENTITY_ID: ENTITY_ID}, True
|
||||
)
|
||||
@@ -1041,7 +971,7 @@ async def test_media_pause(hass: HomeAssistant, remote: Mock) -> None:
|
||||
|
||||
async def test_media_next_track(hass: HomeAssistant, remote: Mock) -> None:
|
||||
"""Test for media_next_track."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN, SERVICE_MEDIA_NEXT_TRACK, {ATTR_ENTITY_ID: ENTITY_ID}, True
|
||||
)
|
||||
@@ -1054,7 +984,7 @@ async def test_media_next_track(hass: HomeAssistant, remote: Mock) -> None:
|
||||
|
||||
async def test_media_previous_track(hass: HomeAssistant, remote: Mock) -> None:
|
||||
"""Test for media_previous_track."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK, {ATTR_ENTITY_ID: ENTITY_ID}, True
|
||||
)
|
||||
@@ -1065,16 +995,6 @@ async def test_media_previous_track(hass: HomeAssistant, remote: Mock) -> None:
|
||||
assert remote.close.call_args_list == [call()]
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("remote")
|
||||
async def test_turn_on_with_turnon(hass: HomeAssistant, delay: Mock) -> None:
|
||||
"""Test turn on."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: ENTITY_ID}, True
|
||||
)
|
||||
assert delay.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("remotews", "rest_api")
|
||||
async def test_turn_on_wol(hass: HomeAssistant) -> None:
|
||||
"""Test turn on."""
|
||||
@@ -1084,7 +1004,7 @@ async def test_turn_on_wol(hass: HomeAssistant) -> None:
|
||||
unique_id="any",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
assert await async_setup_component(hass, SAMSUNGTV_DOMAIN, {})
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.media_player.send_magic_packet"
|
||||
@@ -1098,10 +1018,10 @@ async def test_turn_on_wol(hass: HomeAssistant) -> None:
|
||||
|
||||
async def test_turn_on_without_turnon(hass: HomeAssistant, remote: Mock) -> None:
|
||||
"""Test turn on."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG_NOTURNON)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
with pytest.raises(HomeAssistantError):
|
||||
await hass.services.async_call(
|
||||
DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: ENTITY_ID_NOTURNON}, True
|
||||
DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: ENTITY_ID}, True
|
||||
)
|
||||
# nothing called as not supported feature
|
||||
assert remote.control.call_count == 0
|
||||
@@ -1109,7 +1029,7 @@ async def test_turn_on_without_turnon(hass: HomeAssistant, remote: Mock) -> None
|
||||
|
||||
async def test_play_media(hass: HomeAssistant, remote: Mock) -> None:
|
||||
"""Test for play_media."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
with patch("homeassistant.components.samsungtv.bridge.asyncio.sleep") as sleep:
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
@@ -1138,7 +1058,7 @@ async def test_play_media_invalid_type(hass: HomeAssistant) -> None:
|
||||
"""Test for play_media with invalid media type."""
|
||||
with patch("homeassistant.components.samsungtv.bridge.Remote") as remote:
|
||||
url = "https://example.com"
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
remote.reset_mock()
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
@@ -1160,7 +1080,7 @@ async def test_play_media_channel_as_string(hass: HomeAssistant) -> None:
|
||||
"""Test for play_media with invalid channel as string."""
|
||||
with patch("homeassistant.components.samsungtv.bridge.Remote") as remote:
|
||||
url = "https://example.com"
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
remote.reset_mock()
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
@@ -1181,7 +1101,7 @@ async def test_play_media_channel_as_string(hass: HomeAssistant) -> None:
|
||||
async def test_play_media_channel_as_non_positive(hass: HomeAssistant) -> None:
|
||||
"""Test for play_media with invalid channel as non positive integer."""
|
||||
with patch("homeassistant.components.samsungtv.bridge.Remote") as remote:
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
remote.reset_mock()
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
@@ -1201,7 +1121,7 @@ async def test_play_media_channel_as_non_positive(hass: HomeAssistant) -> None:
|
||||
|
||||
async def test_select_source(hass: HomeAssistant, remote: Mock) -> None:
|
||||
"""Test for select_source."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SELECT_SOURCE,
|
||||
@@ -1218,7 +1138,7 @@ async def test_select_source(hass: HomeAssistant, remote: Mock) -> None:
|
||||
async def test_select_source_invalid_source(hass: HomeAssistant) -> None:
|
||||
"""Test for select_source with invalid source."""
|
||||
with patch("homeassistant.components.samsungtv.bridge.Remote") as remote:
|
||||
await setup_samsungtv(hass, MOCK_CONFIG)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||
remote.reset_mock()
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
@@ -1235,7 +1155,7 @@ async def test_select_source_invalid_source(hass: HomeAssistant) -> None:
|
||||
@pytest.mark.usefixtures("rest_api")
|
||||
async def test_play_media_app(hass: HomeAssistant, remotews: Mock) -> None:
|
||||
"""Test for play_media."""
|
||||
await setup_samsungtv(hass, MOCK_CONFIGWS)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIGWS)
|
||||
remotews.send_commands.reset_mock()
|
||||
|
||||
assert await hass.services.async_call(
|
||||
@@ -1259,7 +1179,7 @@ async def test_play_media_app(hass: HomeAssistant, remotews: Mock) -> None:
|
||||
async def test_select_source_app(hass: HomeAssistant, remotews: Mock) -> None:
|
||||
"""Test for select_source."""
|
||||
remotews.app_list_data = SAMPLE_EVENT_ED_INSTALLED_APP
|
||||
await setup_samsungtv(hass, MOCK_CONFIGWS)
|
||||
await setup_samsungtv_entry(hass, MOCK_CONFIGWS)
|
||||
remotews.send_commands.reset_mock()
|
||||
|
||||
assert await hass.services.async_call(
|
||||
|
||||
@@ -858,7 +858,7 @@ async def test_options_flow_disabled_gen_1(
|
||||
await ws_client.send_json(
|
||||
{
|
||||
"id": 5,
|
||||
"type": "config_entries/get",
|
||||
"type": "config_entries/get_matching",
|
||||
"domain": "shelly",
|
||||
}
|
||||
)
|
||||
@@ -879,7 +879,7 @@ async def test_options_flow_enabled_gen_2(
|
||||
await ws_client.send_json(
|
||||
{
|
||||
"id": 5,
|
||||
"type": "config_entries/get",
|
||||
"type": "config_entries/get_matching",
|
||||
"domain": "shelly",
|
||||
}
|
||||
)
|
||||
@@ -900,7 +900,7 @@ async def test_options_flow_disabled_sleepy_gen_2(
|
||||
await ws_client.send_json(
|
||||
{
|
||||
"id": 5,
|
||||
"type": "config_entries/get",
|
||||
"type": "config_entries/get_matching",
|
||||
"domain": "shelly",
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user