Merge pull request #33932 from home-assistant/rc

0.108.2
This commit is contained in:
Paulus Schoutsen 2020-04-09 19:57:08 -07:00 committed by GitHub
commit 0727c7b9e8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 715 additions and 335 deletions

View file

@ -298,8 +298,6 @@ class BayesianBinarySensor(BinarySensorDevice):
@property
def device_state_attributes(self):
"""Return the state attributes of the sensor."""
print(self.current_observations)
print(self.observations_by_entity)
return {
ATTR_OBSERVATIONS: list(self.current_observations.values()),
ATTR_OCCURRED_OBSERVATION_ENTITIES: list(

View file

@ -2,7 +2,7 @@
"domain": "frontend",
"name": "Home Assistant Frontend",
"documentation": "https://www.home-assistant.io/integrations/frontend",
"requirements": ["home-assistant-frontend==20200407.1"],
"requirements": ["home-assistant-frontend==20200407.2"],
"dependencies": [
"api",
"auth",

View file

@ -116,7 +116,12 @@ class IPPMarkerSensor(IPPSensor):
@property
def state(self) -> Union[None, str, int, float]:
"""Return the state of the sensor."""
return self.coordinator.data.markers[self.marker_index].level
level = self.coordinator.data.markers[self.marker_index].level
if level >= 0:
return level
return None
class IPPPrinterSensor(IPPSensor):

View file

@ -283,11 +283,6 @@ class KonnectedFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
# build config info and wait for user confirmation
self.data[CONF_HOST] = user_input[CONF_HOST]
self.data[CONF_PORT] = user_input[CONF_PORT]
self.data[CONF_ACCESS_TOKEN] = self.hass.data.get(DOMAIN, {}).get(
CONF_ACCESS_TOKEN
) or "".join(
random.choices(f"{string.ascii_uppercase}{string.digits}", k=20)
)
# brief delay to allow processing of recent status req
await asyncio.sleep(0.1)
@ -343,8 +338,12 @@ class KonnectedFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
},
)
# Attach default options and create entry
# Create access token, attach default options and create entry
self.data[CONF_DEFAULT_OPTIONS] = self.options
self.data[CONF_ACCESS_TOKEN] = self.hass.data.get(DOMAIN, {}).get(
CONF_ACCESS_TOKEN
) or "".join(random.choices(f"{string.ascii_uppercase}{string.digits}", k=20))
return self.async_create_entry(
title=KONN_PANEL_MODEL_NAMES[self.data[CONF_MODEL]], data=self.data,
)

View file

@ -21,7 +21,6 @@ from homeassistant.const import (
CONF_PORT,
CONF_TIMEOUT,
CONF_TYPE,
EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP,
)
import homeassistant.helpers.config_validation as cv
@ -36,7 +35,7 @@ from .const import (
CONF_PARITY,
CONF_STOPBITS,
DEFAULT_HUB,
MODBUS_DOMAIN,
MODBUS_DOMAIN as DOMAIN,
SERVICE_WRITE_COIL,
SERVICE_WRITE_REGISTER,
)
@ -69,7 +68,7 @@ ETHERNET_SCHEMA = BASE_SCHEMA.extend(
)
CONFIG_SCHEMA = vol.Schema(
{MODBUS_DOMAIN: vol.All(cv.ensure_list, [vol.Any(SERIAL_SCHEMA, ETHERNET_SCHEMA)])},
{DOMAIN: vol.All(cv.ensure_list, [vol.Any(SERIAL_SCHEMA, ETHERNET_SCHEMA)])},
extra=vol.ALLOW_EXTRA,
)
@ -96,10 +95,9 @@ SERVICE_WRITE_COIL_SCHEMA = vol.Schema(
async def async_setup(hass, config):
"""Set up Modbus component."""
hass.data[MODBUS_DOMAIN] = hub_collect = {}
hass.data[DOMAIN] = hub_collect = {}
_LOGGER.debug("registering hubs")
for client_config in config[MODBUS_DOMAIN]:
for client_config in config[DOMAIN]:
hub_collect[client_config[CONF_NAME]] = ModbusHub(client_config, hass.loop)
def stop_modbus(event):
@ -107,28 +105,13 @@ async def async_setup(hass, config):
for client in hub_collect.values():
del client
def start_modbus(event):
def start_modbus():
"""Start Modbus service."""
for client in hub_collect.values():
_LOGGER.debug("setup hub %s", client.name)
client.setup()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_modbus)
# Register services for modbus
hass.services.async_register(
MODBUS_DOMAIN,
SERVICE_WRITE_REGISTER,
write_register,
schema=SERVICE_WRITE_REGISTER_SCHEMA,
)
hass.services.async_register(
MODBUS_DOMAIN,
SERVICE_WRITE_COIL,
write_coil,
schema=SERVICE_WRITE_COIL_SCHEMA,
)
async def write_register(service):
"""Write Modbus registers."""
unit = int(float(service.data[ATTR_UNIT]))
@ -152,8 +135,19 @@ async def async_setup(hass, config):
client_name = service.data[ATTR_HUB]
await hub_collect[client_name].write_coil(unit, address, state)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, start_modbus)
# do not wait for EVENT_HOMEASSISTANT_START, activate pymodbus now
await hass.async_add_executor_job(start_modbus)
# Register services for modbus
hass.services.async_register(
DOMAIN,
SERVICE_WRITE_REGISTER,
write_register,
schema=SERVICE_WRITE_REGISTER_SCHEMA,
)
hass.services.async_register(
DOMAIN, SERVICE_WRITE_COIL, write_coil, schema=SERVICE_WRITE_COIL_SCHEMA,
)
return True
@ -162,7 +156,6 @@ class ModbusHub:
def __init__(self, client_config, main_loop):
"""Initialize the Modbus hub."""
_LOGGER.debug("Preparing setup: %s", client_config)
# generic configuration
self._loop = main_loop
@ -172,7 +165,7 @@ class ModbusHub:
self._config_type = client_config[CONF_TYPE]
self._config_port = client_config[CONF_PORT]
self._config_timeout = client_config[CONF_TIMEOUT]
self._config_delay = client_config[CONF_DELAY]
self._config_delay = 0
if self._config_type == "serial":
# serial configuration
@ -184,6 +177,7 @@ class ModbusHub:
else:
# network configuration
self._config_host = client_config[CONF_HOST]
self._config_delay = client_config[CONF_DELAY]
@property
def name(self):
@ -201,7 +195,6 @@ class ModbusHub:
# Client* do deliver loop, client as result but
# pylint does not accept that fact
_LOGGER.debug("doing setup")
if self._config_type == "serial":
_, self._client = ClientSerial(
schedulers.ASYNC_IO,
@ -211,7 +204,6 @@ class ModbusHub:
stopbits=self._config_stopbits,
bytesize=self._config_bytesize,
parity=self._config_parity,
timeout=self._config_timeout,
loop=self._loop,
)
elif self._config_type == "rtuovertcp":

View file

@ -54,7 +54,7 @@ PLATFORM_SCHEMA = vol.All(
)
async def async_setup_platform(hass, config, add_entities, discovery_info=None):
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the Modbus binary sensors."""
sensors = []
for entry in config[CONF_INPUTS]:
@ -70,7 +70,7 @@ async def async_setup_platform(hass, config, add_entities, discovery_info=None):
)
)
add_entities(sensors)
async_add_entities(sensors)
class ModbusBinarySensor(BinarySensorDevice):

View file

@ -72,7 +72,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
)
async def async_setup_platform(hass, config, add_entities, discovery_info=None):
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the Modbus Thermostat Platform."""
name = config[CONF_NAME]
modbus_slave = config[CONF_SLAVE]
@ -91,7 +91,7 @@ async def async_setup_platform(hass, config, add_entities, discovery_info=None):
hub_name = config[CONF_HUB]
hub = hass.data[MODBUS_DOMAIN][hub_name]
add_entities(
async_add_entities(
[
ModbusThermostat(
hub,

View file

@ -89,7 +89,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
)
async def async_setup_platform(hass, config, add_entities, discovery_info=None):
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the Modbus sensors."""
sensors = []
data_types = {DATA_TYPE_INT: {1: "h", 2: "i", 4: "q"}}
@ -148,7 +148,7 @@ async def async_setup_platform(hass, config, add_entities, discovery_info=None):
if not sensors:
return False
add_entities(sensors)
async_add_entities(sensors)
class ModbusRegisterSensor(RestoreEntity):

View file

@ -76,7 +76,7 @@ PLATFORM_SCHEMA = vol.All(
)
async def async_setup_platform(hass, config, add_entities, discovery_info=None):
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Read configuration and create Modbus devices."""
switches = []
if CONF_COILS in config:
@ -109,7 +109,7 @@ async def async_setup_platform(hass, config, add_entities, discovery_info=None):
)
)
add_entities(switches)
async_add_entities(switches)
class ModbusCoilSwitch(ToggleEntity, RestoreEntity):

View file

@ -1,6 +1,8 @@
"""Support for interfacing with Monoprice 6 zone home audio controller."""
import logging
from serial import SerialException
from homeassistant import core
from homeassistant.components.media_player import MediaPlayerDevice
from homeassistant.components.media_player.const import (
@ -18,6 +20,8 @@ from .const import CONF_SOURCES, DOMAIN, SERVICE_RESTORE, SERVICE_SNAPSHOT
_LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 1
SUPPORT_MONOPRICE = (
SUPPORT_VOLUME_MUTE
| SUPPORT_VOLUME_SET
@ -127,9 +131,15 @@ class MonopriceZone(MediaPlayerDevice):
def update(self):
"""Retrieve latest state."""
state = self._monoprice.zone_status(self._zone_id)
try:
state = self._monoprice.zone_status(self._zone_id)
except SerialException:
_LOGGER.warning("Could not update zone %d", self._zone_id)
return
if not state:
return False
return
self._state = STATE_ON if state.power else STATE_OFF
self._volume = state.volume
self._mute = state.mute
@ -138,7 +148,6 @@ class MonopriceZone(MediaPlayerDevice):
self._source = self._source_id_name[idx]
else:
self._source = None
return True
@property
def entity_registry_enabled_default(self):

View file

@ -63,7 +63,7 @@ SENSORS = (
"nextcloud_storage_num_files",
"nextcloud_storage_num_storages",
"nextcloud_storage_num_storages_local",
"nextcloud_storage_num_storage_home",
"nextcloud_storage_num_storages_home",
"nextcloud_storage_num_storages_other",
"nextcloud_shares_num_shares",
"nextcloud_shares_num_shares_user",
@ -83,9 +83,9 @@ SENSORS = (
"nextcloud_database_type",
"nextcloud_database_version",
"nextcloud_database_version",
"nextcloud_activeusers_last5minutes",
"nextcloud_activeusers_last1hour",
"nextcloud_activeusers_last24hours",
"nextcloud_activeUsers_last5minutes",
"nextcloud_activeUsers_last1hour",
"nextcloud_activeUsers_last24hours",
)

View file

@ -516,7 +516,8 @@ class ONVIFHassCamera(Camera):
"""Read image from a URL."""
try:
response = requests.get(self._snapshot, timeout=5, auth=auth)
return response.content
if response.status_code < 300:
return response.content
except requests.exceptions.RequestException as error:
_LOGGER.error(
"Fetch snapshot image failed from %s, falling back to FFmpeg; %s",
@ -524,6 +525,8 @@ class ONVIFHassCamera(Camera):
error,
)
return None
image = await self.hass.async_add_job(fetch)
if image is None:

View file

@ -1,5 +1,4 @@
"""Shared class to maintain Plex server instances."""
from functools import partial, wraps
import logging
import ssl
from urllib.parse import urlparse
@ -13,8 +12,8 @@ import requests.exceptions
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
from homeassistant.const import CONF_TOKEN, CONF_URL, CONF_VERIFY_SSL
from homeassistant.core import callback
from homeassistant.helpers.debounce import Debouncer
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.event import async_call_later
from .const import (
CONF_CLIENT_IDENTIFIER,
@ -43,31 +42,6 @@ plexapi.X_PLEX_PRODUCT = X_PLEX_PRODUCT
plexapi.X_PLEX_VERSION = X_PLEX_VERSION
def debounce(func):
"""Decorate function to debounce callbacks from Plex websocket."""
unsub = None
async def call_later_listener(self, _):
"""Handle call_later callback."""
nonlocal unsub
unsub = None
await func(self)
@wraps(func)
async def wrapper(self):
"""Schedule async callback."""
nonlocal unsub
if unsub:
_LOGGER.debug("Throttling update of %s", self.friendly_name)
unsub() # pylint: disable=not-callable
unsub = async_call_later(
self.hass, DEBOUNCE_TIMEOUT, partial(call_later_listener, self),
)
return wrapper
class PlexServer:
"""Manages a single Plex server connection."""
@ -87,6 +61,13 @@ class PlexServer:
self._accounts = []
self._owner_username = None
self._version = None
self.async_update_platforms = Debouncer(
hass,
_LOGGER,
cooldown=DEBOUNCE_TIMEOUT,
immediate=True,
function=self._async_update_platforms,
).async_call
# Header conditionally added as it is not available in config entry v1
if CONF_CLIENT_IDENTIFIER in server_config:
@ -192,8 +173,7 @@ class PlexServer:
"""Fetch all data from the Plex server in a single method."""
return (self._plex_server.clients(), self._plex_server.sessions())
@debounce
async def async_update_platforms(self):
async def _async_update_platforms(self):
"""Update the platform entities."""
_LOGGER.debug("Updating devices")

View file

@ -3,6 +3,5 @@
"name": "Switcher",
"documentation": "https://www.home-assistant.io/integrations/switcher_kis/",
"codeowners": ["@tomerfi"],
"requirements": ["aioswitcher==2019.4.26"],
"dependencies": []
"requirements": ["aioswitcher==1.1.0"]
}

View file

@ -40,6 +40,7 @@ ATTR_MONTHLY_ENERGY_KWH = "monthly_energy_kwh"
LIGHT_STATE_DFT_ON = "dft_on_state"
LIGHT_STATE_ON_OFF = "on_off"
LIGHT_STATE_RELAY_STATE = "relay_state"
LIGHT_STATE_BRIGHTNESS = "brightness"
LIGHT_STATE_COLOR_TEMP = "color_temp"
LIGHT_STATE_HUE = "hue"
@ -128,6 +129,7 @@ class LightFeatures(NamedTuple):
supported_features: int
min_mireds: float
max_mireds: float
has_emeter: bool
class TPLinkSmartBulb(Light):
@ -285,8 +287,9 @@ class TPLinkSmartBulb(Light):
model = sysinfo[LIGHT_SYSINFO_MODEL]
min_mireds = None
max_mireds = None
has_emeter = self.smartbulb.has_emeter
if sysinfo.get(LIGHT_SYSINFO_IS_DIMMABLE):
if sysinfo.get(LIGHT_SYSINFO_IS_DIMMABLE) or LIGHT_STATE_BRIGHTNESS in sysinfo:
supported_features += SUPPORT_BRIGHTNESS
if sysinfo.get(LIGHT_SYSINFO_IS_VARIABLE_COLOR_TEMP):
supported_features += SUPPORT_COLOR_TEMP
@ -306,6 +309,7 @@ class TPLinkSmartBulb(Light):
supported_features=supported_features,
min_mireds=min_mireds,
max_mireds=max_mireds,
has_emeter=has_emeter,
)
def _get_light_state_retry(self) -> LightState:
@ -357,10 +361,10 @@ class TPLinkSmartBulb(Light):
def _get_light_state(self) -> LightState:
"""Get the light state."""
self._update_emeter()
return self._light_state_from_params(self.smartbulb.get_light_state())
return self._light_state_from_params(self._get_device_state())
def _update_emeter(self):
if not self.smartbulb.has_emeter:
if not self._light_features.has_emeter:
return
now = dt_util.utcnow()
@ -439,7 +443,44 @@ class TPLinkSmartBulb(Light):
if not diff:
return
return self.smartbulb.set_light_state(diff)
return self._set_device_state(diff)
def _get_device_state(self):
"""State of the bulb or smart dimmer switch."""
if isinstance(self.smartbulb, SmartBulb):
return self.smartbulb.get_light_state()
sysinfo = self.smartbulb.sys_info
# Its not really a bulb, its a dimmable SmartPlug (aka Wall Switch)
return {
LIGHT_STATE_ON_OFF: sysinfo[LIGHT_STATE_RELAY_STATE],
LIGHT_STATE_BRIGHTNESS: sysinfo.get(LIGHT_STATE_BRIGHTNESS, 0),
LIGHT_STATE_COLOR_TEMP: 0,
LIGHT_STATE_HUE: 0,
LIGHT_STATE_SATURATION: 0,
}
def _set_device_state(self, state):
"""Set state of the bulb or smart dimmer switch."""
if isinstance(self.smartbulb, SmartBulb):
return self.smartbulb.set_light_state(state)
# Its not really a bulb, its a dimmable SmartPlug (aka Wall Switch)
if LIGHT_STATE_BRIGHTNESS in state:
# Brightness of 0 is accepted by the
# device but the underlying library rejects it
# so we turn off instead.
if state[LIGHT_STATE_BRIGHTNESS]:
self.smartbulb.brightness = state[LIGHT_STATE_BRIGHTNESS]
else:
self.smartbulb.state = self.smartbulb.SWITCH_STATE_OFF
elif LIGHT_STATE_ON_OFF in state:
if state[LIGHT_STATE_ON_OFF]:
self.smartbulb.state = self.smartbulb.SWITCH_STATE_ON
else:
self.smartbulb.state = self.smartbulb.SWITCH_STATE_OFF
return self._get_device_state()
def _light_state_diff(old_light_state: LightState, new_light_state: LightState):

View file

@ -2,8 +2,7 @@
"domain": "vizio",
"name": "VIZIO SmartCast",
"documentation": "https://www.home-assistant.io/integrations/vizio",
"requirements": ["pyvizio==0.1.44"],
"dependencies": [],
"requirements": ["pyvizio==0.1.46"],
"codeowners": ["@raman325"],
"config_flow": true,
"zeroconf": ["_viziocast._tcp.local."],

View file

@ -1,7 +1,7 @@
"""Constants used by Home Assistant components."""
MAJOR_VERSION = 0
MINOR_VERSION = 108
PATCH_VERSION = "1"
PATCH_VERSION = "2"
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__ = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER = (3, 7, 0)

View file

@ -12,7 +12,7 @@ cryptography==2.8
defusedxml==0.6.0
distro==1.4.0
hass-nabucasa==0.32.2
home-assistant-frontend==20200407.1
home-assistant-frontend==20200407.2
importlib-metadata==1.5.0
jinja2>=2.11.1
netdisco==2.6.0

View file

@ -208,7 +208,7 @@ aiopvpc==1.0.2
aiopylgtv==0.3.3
# homeassistant.components.switcher_kis
aioswitcher==2019.4.26
aioswitcher==1.1.0
# homeassistant.components.unifi
aiounifi==15
@ -704,7 +704,7 @@ hole==0.5.1
holidays==0.10.1
# homeassistant.components.frontend
home-assistant-frontend==20200407.1
home-assistant-frontend==20200407.2
# homeassistant.components.zwave
homeassistant-pyozw==0.1.10
@ -1738,7 +1738,7 @@ pyversasense==0.0.6
pyvesync==1.1.0
# homeassistant.components.vizio
pyvizio==0.1.44
pyvizio==0.1.46
# homeassistant.components.velux
pyvlx==0.2.12

View file

@ -91,7 +91,7 @@ aiopvpc==1.0.2
aiopylgtv==0.3.3
# homeassistant.components.switcher_kis
aioswitcher==2019.4.26
aioswitcher==1.1.0
# homeassistant.components.unifi
aiounifi==15
@ -282,7 +282,7 @@ hole==0.5.1
holidays==0.10.1
# homeassistant.components.frontend
home-assistant-frontend==20200407.1
home-assistant-frontend==20200407.2
# homeassistant.components.zwave
homeassistant-pyozw==0.1.10
@ -647,7 +647,7 @@ pyvera==0.3.7
pyvesync==1.1.0
# homeassistant.components.vizio
pyvizio==0.1.44
pyvizio==0.1.46
# homeassistant.components.html5
pywebpush==1.9.2

View file

@ -362,10 +362,11 @@ async def test_ssdp_host_update(hass, mock_panel):
)
assert result["type"] == "abort"
# confirm the host value was updated
# confirm the host value was updated, access_token was not
entry = hass.config_entries.async_entries(config_flow.DOMAIN)[0]
assert entry.data["host"] == "1.1.1.1"
assert entry.data["port"] == 1234
assert entry.data["access_token"] == "11223344556677889900"
async def test_import_existing_config(hass, mock_panel):
@ -494,6 +495,7 @@ async def test_import_existing_config_entry(hass, mock_panel):
data={
"host": "0.0.0.0",
"port": 1111,
"access_token": "ORIGINALTOKEN",
"id": "112233445566",
"extra": "something",
},
@ -546,14 +548,14 @@ async def test_import_existing_config_entry(hass, mock_panel):
assert result["type"] == "abort"
# We should have updated the entry
# We should have updated the host info but not the access token
assert len(hass.config_entries.async_entries("konnected")) == 1
assert hass.config_entries.async_entries("konnected")[0].data == {
"host": "1.2.3.4",
"port": 1234,
"access_token": "ORIGINALTOKEN",
"id": "112233445566",
"model": "Konnected Pro",
"access_token": "SUPERSECRETTOKEN",
"extra": "something",
}

View file

@ -0,0 +1,90 @@
"""The tests for the Modbus sensor component."""
from datetime import timedelta
import logging
from unittest import mock
import pytest
from homeassistant.components.modbus.const import (
CALL_TYPE_REGISTER_INPUT,
CONF_REGISTER,
CONF_REGISTER_TYPE,
CONF_REGISTERS,
DEFAULT_HUB,
MODBUS_DOMAIN as DOMAIN,
)
from homeassistant.const import CONF_NAME, CONF_PLATFORM, CONF_SCAN_INTERVAL
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
from tests.common import MockModule, async_fire_time_changed, mock_integration
_LOGGER = logging.getLogger(__name__)
@pytest.fixture()
def mock_hub(hass):
"""Mock hub."""
mock_integration(hass, MockModule(DOMAIN))
hub = mock.MagicMock()
hub.name = "hub"
hass.data[DOMAIN] = {DEFAULT_HUB: hub}
return hub
class ReadResult:
"""Storage class for register read results."""
def __init__(self, register_words):
"""Init."""
self.registers = register_words
read_result = None
async def run_test(
hass, use_mock_hub, register_config, entity_domain, register_words, expected
):
"""Run test for given config and check that sensor outputs expected result."""
async def simulate_read_registers(unit, address, count):
"""Simulate modbus register read."""
del unit, address, count # not used in simulation, but in real connection
return read_result
# Full sensor configuration
sensor_name = "modbus_test_sensor"
scan_interval = 5
config = {
entity_domain: {
CONF_PLATFORM: "modbus",
CONF_SCAN_INTERVAL: scan_interval,
CONF_REGISTERS: [
dict(**{CONF_NAME: sensor_name, CONF_REGISTER: 1234}, **register_config)
],
}
}
# Setup inputs for the sensor
read_result = ReadResult(register_words)
if register_config.get(CONF_REGISTER_TYPE) == CALL_TYPE_REGISTER_INPUT:
use_mock_hub.read_input_registers = simulate_read_registers
else:
use_mock_hub.read_holding_registers = simulate_read_registers
# Initialize sensor
now = dt_util.utcnow()
with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now):
assert await async_setup_component(hass, entity_domain, config)
# Trigger update call with time_changed event
now += timedelta(seconds=scan_interval + 1)
with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now):
async_fire_time_changed(hass, now)
await hass.async_block_till_done()
# Check state
entity_id = f"{entity_domain}.{sensor_name}"
state = hass.states.get(entity_id).state
assert state == expected

View file

@ -1,8 +1,5 @@
"""The tests for the Modbus sensor component."""
from datetime import timedelta
from unittest import mock
import pytest
import logging
from homeassistant.components.modbus.const import (
CALL_TYPE_REGISTER_HOLDING,
@ -11,78 +8,18 @@ from homeassistant.components.modbus.const import (
CONF_DATA_TYPE,
CONF_OFFSET,
CONF_PRECISION,
CONF_REGISTER,
CONF_REGISTER_TYPE,
CONF_REGISTERS,
CONF_REVERSE_ORDER,
CONF_SCALE,
DATA_TYPE_FLOAT,
DATA_TYPE_INT,
DATA_TYPE_UINT,
DEFAULT_HUB,
MODBUS_DOMAIN,
)
from homeassistant.const import CONF_NAME, CONF_PLATFORM, CONF_SCAN_INTERVAL
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from tests.common import MockModule, async_fire_time_changed, mock_integration
from .conftest import run_test
@pytest.fixture()
def mock_hub(hass):
"""Mock hub."""
mock_integration(hass, MockModule(MODBUS_DOMAIN))
hub = mock.MagicMock()
hub.name = "hub"
hass.data[MODBUS_DOMAIN] = {DEFAULT_HUB: hub}
return hub
common_register_config = {CONF_NAME: "test-config", CONF_REGISTER: 1234}
class ReadResult:
"""Storage class for register read results."""
def __init__(self, register_words):
"""Init."""
self.registers = register_words
async def run_test(hass, mock_hub, register_config, register_words, expected):
"""Run test for given config and check that sensor outputs expected result."""
# Full sensor configuration
sensor_name = "modbus_test_sensor"
scan_interval = 5
config = {
MODBUS_DOMAIN: {
CONF_PLATFORM: "modbus",
CONF_SCAN_INTERVAL: scan_interval,
CONF_REGISTERS: [
dict(**{CONF_NAME: sensor_name, CONF_REGISTER: 1234}, **register_config)
],
}
}
# Setup inputs for the sensor
read_result = ReadResult(register_words)
if register_config.get(CONF_REGISTER_TYPE) == CALL_TYPE_REGISTER_INPUT:
mock_hub.read_input_registers.return_value = read_result
else:
mock_hub.read_holding_registers.return_value = read_result
# Initialize sensor
now = dt_util.utcnow()
with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now):
assert await async_setup_component(hass, MODBUS_DOMAIN, config)
# Trigger update call with time_changed event
now += timedelta(seconds=scan_interval + 1)
with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now):
async_fire_time_changed(hass, now)
await hass.async_block_till_done()
_LOGGER = logging.getLogger(__name__)
async def test_simple_word_register(hass, mock_hub):
@ -94,14 +31,26 @@ async def test_simple_word_register(hass, mock_hub):
CONF_OFFSET: 0,
CONF_PRECISION: 0,
}
await run_test(hass, mock_hub, register_config, register_words=[0], expected="0")
await run_test(
hass,
mock_hub,
register_config,
SENSOR_DOMAIN,
register_words=[0],
expected="0",
)
async def test_optional_conf_keys(hass, mock_hub):
"""Test handling of optional configuration keys."""
register_config = {}
await run_test(
hass, mock_hub, register_config, register_words=[0x8000], expected="-32768"
hass,
mock_hub,
register_config,
SENSOR_DOMAIN,
register_words=[0x8000],
expected="-32768",
)
@ -114,7 +63,14 @@ async def test_offset(hass, mock_hub):
CONF_OFFSET: 13,
CONF_PRECISION: 0,
}
await run_test(hass, mock_hub, register_config, register_words=[7], expected="20")
await run_test(
hass,
mock_hub,
register_config,
SENSOR_DOMAIN,
register_words=[7],
expected="20",
)
async def test_scale_and_offset(hass, mock_hub):
@ -126,7 +82,14 @@ async def test_scale_and_offset(hass, mock_hub):
CONF_OFFSET: 13,
CONF_PRECISION: 0,
}
await run_test(hass, mock_hub, register_config, register_words=[7], expected="34")
await run_test(
hass,
mock_hub,
register_config,
SENSOR_DOMAIN,
register_words=[7],
expected="34",
)
async def test_ints_can_have_precision(hass, mock_hub):
@ -139,7 +102,12 @@ async def test_ints_can_have_precision(hass, mock_hub):
CONF_PRECISION: 4,
}
await run_test(
hass, mock_hub, register_config, register_words=[7], expected="34.0000"
hass,
mock_hub,
register_config,
SENSOR_DOMAIN,
register_words=[7],
expected="34.0000",
)
@ -152,7 +120,14 @@ async def test_floats_get_rounded_correctly(hass, mock_hub):
CONF_OFFSET: 0,
CONF_PRECISION: 0,
}
await run_test(hass, mock_hub, register_config, register_words=[1], expected="2")
await run_test(
hass,
mock_hub,
register_config,
SENSOR_DOMAIN,
register_words=[1],
expected="2",
)
async def test_parameters_as_strings(hass, mock_hub):
@ -164,7 +139,14 @@ async def test_parameters_as_strings(hass, mock_hub):
CONF_OFFSET: "5",
CONF_PRECISION: "1",
}
await run_test(hass, mock_hub, register_config, register_words=[9], expected="18.5")
await run_test(
hass,
mock_hub,
register_config,
SENSOR_DOMAIN,
register_words=[9],
expected="18.5",
)
async def test_floating_point_scale(hass, mock_hub):
@ -176,7 +158,14 @@ async def test_floating_point_scale(hass, mock_hub):
CONF_OFFSET: 0,
CONF_PRECISION: 2,
}
await run_test(hass, mock_hub, register_config, register_words=[1], expected="2.40")
await run_test(
hass,
mock_hub,
register_config,
SENSOR_DOMAIN,
register_words=[1],
expected="2.40",
)
async def test_floating_point_offset(hass, mock_hub):
@ -188,7 +177,14 @@ async def test_floating_point_offset(hass, mock_hub):
CONF_OFFSET: -10.3,
CONF_PRECISION: 1,
}
await run_test(hass, mock_hub, register_config, register_words=[2], expected="-8.3")
await run_test(
hass,
mock_hub,
register_config,
SENSOR_DOMAIN,
register_words=[2],
expected="-8.3",
)
async def test_signed_two_word_register(hass, mock_hub):
@ -204,6 +200,7 @@ async def test_signed_two_word_register(hass, mock_hub):
hass,
mock_hub,
register_config,
SENSOR_DOMAIN,
register_words=[0x89AB, 0xCDEF],
expected="-1985229329",
)
@ -222,6 +219,7 @@ async def test_unsigned_two_word_register(hass, mock_hub):
hass,
mock_hub,
register_config,
SENSOR_DOMAIN,
register_words=[0x89AB, 0xCDEF],
expected=str(0x89ABCDEF),
)
@ -238,6 +236,7 @@ async def test_reversed(hass, mock_hub):
hass,
mock_hub,
register_config,
SENSOR_DOMAIN,
register_words=[0x89AB, 0xCDEF],
expected=str(0xCDEF89AB),
)
@ -256,6 +255,7 @@ async def test_four_word_register(hass, mock_hub):
hass,
mock_hub,
register_config,
SENSOR_DOMAIN,
register_words=[0x89AB, 0xCDEF, 0x0123, 0x4567],
expected="9920249030613615975",
)
@ -274,6 +274,7 @@ async def test_four_word_register_precision_is_intact_with_int_params(hass, mock
hass,
mock_hub,
register_config,
SENSOR_DOMAIN,
register_words=[0x0123, 0x4567, 0x89AB, 0xCDEF],
expected="163971058432973793",
)
@ -292,6 +293,7 @@ async def test_four_word_register_precision_is_lost_with_float_params(hass, mock
hass,
mock_hub,
register_config,
SENSOR_DOMAIN,
register_words=[0x0123, 0x4567, 0x89AB, 0xCDEF],
expected="163971058432973792",
)
@ -311,6 +313,7 @@ async def test_two_word_input_register(hass, mock_hub):
hass,
mock_hub,
register_config,
SENSOR_DOMAIN,
register_words=[0x89AB, 0xCDEF],
expected=str(0x89ABCDEF),
)
@ -330,6 +333,7 @@ async def test_two_word_holding_register(hass, mock_hub):
hass,
mock_hub,
register_config,
SENSOR_DOMAIN,
register_words=[0x89AB, 0xCDEF],
expected=str(0x89ABCDEF),
)
@ -349,6 +353,7 @@ async def test_float_data_type(hass, mock_hub):
hass,
mock_hub,
register_config,
SENSOR_DOMAIN,
register_words=[16286, 1617],
expected="1.23457",
)

View file

@ -294,6 +294,58 @@ async def test_update(hass):
assert "three" == state.attributes[ATTR_INPUT_SOURCE]
async def test_failed_update(hass):
"""Test updating failure from monoprice."""
monoprice = MockMonoprice()
await _setup_monoprice(hass, monoprice)
# Changing media player to new state
await _call_media_player_service(
hass, SERVICE_VOLUME_SET, {"entity_id": ZONE_1_ID, "volume_level": 0.0}
)
await _call_media_player_service(
hass, SERVICE_SELECT_SOURCE, {"entity_id": ZONE_1_ID, "source": "one"}
)
monoprice.set_source(11, 3)
monoprice.set_volume(11, 38)
with patch.object(MockMonoprice, "zone_status", side_effect=SerialException):
await async_update_entity(hass, ZONE_1_ID)
await hass.async_block_till_done()
state = hass.states.get(ZONE_1_ID)
assert state.attributes[ATTR_MEDIA_VOLUME_LEVEL] == 0.0
assert state.attributes[ATTR_INPUT_SOURCE] == "one"
async def test_empty_update(hass):
"""Test updating with no state from monoprice."""
monoprice = MockMonoprice()
await _setup_monoprice(hass, monoprice)
# Changing media player to new state
await _call_media_player_service(
hass, SERVICE_VOLUME_SET, {"entity_id": ZONE_1_ID, "volume_level": 0.0}
)
await _call_media_player_service(
hass, SERVICE_SELECT_SOURCE, {"entity_id": ZONE_1_ID, "source": "one"}
)
monoprice.set_source(11, 3)
monoprice.set_volume(11, 38)
with patch.object(MockMonoprice, "zone_status", return_value=None):
await async_update_entity(hass, ZONE_1_ID)
await hass.async_block_till_done()
state = hass.states.get(ZONE_1_ID)
assert state.attributes[ATTR_MEDIA_VOLUME_LEVEL] == 0.0
assert state.attributes[ATTR_INPUT_SOURCE] == "one"
async def test_supported_features(hass):
"""Test supported features property."""
await _setup_monoprice(hass, MockMonoprice())

View file

@ -1,20 +0,0 @@
"""Common fixtures and functions for Plex tests."""
from datetime import timedelta
from homeassistant.components.plex.const import (
DEBOUNCE_TIMEOUT,
PLEX_UPDATE_PLATFORMS_SIGNAL,
)
from homeassistant.helpers.dispatcher import async_dispatcher_send
import homeassistant.util.dt as dt_util
from tests.common import async_fire_time_changed
async def trigger_plex_update(hass, server_id):
"""Update Plex by sending signal and jumping ahead by debounce timeout."""
async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
await hass.async_block_till_done()
next_update = dt_util.utcnow() + timedelta(seconds=DEBOUNCE_TIMEOUT)
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()

View file

@ -15,13 +15,14 @@ from homeassistant.components.plex.const import (
CONF_USE_EPISODE_ART,
DOMAIN,
PLEX_SERVER_CONFIG,
PLEX_UPDATE_PLATFORMS_SIGNAL,
SERVERS,
)
from homeassistant.config_entries import ENTRY_STATE_LOADED
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TOKEN, CONF_URL
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.setup import async_setup_component
from .common import trigger_plex_update
from .const import DEFAULT_DATA, DEFAULT_OPTIONS, MOCK_SERVERS, MOCK_TOKEN
from .mock_classes import MockPlexAccount, MockPlexServer
@ -415,7 +416,8 @@ async def test_option_flow_new_users_available(hass, caplog):
server_id = mock_plex_server.machineIdentifier
await trigger_plex_update(hass, server_id)
async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
await hass.async_block_till_done()
monitored_users = hass.data[DOMAIN][SERVERS][server_id].option_monitored_users

View file

@ -3,8 +3,9 @@ import copy
from datetime import timedelta
import ssl
from asynctest import patch
from asynctest import ClockedTestCase, patch
import plexapi
import pytest
import requests
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
@ -23,14 +24,19 @@ from homeassistant.const import (
CONF_URL,
CONF_VERIFY_SSL,
)
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
from .common import trigger_plex_update
from .const import DEFAULT_DATA, DEFAULT_OPTIONS, MOCK_SERVERS, MOCK_TOKEN
from .mock_classes import MockPlexAccount, MockPlexServer
from tests.common import MockConfigEntry, async_fire_time_changed
from tests.common import (
MockConfigEntry,
async_fire_time_changed,
async_test_home_assistant,
mock_storage,
)
async def test_setup_with_config(hass):
@ -67,70 +73,90 @@ async def test_setup_with_config(hass):
assert loaded_server.plex_server == mock_plex_server
assert server_id in hass.data[const.DOMAIN][const.DISPATCHERS]
assert server_id in hass.data[const.DOMAIN][const.WEBSOCKETS]
assert (
hass.data[const.DOMAIN][const.PLATFORMS_COMPLETED][server_id] == const.PLATFORMS
)
class TestClockedPlex(ClockedTestCase):
"""Create clock-controlled asynctest class."""
async def test_setup_with_config_entry(hass, caplog):
"""Test setup component with config."""
@pytest.fixture(autouse=True)
def inject_fixture(self, caplog):
"""Inject pytest fixtures as instance attributes."""
self.caplog = caplog
mock_plex_server = MockPlexServer()
async def setUp(self):
"""Initialize this test class."""
self.hass = await async_test_home_assistant(self.loop)
self.mock_storage = mock_storage()
self.mock_storage.__enter__()
entry = MockConfigEntry(
domain=const.DOMAIN,
data=DEFAULT_DATA,
options=DEFAULT_OPTIONS,
unique_id=DEFAULT_DATA["server_id"],
)
async def tearDown(self):
"""Clean up the HomeAssistant instance."""
await self.hass.async_stop()
self.mock_storage.__exit__(None, None, None)
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
"homeassistant.components.plex.PlexWebsocket.listen"
) as mock_listen:
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
async def test_setup_with_config_entry(self):
"""Test setup component with config."""
hass = self.hass
mock_plex_server = MockPlexServer()
entry = MockConfigEntry(
domain=const.DOMAIN,
data=DEFAULT_DATA,
options=DEFAULT_OPTIONS,
unique_id=DEFAULT_DATA["server_id"],
)
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
"homeassistant.components.plex.PlexWebsocket.listen"
) as mock_listen:
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert mock_listen.called
assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1
assert entry.state == ENTRY_STATE_LOADED
server_id = mock_plex_server.machineIdentifier
loaded_server = hass.data[const.DOMAIN][const.SERVERS][server_id]
assert loaded_server.plex_server == mock_plex_server
async_dispatcher_send(
hass, const.PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)
)
await hass.async_block_till_done()
assert mock_listen.called
sensor = hass.states.get("sensor.plex_plex_server_1")
assert sensor.state == str(len(mock_plex_server.accounts))
assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1
assert entry.state == ENTRY_STATE_LOADED
server_id = mock_plex_server.machineIdentifier
loaded_server = hass.data[const.DOMAIN][const.SERVERS][server_id]
assert loaded_server.plex_server == mock_plex_server
assert server_id in hass.data[const.DOMAIN][const.DISPATCHERS]
assert server_id in hass.data[const.DOMAIN][const.WEBSOCKETS]
assert (
hass.data[const.DOMAIN][const.PLATFORMS_COMPLETED][server_id] == const.PLATFORMS
)
await trigger_plex_update(hass, server_id)
sensor = hass.states.get("sensor.plex_plex_server_1")
assert sensor.state == str(len(mock_plex_server.accounts))
await trigger_plex_update(hass, server_id)
for test_exception in (
plexapi.exceptions.BadRequest,
requests.exceptions.RequestException,
):
with patch.object(
mock_plex_server, "clients", side_effect=test_exception
) as patched_clients_bad_request:
await trigger_plex_update(hass, server_id)
assert patched_clients_bad_request.called
assert (
f"Could not connect to Plex server: {mock_plex_server.friendlyName}"
in caplog.text
# Ensure existing entities refresh
await self.advance(const.DEBOUNCE_TIMEOUT)
async_dispatcher_send(
hass, const.PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)
)
caplog.clear()
await hass.async_block_till_done()
for test_exception in (
plexapi.exceptions.BadRequest,
requests.exceptions.RequestException,
):
with patch.object(
mock_plex_server, "clients", side_effect=test_exception
) as patched_clients_bad_request:
await self.advance(const.DEBOUNCE_TIMEOUT)
async_dispatcher_send(
hass, const.PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)
)
await hass.async_block_till_done()
assert patched_clients_bad_request.called
assert (
f"Could not connect to Plex server: {mock_plex_server.friendlyName}"
in self.caplog.text
)
self.caplog.clear()
async def test_set_config_entry_unique_id(hass):
@ -251,22 +277,12 @@ async def test_unload_config_entry(hass):
assert loaded_server.plex_server == mock_plex_server
assert server_id in hass.data[const.DOMAIN][const.DISPATCHERS]
assert server_id in hass.data[const.DOMAIN][const.WEBSOCKETS]
assert (
hass.data[const.DOMAIN][const.PLATFORMS_COMPLETED][server_id] == const.PLATFORMS
)
with patch("homeassistant.components.plex.PlexWebsocket.close") as mock_close:
await hass.config_entries.async_unload(entry.entry_id)
assert mock_close.called
assert entry.state == ENTRY_STATE_NOT_LOADED
assert server_id not in hass.data[const.DOMAIN][const.SERVERS]
assert server_id not in hass.data[const.DOMAIN][const.DISPATCHERS]
assert server_id not in hass.data[const.DOMAIN][const.WEBSOCKETS]
async def test_setup_with_photo_session(hass):
"""Test setup component with config."""
@ -292,7 +308,8 @@ async def test_setup_with_photo_session(hass):
server_id = mock_plex_server.machineIdentifier
await trigger_plex_update(hass, server_id)
async_dispatcher_send(hass, const.PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
await hass.async_block_till_done()
media_player = hass.states.get("media_player.plex_product_title")
assert media_player.state == "idle"

View file

@ -1,8 +1,7 @@
"""Tests for Plex server."""
import copy
from datetime import timedelta
from asynctest import patch
from asynctest import ClockedTestCase, patch
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
from homeassistant.components.plex.const import (
@ -14,13 +13,11 @@ from homeassistant.components.plex.const import (
SERVERS,
)
from homeassistant.helpers.dispatcher import async_dispatcher_send
import homeassistant.util.dt as dt_util
from .common import trigger_plex_update
from .const import DEFAULT_DATA, DEFAULT_OPTIONS
from .mock_classes import MockPlexServer
from tests.common import MockConfigEntry, async_fire_time_changed
from tests.common import MockConfigEntry, async_test_home_assistant, mock_storage
async def test_new_users_available(hass):
@ -48,7 +45,8 @@ async def test_new_users_available(hass):
server_id = mock_plex_server.machineIdentifier
await trigger_plex_update(hass, server_id)
async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
await hass.async_block_till_done()
monitored_users = hass.data[DOMAIN][SERVERS][server_id].option_monitored_users
@ -86,7 +84,8 @@ async def test_new_ignored_users_available(hass, caplog):
server_id = mock_plex_server.machineIdentifier
await trigger_plex_update(hass, server_id)
async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
await hass.async_block_till_done()
monitored_users = hass.data[DOMAIN][SERVERS][server_id].option_monitored_users
@ -100,72 +99,109 @@ async def test_new_ignored_users_available(hass, caplog):
assert sensor.state == str(len(mock_plex_server.accounts))
async def test_mark_sessions_idle(hass):
"""Test marking media_players as idle when sessions end."""
entry = MockConfigEntry(
domain=DOMAIN,
data=DEFAULT_DATA,
options=DEFAULT_OPTIONS,
unique_id=DEFAULT_DATA["server_id"],
)
class TestClockedPlex(ClockedTestCase):
"""Create clock-controlled asynctest class."""
mock_plex_server = MockPlexServer(config_entry=entry)
async def setUp(self):
"""Initialize this test class."""
self.hass = await async_test_home_assistant(self.loop)
self.mock_storage = mock_storage()
self.mock_storage.__enter__()
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
"homeassistant.components.plex.PlexWebsocket.listen"
):
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
async def tearDown(self):
"""Clean up the HomeAssistant instance."""
await self.hass.async_stop()
self.mock_storage.__exit__(None, None, None)
async def test_mark_sessions_idle(self):
"""Test marking media_players as idle when sessions end."""
hass = self.hass
entry = MockConfigEntry(
domain=DOMAIN,
data=DEFAULT_DATA,
options=DEFAULT_OPTIONS,
unique_id=DEFAULT_DATA["server_id"],
)
mock_plex_server = MockPlexServer(config_entry=entry)
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
"homeassistant.components.plex.PlexWebsocket.listen"
):
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
server_id = mock_plex_server.machineIdentifier
async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
await hass.async_block_till_done()
server_id = mock_plex_server.machineIdentifier
sensor = hass.states.get("sensor.plex_plex_server_1")
assert sensor.state == str(len(mock_plex_server.accounts))
await trigger_plex_update(hass, server_id)
mock_plex_server.clear_clients()
mock_plex_server.clear_sessions()
sensor = hass.states.get("sensor.plex_plex_server_1")
assert sensor.state == str(len(mock_plex_server.accounts))
mock_plex_server.clear_clients()
mock_plex_server.clear_sessions()
await trigger_plex_update(hass, server_id)
sensor = hass.states.get("sensor.plex_plex_server_1")
assert sensor.state == "0"
async def test_debouncer(hass, caplog):
"""Test debouncer decorator logic."""
entry = MockConfigEntry(
domain=DOMAIN,
data=DEFAULT_DATA,
options=DEFAULT_OPTIONS,
unique_id=DEFAULT_DATA["server_id"],
)
mock_plex_server = MockPlexServer(config_entry=entry)
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
"homeassistant.components.plex.PlexWebsocket.listen"
):
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await self.advance(DEBOUNCE_TIMEOUT)
async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
await hass.async_block_till_done()
server_id = mock_plex_server.machineIdentifier
sensor = hass.states.get("sensor.plex_plex_server_1")
assert sensor.state == "0"
# First two updates are skipped
async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
await hass.async_block_till_done()
async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
await hass.async_block_till_done()
async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
await hass.async_block_till_done()
async def test_debouncer(self):
"""Test debouncer behavior."""
hass = self.hass
next_update = dt_util.utcnow() + timedelta(seconds=DEBOUNCE_TIMEOUT)
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
entry = MockConfigEntry(
domain=DOMAIN,
data=DEFAULT_DATA,
options=DEFAULT_OPTIONS,
unique_id=DEFAULT_DATA["server_id"],
)
assert (
caplog.text.count(f"Throttling update of {mock_plex_server.friendlyName}") == 2
)
mock_plex_server = MockPlexServer(config_entry=entry)
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
"homeassistant.components.plex.PlexWebsocket.listen"
):
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
server_id = mock_plex_server.machineIdentifier
with patch.object(mock_plex_server, "clients", return_value=[]), patch.object(
mock_plex_server, "sessions", return_value=[]
) as mock_update:
# Called immediately
async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
await hass.async_block_till_done()
assert mock_update.call_count == 1
# Throttled
async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
await hass.async_block_till_done()
assert mock_update.call_count == 1
# Throttled
async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
await hass.async_block_till_done()
assert mock_update.call_count == 1
# Called from scheduler
await self.advance(DEBOUNCE_TIMEOUT)
await hass.async_block_till_done()
assert mock_update.call_count == 2
# Throttled
async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
await hass.async_block_till_done()
assert mock_update.call_count == 2
# Called from scheduler
await self.advance(DEBOUNCE_TIMEOUT)
await hass.async_block_till_done()
assert mock_update.call_count == 3

View file

@ -1,6 +1,6 @@
"""Tests for light platform."""
from typing import Callable, NamedTuple
from unittest.mock import Mock, patch
from unittest.mock import Mock, PropertyMock, patch
from pyHS100 import SmartDeviceException
import pytest
@ -16,7 +16,11 @@ from homeassistant.components.light import (
ATTR_HS_COLOR,
DOMAIN as LIGHT_DOMAIN,
)
from homeassistant.components.tplink.common import CONF_DISCOVERY, CONF_LIGHT
from homeassistant.components.tplink.common import (
CONF_DIMMER,
CONF_DISCOVERY,
CONF_LIGHT,
)
from homeassistant.const import (
ATTR_ENTITY_ID,
CONF_HOST,
@ -41,6 +45,15 @@ class LightMockData(NamedTuple):
get_emeter_monthly_mock: Mock
class SmartSwitchMockData(NamedTuple):
"""Mock smart switch data."""
sys_info: dict
state_mock: Mock
brightness_mock: Mock
get_sysinfo_mock: Mock
@pytest.fixture(name="light_mock_data")
def light_mock_data_fixture() -> None:
"""Create light mock data."""
@ -152,6 +165,74 @@ def light_mock_data_fixture() -> None:
)
@pytest.fixture(name="dimmer_switch_mock_data")
def dimmer_switch_mock_data_fixture() -> None:
"""Create dimmer switch mock data."""
sys_info = {
"sw_ver": "1.2.3",
"hw_ver": "2.3.4",
"mac": "aa:bb:cc:dd:ee:ff",
"mic_mac": "00:11:22:33:44",
"type": "switch",
"hwId": "1234",
"fwId": "4567",
"oemId": "891011",
"dev_name": "dimmer1",
"rssi": 11,
"latitude": "0",
"longitude": "0",
"is_color": False,
"is_dimmable": True,
"is_variable_color_temp": False,
"model": "HS220",
"alias": "dimmer1",
"feature": ":",
"relay_state": 1,
"brightness": 13,
}
def state(*args, **kwargs):
nonlocal sys_info
if len(args) == 0:
return sys_info["relay_state"]
if args[0] == "ON":
sys_info["relay_state"] = 1
else:
sys_info["relay_state"] = 0
def brightness(*args, **kwargs):
nonlocal sys_info
if len(args) == 0:
return sys_info["brightness"]
if sys_info["brightness"] == 0:
sys_info["relay_state"] = 0
else:
sys_info["relay_state"] = 1
sys_info["brightness"] = args[0]
get_sysinfo_patch = patch(
"homeassistant.components.tplink.common.SmartDevice.get_sysinfo",
return_value=sys_info,
)
state_patch = patch(
"homeassistant.components.tplink.common.SmartPlug.state",
new_callable=PropertyMock,
side_effect=state,
)
brightness_patch = patch(
"homeassistant.components.tplink.common.SmartPlug.brightness",
new_callable=PropertyMock,
side_effect=brightness,
)
with brightness_patch as brightness_mock, state_patch as state_mock, get_sysinfo_patch as get_sysinfo_mock:
yield SmartSwitchMockData(
sys_info=sys_info,
brightness_mock=brightness_mock,
state_mock=state_mock,
get_sysinfo_mock=get_sysinfo_mock,
)
async def update_entity(hass: HomeAssistant, entity_id: str) -> None:
"""Run an update action for an entity."""
await hass.services.async_call(
@ -160,6 +241,96 @@ async def update_entity(hass: HomeAssistant, entity_id: str) -> None:
await hass.async_block_till_done()
async def test_smartswitch(
hass: HomeAssistant, dimmer_switch_mock_data: SmartSwitchMockData
) -> None:
"""Test function."""
sys_info = dimmer_switch_mock_data.sys_info
await async_setup_component(hass, HA_DOMAIN, {})
await hass.async_block_till_done()
await async_setup_component(
hass,
tplink.DOMAIN,
{
tplink.DOMAIN: {
CONF_DISCOVERY: False,
CONF_DIMMER: [{CONF_HOST: "123.123.123.123"}],
}
},
)
await hass.async_block_till_done()
assert hass.states.get("light.dimmer1")
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: "light.dimmer1"},
blocking=True,
)
await hass.async_block_till_done()
await update_entity(hass, "light.dimmer1")
assert hass.states.get("light.dimmer1").state == "off"
assert sys_info["relay_state"] == 0
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: "light.dimmer1", ATTR_BRIGHTNESS: 50},
blocking=True,
)
await hass.async_block_till_done()
await update_entity(hass, "light.dimmer1")
state = hass.states.get("light.dimmer1")
assert state.state == "on"
assert state.attributes["brightness"] == 48.45
assert sys_info["relay_state"] == 1
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: "light.dimmer1", ATTR_BRIGHTNESS: 55},
blocking=True,
)
await hass.async_block_till_done()
await update_entity(hass, "light.dimmer1")
state = hass.states.get("light.dimmer1")
assert state.state == "on"
assert state.attributes["brightness"] == 53.55
assert sys_info["brightness"] == 21
sys_info["relay_state"] = 0
sys_info["brightness"] = 66
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: "light.dimmer1"},
blocking=True,
)
await hass.async_block_till_done()
await update_entity(hass, "light.dimmer1")
state = hass.states.get("light.dimmer1")
assert state.state == "off"
await hass.services.async_call(
LIGHT_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: "light.dimmer1"}, blocking=True,
)
await hass.async_block_till_done()
await update_entity(hass, "light.dimmer1")
state = hass.states.get("light.dimmer1")
assert state.state == "on"
assert state.attributes["brightness"] == 168.3
assert sys_info["brightness"] == 66
async def test_light(hass: HomeAssistant, light_mock_data: LightMockData) -> None:
"""Test function."""
light_state = light_mock_data.light_state