commit
0727c7b9e8
29 changed files with 715 additions and 335 deletions
|
@ -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(
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
@ -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":
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
@ -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": []
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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."],
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
90
tests/components/modbus/conftest.py
Normal file
90
tests/components/modbus/conftest.py
Normal 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
|
|
@ -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",
|
||||||
)
|
)
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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()
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue