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 @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the state attributes of the sensor.""" """Return the state attributes of the sensor."""
print(self.current_observations)
print(self.observations_by_entity)
return { return {
ATTR_OBSERVATIONS: list(self.current_observations.values()), ATTR_OBSERVATIONS: list(self.current_observations.values()),
ATTR_OCCURRED_OBSERVATION_ENTITIES: list( ATTR_OCCURRED_OBSERVATION_ENTITIES: list(

View file

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

View file

@ -116,7 +116,12 @@ class IPPMarkerSensor(IPPSensor):
@property @property
def state(self) -> Union[None, str, int, float]: def state(self) -> Union[None, str, int, float]:
"""Return the state of the sensor.""" """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): class IPPPrinterSensor(IPPSensor):

View file

@ -283,11 +283,6 @@ class KonnectedFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
# build config info and wait for user confirmation # build config info and wait for user confirmation
self.data[CONF_HOST] = user_input[CONF_HOST] self.data[CONF_HOST] = user_input[CONF_HOST]
self.data[CONF_PORT] = user_input[CONF_PORT] 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 # brief delay to allow processing of recent status req
await asyncio.sleep(0.1) 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_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( return self.async_create_entry(
title=KONN_PANEL_MODEL_NAMES[self.data[CONF_MODEL]], data=self.data, 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_PORT,
CONF_TIMEOUT, CONF_TIMEOUT,
CONF_TYPE, CONF_TYPE,
EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_STOP,
) )
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -36,7 +35,7 @@ from .const import (
CONF_PARITY, CONF_PARITY,
CONF_STOPBITS, CONF_STOPBITS,
DEFAULT_HUB, DEFAULT_HUB,
MODBUS_DOMAIN, MODBUS_DOMAIN as DOMAIN,
SERVICE_WRITE_COIL, SERVICE_WRITE_COIL,
SERVICE_WRITE_REGISTER, SERVICE_WRITE_REGISTER,
) )
@ -69,7 +68,7 @@ ETHERNET_SCHEMA = BASE_SCHEMA.extend(
) )
CONFIG_SCHEMA = vol.Schema( 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, extra=vol.ALLOW_EXTRA,
) )
@ -96,10 +95,9 @@ SERVICE_WRITE_COIL_SCHEMA = vol.Schema(
async def async_setup(hass, config): async def async_setup(hass, config):
"""Set up Modbus component.""" """Set up Modbus component."""
hass.data[MODBUS_DOMAIN] = hub_collect = {} hass.data[DOMAIN] = hub_collect = {}
_LOGGER.debug("registering hubs") for client_config in config[DOMAIN]:
for client_config in config[MODBUS_DOMAIN]:
hub_collect[client_config[CONF_NAME]] = ModbusHub(client_config, hass.loop) hub_collect[client_config[CONF_NAME]] = ModbusHub(client_config, hass.loop)
def stop_modbus(event): def stop_modbus(event):
@ -107,28 +105,13 @@ async def async_setup(hass, config):
for client in hub_collect.values(): for client in hub_collect.values():
del client del client
def start_modbus(event): def start_modbus():
"""Start Modbus service.""" """Start Modbus service."""
for client in hub_collect.values(): for client in hub_collect.values():
_LOGGER.debug("setup hub %s", client.name)
client.setup() client.setup()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_modbus) 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): async def write_register(service):
"""Write Modbus registers.""" """Write Modbus registers."""
unit = int(float(service.data[ATTR_UNIT])) unit = int(float(service.data[ATTR_UNIT]))
@ -152,8 +135,19 @@ async def async_setup(hass, config):
client_name = service.data[ATTR_HUB] client_name = service.data[ATTR_HUB]
await hub_collect[client_name].write_coil(unit, address, state) 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 return True
@ -162,7 +156,6 @@ class ModbusHub:
def __init__(self, client_config, main_loop): def __init__(self, client_config, main_loop):
"""Initialize the Modbus hub.""" """Initialize the Modbus hub."""
_LOGGER.debug("Preparing setup: %s", client_config)
# generic configuration # generic configuration
self._loop = main_loop self._loop = main_loop
@ -172,7 +165,7 @@ class ModbusHub:
self._config_type = client_config[CONF_TYPE] self._config_type = client_config[CONF_TYPE]
self._config_port = client_config[CONF_PORT] self._config_port = client_config[CONF_PORT]
self._config_timeout = client_config[CONF_TIMEOUT] self._config_timeout = client_config[CONF_TIMEOUT]
self._config_delay = client_config[CONF_DELAY] self._config_delay = 0
if self._config_type == "serial": if self._config_type == "serial":
# serial configuration # serial configuration
@ -184,6 +177,7 @@ class ModbusHub:
else: else:
# network configuration # network configuration
self._config_host = client_config[CONF_HOST] self._config_host = client_config[CONF_HOST]
self._config_delay = client_config[CONF_DELAY]
@property @property
def name(self): def name(self):
@ -201,7 +195,6 @@ class ModbusHub:
# Client* do deliver loop, client as result but # Client* do deliver loop, client as result but
# pylint does not accept that fact # pylint does not accept that fact
_LOGGER.debug("doing setup")
if self._config_type == "serial": if self._config_type == "serial":
_, self._client = ClientSerial( _, self._client = ClientSerial(
schedulers.ASYNC_IO, schedulers.ASYNC_IO,
@ -211,7 +204,6 @@ class ModbusHub:
stopbits=self._config_stopbits, stopbits=self._config_stopbits,
bytesize=self._config_bytesize, bytesize=self._config_bytesize,
parity=self._config_parity, parity=self._config_parity,
timeout=self._config_timeout,
loop=self._loop, loop=self._loop,
) )
elif self._config_type == "rtuovertcp": 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.""" """Set up the Modbus binary sensors."""
sensors = [] sensors = []
for entry in config[CONF_INPUTS]: 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): 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.""" """Set up the Modbus Thermostat Platform."""
name = config[CONF_NAME] name = config[CONF_NAME]
modbus_slave = config[CONF_SLAVE] 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_name = config[CONF_HUB]
hub = hass.data[MODBUS_DOMAIN][hub_name] hub = hass.data[MODBUS_DOMAIN][hub_name]
add_entities( async_add_entities(
[ [
ModbusThermostat( ModbusThermostat(
hub, 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.""" """Set up the Modbus sensors."""
sensors = [] sensors = []
data_types = {DATA_TYPE_INT: {1: "h", 2: "i", 4: "q"}} 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: if not sensors:
return False return False
add_entities(sensors) async_add_entities(sensors)
class ModbusRegisterSensor(RestoreEntity): 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.""" """Read configuration and create Modbus devices."""
switches = [] switches = []
if CONF_COILS in config: 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): class ModbusCoilSwitch(ToggleEntity, RestoreEntity):

View file

@ -1,6 +1,8 @@
"""Support for interfacing with Monoprice 6 zone home audio controller.""" """Support for interfacing with Monoprice 6 zone home audio controller."""
import logging import logging
from serial import SerialException
from homeassistant import core from homeassistant import core
from homeassistant.components.media_player import MediaPlayerDevice from homeassistant.components.media_player import MediaPlayerDevice
from homeassistant.components.media_player.const import ( 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__) _LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 1
SUPPORT_MONOPRICE = ( SUPPORT_MONOPRICE = (
SUPPORT_VOLUME_MUTE SUPPORT_VOLUME_MUTE
| SUPPORT_VOLUME_SET | SUPPORT_VOLUME_SET
@ -127,9 +131,15 @@ class MonopriceZone(MediaPlayerDevice):
def update(self): def update(self):
"""Retrieve latest state.""" """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: if not state:
return False return
self._state = STATE_ON if state.power else STATE_OFF self._state = STATE_ON if state.power else STATE_OFF
self._volume = state.volume self._volume = state.volume
self._mute = state.mute self._mute = state.mute
@ -138,7 +148,6 @@ class MonopriceZone(MediaPlayerDevice):
self._source = self._source_id_name[idx] self._source = self._source_id_name[idx]
else: else:
self._source = None self._source = None
return True
@property @property
def entity_registry_enabled_default(self): def entity_registry_enabled_default(self):

View file

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

View file

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

View file

@ -1,5 +1,4 @@
"""Shared class to maintain Plex server instances.""" """Shared class to maintain Plex server instances."""
from functools import partial, wraps
import logging import logging
import ssl import ssl
from urllib.parse import urlparse from urllib.parse import urlparse
@ -13,8 +12,8 @@ import requests.exceptions
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
from homeassistant.const import CONF_TOKEN, CONF_URL, CONF_VERIFY_SSL from homeassistant.const import CONF_TOKEN, CONF_URL, CONF_VERIFY_SSL
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.debounce import Debouncer
from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.event import async_call_later
from .const import ( from .const import (
CONF_CLIENT_IDENTIFIER, CONF_CLIENT_IDENTIFIER,
@ -43,31 +42,6 @@ plexapi.X_PLEX_PRODUCT = X_PLEX_PRODUCT
plexapi.X_PLEX_VERSION = X_PLEX_VERSION 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: class PlexServer:
"""Manages a single Plex server connection.""" """Manages a single Plex server connection."""
@ -87,6 +61,13 @@ class PlexServer:
self._accounts = [] self._accounts = []
self._owner_username = None self._owner_username = None
self._version = 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 # Header conditionally added as it is not available in config entry v1
if CONF_CLIENT_IDENTIFIER in server_config: if CONF_CLIENT_IDENTIFIER in server_config:
@ -192,8 +173,7 @@ class PlexServer:
"""Fetch all data from the Plex server in a single method.""" """Fetch all data from the Plex server in a single method."""
return (self._plex_server.clients(), self._plex_server.sessions()) 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.""" """Update the platform entities."""
_LOGGER.debug("Updating devices") _LOGGER.debug("Updating devices")

View file

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

View file

@ -40,6 +40,7 @@ ATTR_MONTHLY_ENERGY_KWH = "monthly_energy_kwh"
LIGHT_STATE_DFT_ON = "dft_on_state" LIGHT_STATE_DFT_ON = "dft_on_state"
LIGHT_STATE_ON_OFF = "on_off" LIGHT_STATE_ON_OFF = "on_off"
LIGHT_STATE_RELAY_STATE = "relay_state"
LIGHT_STATE_BRIGHTNESS = "brightness" LIGHT_STATE_BRIGHTNESS = "brightness"
LIGHT_STATE_COLOR_TEMP = "color_temp" LIGHT_STATE_COLOR_TEMP = "color_temp"
LIGHT_STATE_HUE = "hue" LIGHT_STATE_HUE = "hue"
@ -128,6 +129,7 @@ class LightFeatures(NamedTuple):
supported_features: int supported_features: int
min_mireds: float min_mireds: float
max_mireds: float max_mireds: float
has_emeter: bool
class TPLinkSmartBulb(Light): class TPLinkSmartBulb(Light):
@ -285,8 +287,9 @@ class TPLinkSmartBulb(Light):
model = sysinfo[LIGHT_SYSINFO_MODEL] model = sysinfo[LIGHT_SYSINFO_MODEL]
min_mireds = None min_mireds = None
max_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 supported_features += SUPPORT_BRIGHTNESS
if sysinfo.get(LIGHT_SYSINFO_IS_VARIABLE_COLOR_TEMP): if sysinfo.get(LIGHT_SYSINFO_IS_VARIABLE_COLOR_TEMP):
supported_features += SUPPORT_COLOR_TEMP supported_features += SUPPORT_COLOR_TEMP
@ -306,6 +309,7 @@ class TPLinkSmartBulb(Light):
supported_features=supported_features, supported_features=supported_features,
min_mireds=min_mireds, min_mireds=min_mireds,
max_mireds=max_mireds, max_mireds=max_mireds,
has_emeter=has_emeter,
) )
def _get_light_state_retry(self) -> LightState: def _get_light_state_retry(self) -> LightState:
@ -357,10 +361,10 @@ class TPLinkSmartBulb(Light):
def _get_light_state(self) -> LightState: def _get_light_state(self) -> LightState:
"""Get the light state.""" """Get the light state."""
self._update_emeter() 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): def _update_emeter(self):
if not self.smartbulb.has_emeter: if not self._light_features.has_emeter:
return return
now = dt_util.utcnow() now = dt_util.utcnow()
@ -439,7 +443,44 @@ class TPLinkSmartBulb(Light):
if not diff: if not diff:
return 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): def _light_state_diff(old_light_state: LightState, new_light_state: LightState):

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -362,10 +362,11 @@ async def test_ssdp_host_update(hass, mock_panel):
) )
assert result["type"] == "abort" 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] entry = hass.config_entries.async_entries(config_flow.DOMAIN)[0]
assert entry.data["host"] == "1.1.1.1" assert entry.data["host"] == "1.1.1.1"
assert entry.data["port"] == 1234 assert entry.data["port"] == 1234
assert entry.data["access_token"] == "11223344556677889900"
async def test_import_existing_config(hass, mock_panel): async def test_import_existing_config(hass, mock_panel):
@ -494,6 +495,7 @@ async def test_import_existing_config_entry(hass, mock_panel):
data={ data={
"host": "0.0.0.0", "host": "0.0.0.0",
"port": 1111, "port": 1111,
"access_token": "ORIGINALTOKEN",
"id": "112233445566", "id": "112233445566",
"extra": "something", "extra": "something",
}, },
@ -546,14 +548,14 @@ async def test_import_existing_config_entry(hass, mock_panel):
assert result["type"] == "abort" 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 len(hass.config_entries.async_entries("konnected")) == 1
assert hass.config_entries.async_entries("konnected")[0].data == { assert hass.config_entries.async_entries("konnected")[0].data == {
"host": "1.2.3.4", "host": "1.2.3.4",
"port": 1234, "port": 1234,
"access_token": "ORIGINALTOKEN",
"id": "112233445566", "id": "112233445566",
"model": "Konnected Pro", "model": "Konnected Pro",
"access_token": "SUPERSECRETTOKEN",
"extra": "something", "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.""" """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 ( from homeassistant.components.modbus.const import (
CALL_TYPE_REGISTER_HOLDING, CALL_TYPE_REGISTER_HOLDING,
@ -11,78 +8,18 @@ from homeassistant.components.modbus.const import (
CONF_DATA_TYPE, CONF_DATA_TYPE,
CONF_OFFSET, CONF_OFFSET,
CONF_PRECISION, CONF_PRECISION,
CONF_REGISTER,
CONF_REGISTER_TYPE, CONF_REGISTER_TYPE,
CONF_REGISTERS,
CONF_REVERSE_ORDER, CONF_REVERSE_ORDER,
CONF_SCALE, CONF_SCALE,
DATA_TYPE_FLOAT, DATA_TYPE_FLOAT,
DATA_TYPE_INT, DATA_TYPE_INT,
DATA_TYPE_UINT, DATA_TYPE_UINT,
DEFAULT_HUB,
MODBUS_DOMAIN,
) )
from homeassistant.const import CONF_NAME, CONF_PLATFORM, CONF_SCAN_INTERVAL from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
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 from .conftest import run_test
_LOGGER = logging.getLogger(__name__)
@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()
async def test_simple_word_register(hass, mock_hub): 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_OFFSET: 0,
CONF_PRECISION: 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): async def test_optional_conf_keys(hass, mock_hub):
"""Test handling of optional configuration keys.""" """Test handling of optional configuration keys."""
register_config = {} register_config = {}
await run_test( 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_OFFSET: 13,
CONF_PRECISION: 0, 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): 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_OFFSET: 13,
CONF_PRECISION: 0, 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): 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, CONF_PRECISION: 4,
} }
await run_test( 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_OFFSET: 0,
CONF_PRECISION: 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): 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_OFFSET: "5",
CONF_PRECISION: "1", 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): 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_OFFSET: 0,
CONF_PRECISION: 2, 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): 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_OFFSET: -10.3,
CONF_PRECISION: 1, 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): 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, hass,
mock_hub, mock_hub,
register_config, register_config,
SENSOR_DOMAIN,
register_words=[0x89AB, 0xCDEF], register_words=[0x89AB, 0xCDEF],
expected="-1985229329", expected="-1985229329",
) )
@ -222,6 +219,7 @@ async def test_unsigned_two_word_register(hass, mock_hub):
hass, hass,
mock_hub, mock_hub,
register_config, register_config,
SENSOR_DOMAIN,
register_words=[0x89AB, 0xCDEF], register_words=[0x89AB, 0xCDEF],
expected=str(0x89ABCDEF), expected=str(0x89ABCDEF),
) )
@ -238,6 +236,7 @@ async def test_reversed(hass, mock_hub):
hass, hass,
mock_hub, mock_hub,
register_config, register_config,
SENSOR_DOMAIN,
register_words=[0x89AB, 0xCDEF], register_words=[0x89AB, 0xCDEF],
expected=str(0xCDEF89AB), expected=str(0xCDEF89AB),
) )
@ -256,6 +255,7 @@ async def test_four_word_register(hass, mock_hub):
hass, hass,
mock_hub, mock_hub,
register_config, register_config,
SENSOR_DOMAIN,
register_words=[0x89AB, 0xCDEF, 0x0123, 0x4567], register_words=[0x89AB, 0xCDEF, 0x0123, 0x4567],
expected="9920249030613615975", expected="9920249030613615975",
) )
@ -274,6 +274,7 @@ async def test_four_word_register_precision_is_intact_with_int_params(hass, mock
hass, hass,
mock_hub, mock_hub,
register_config, register_config,
SENSOR_DOMAIN,
register_words=[0x0123, 0x4567, 0x89AB, 0xCDEF], register_words=[0x0123, 0x4567, 0x89AB, 0xCDEF],
expected="163971058432973793", expected="163971058432973793",
) )
@ -292,6 +293,7 @@ async def test_four_word_register_precision_is_lost_with_float_params(hass, mock
hass, hass,
mock_hub, mock_hub,
register_config, register_config,
SENSOR_DOMAIN,
register_words=[0x0123, 0x4567, 0x89AB, 0xCDEF], register_words=[0x0123, 0x4567, 0x89AB, 0xCDEF],
expected="163971058432973792", expected="163971058432973792",
) )
@ -311,6 +313,7 @@ async def test_two_word_input_register(hass, mock_hub):
hass, hass,
mock_hub, mock_hub,
register_config, register_config,
SENSOR_DOMAIN,
register_words=[0x89AB, 0xCDEF], register_words=[0x89AB, 0xCDEF],
expected=str(0x89ABCDEF), expected=str(0x89ABCDEF),
) )
@ -330,6 +333,7 @@ async def test_two_word_holding_register(hass, mock_hub):
hass, hass,
mock_hub, mock_hub,
register_config, register_config,
SENSOR_DOMAIN,
register_words=[0x89AB, 0xCDEF], register_words=[0x89AB, 0xCDEF],
expected=str(0x89ABCDEF), expected=str(0x89ABCDEF),
) )
@ -349,6 +353,7 @@ async def test_float_data_type(hass, mock_hub):
hass, hass,
mock_hub, mock_hub,
register_config, register_config,
SENSOR_DOMAIN,
register_words=[16286, 1617], register_words=[16286, 1617],
expected="1.23457", expected="1.23457",
) )

View file

@ -294,6 +294,58 @@ async def test_update(hass):
assert "three" == state.attributes[ATTR_INPUT_SOURCE] 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): async def test_supported_features(hass):
"""Test supported features property.""" """Test supported features property."""
await _setup_monoprice(hass, MockMonoprice()) 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, CONF_USE_EPISODE_ART,
DOMAIN, DOMAIN,
PLEX_SERVER_CONFIG, PLEX_SERVER_CONFIG,
PLEX_UPDATE_PLATFORMS_SIGNAL,
SERVERS, SERVERS,
) )
from homeassistant.config_entries import ENTRY_STATE_LOADED from homeassistant.config_entries import ENTRY_STATE_LOADED
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TOKEN, CONF_URL 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 homeassistant.setup import async_setup_component
from .common import trigger_plex_update
from .const import DEFAULT_DATA, DEFAULT_OPTIONS, MOCK_SERVERS, MOCK_TOKEN from .const import DEFAULT_DATA, DEFAULT_OPTIONS, MOCK_SERVERS, MOCK_TOKEN
from .mock_classes import MockPlexAccount, MockPlexServer 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 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 monitored_users = hass.data[DOMAIN][SERVERS][server_id].option_monitored_users

View file

@ -3,8 +3,9 @@ import copy
from datetime import timedelta from datetime import timedelta
import ssl import ssl
from asynctest import patch from asynctest import ClockedTestCase, patch
import plexapi import plexapi
import pytest
import requests import requests
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
@ -23,14 +24,19 @@ from homeassistant.const import (
CONF_URL, CONF_URL,
CONF_VERIFY_SSL, CONF_VERIFY_SSL,
) )
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util 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 .const import DEFAULT_DATA, DEFAULT_OPTIONS, MOCK_SERVERS, MOCK_TOKEN
from .mock_classes import MockPlexAccount, MockPlexServer 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): 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 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): @pytest.fixture(autouse=True)
"""Test setup component with config.""" 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( async def tearDown(self):
domain=const.DOMAIN, """Clean up the HomeAssistant instance."""
data=DEFAULT_DATA, await self.hass.async_stop()
options=DEFAULT_OPTIONS, self.mock_storage.__exit__(None, None, None)
unique_id=DEFAULT_DATA["server_id"],
)
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( async def test_setup_with_config_entry(self):
"homeassistant.components.plex.PlexWebsocket.listen" """Test setup component with config."""
) as mock_listen: hass = self.hass
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id) 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() 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 # Ensure existing entities refresh
assert entry.state == ENTRY_STATE_LOADED await self.advance(const.DEBOUNCE_TIMEOUT)
async_dispatcher_send(
server_id = mock_plex_server.machineIdentifier hass, const.PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)
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
) )
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): 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 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: with patch("homeassistant.components.plex.PlexWebsocket.close") as mock_close:
await hass.config_entries.async_unload(entry.entry_id) await hass.config_entries.async_unload(entry.entry_id)
assert mock_close.called assert mock_close.called
assert entry.state == ENTRY_STATE_NOT_LOADED 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): async def test_setup_with_photo_session(hass):
"""Test setup component with config.""" """Test setup component with config."""
@ -292,7 +308,8 @@ async def test_setup_with_photo_session(hass):
server_id = mock_plex_server.machineIdentifier 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") media_player = hass.states.get("media_player.plex_product_title")
assert media_player.state == "idle" assert media_player.state == "idle"

View file

@ -1,8 +1,7 @@
"""Tests for Plex server.""" """Tests for Plex server."""
import copy 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.media_player import DOMAIN as MP_DOMAIN
from homeassistant.components.plex.const import ( from homeassistant.components.plex.const import (
@ -14,13 +13,11 @@ from homeassistant.components.plex.const import (
SERVERS, SERVERS,
) )
from homeassistant.helpers.dispatcher import async_dispatcher_send 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 .const import DEFAULT_DATA, DEFAULT_OPTIONS
from .mock_classes import MockPlexServer 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): async def test_new_users_available(hass):
@ -48,7 +45,8 @@ async def test_new_users_available(hass):
server_id = mock_plex_server.machineIdentifier 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 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 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 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)) assert sensor.state == str(len(mock_plex_server.accounts))
async def test_mark_sessions_idle(hass): class TestClockedPlex(ClockedTestCase):
"""Test marking media_players as idle when sessions end.""" """Create clock-controlled asynctest class."""
entry = MockConfigEntry(
domain=DOMAIN,
data=DEFAULT_DATA,
options=DEFAULT_OPTIONS,
unique_id=DEFAULT_DATA["server_id"],
)
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( async def tearDown(self):
"homeassistant.components.plex.PlexWebsocket.listen" """Clean up the HomeAssistant instance."""
): await self.hass.async_stop()
entry.add_to_hass(hass) self.mock_storage.__exit__(None, None, None)
assert await hass.config_entries.async_setup(entry.entry_id)
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() 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") await self.advance(DEBOUNCE_TIMEOUT)
assert sensor.state == str(len(mock_plex_server.accounts)) async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
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 hass.async_block_till_done() 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 def test_debouncer(self):
async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) """Test debouncer behavior."""
await hass.async_block_till_done() hass = self.hass
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()
next_update = dt_util.utcnow() + timedelta(seconds=DEBOUNCE_TIMEOUT) entry = MockConfigEntry(
async_fire_time_changed(hass, next_update) domain=DOMAIN,
await hass.async_block_till_done() data=DEFAULT_DATA,
options=DEFAULT_OPTIONS,
unique_id=DEFAULT_DATA["server_id"],
)
assert ( mock_plex_server = MockPlexServer(config_entry=entry)
caplog.text.count(f"Throttling update of {mock_plex_server.friendlyName}") == 2
) 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.""" """Tests for light platform."""
from typing import Callable, NamedTuple from typing import Callable, NamedTuple
from unittest.mock import Mock, patch from unittest.mock import Mock, PropertyMock, patch
from pyHS100 import SmartDeviceException from pyHS100 import SmartDeviceException
import pytest import pytest
@ -16,7 +16,11 @@ from homeassistant.components.light import (
ATTR_HS_COLOR, ATTR_HS_COLOR,
DOMAIN as LIGHT_DOMAIN, 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 ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
CONF_HOST, CONF_HOST,
@ -41,6 +45,15 @@ class LightMockData(NamedTuple):
get_emeter_monthly_mock: Mock 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") @pytest.fixture(name="light_mock_data")
def light_mock_data_fixture() -> None: def light_mock_data_fixture() -> None:
"""Create light mock data.""" """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: async def update_entity(hass: HomeAssistant, entity_id: str) -> None:
"""Run an update action for an entity.""" """Run an update action for an entity."""
await hass.services.async_call( 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() 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: async def test_light(hass: HomeAssistant, light_mock_data: LightMockData) -> None:
"""Test function.""" """Test function."""
light_state = light_mock_data.light_state light_state = light_mock_data.light_state