Compare commits

..

19 Commits

Author SHA1 Message Date
Paulus Schoutsen
9b757e4c22 Merge pull request #34517 from home-assistant/rc 2020-04-21 17:26:36 -07:00
Paulus Schoutsen
61620dec30 Bumped version to 0.108.7 2020-04-21 16:27:13 -07:00
Pascal Vizeli
12e92d504d Fix cloud binary_sensor & TTS/STT (#34505) 2020-04-21 16:26:54 -07:00
ochlocracy
16e1b3772c Modify requirements for CameraStreamController in Alexa (#34470) 2020-04-21 16:26:53 -07:00
J. Nick Koston
59686274d7 Ensure nexia state file is in a writable location (#34325)
* bump nexia to 0.9.2
2020-04-21 16:26:52 -07:00
jan iversen
945567150d Rollback modbus to version 0.107.7 keep new functionality (#34287)
* Rollback modbus to version 0.107.7

Update manifest to not use async.

Rollback entities to sync version.

Keep newer modifications apart from async.

Rollback __init__ to sync version but keep the new functionality.

add async sub directory

Adding the current (not working) version in a sub directory,
to allow easy sharing with a few alfa testers.

The async version are to be updated to use the serial/tcp already
available instead of the flaky pymodbus version. pymodbus is still
needed to encode/decode the messagess.

Update test cases to reflect sync implementation, but
keep the new functionality like e.g. conftest.py.

* do not publish async version

The async version will be made available in a forked repo, until
it is ready to replace the production code.
2020-04-21 16:26:00 -07:00
jan iversen
eedfca6623 Fix modbus sync/async issues (#34043)
* add pyserial to manifest

pymodbus is very developer oriented and assumes every developer
adapt the requierements.txt to his/hers needs.

Our requirements.txt is different it contains all posibilities allowing
user to later change configuration without having to install extra
packages.

As a consequence manifest.json needs to include the pyserial.

* modbus: make truly async client creation

Make hass call listen_once async.
Integrate content of start_modbus into async_setup.

Do not use the boiler plate create tcp client function from
pymodbus as it is sync, and also does not work well with asyncio,
instead call the init_<type> directly, since that is async.

* both component/modbus and component/serial uses pyserial-async
but with slighty different version requirements.

Combined the 2.

* Review 1

* Review 2

* Review

@staticmethod is no good, because the function uses class variables.

* Review

Pytest is sometimes a bit cryptic, lets hope this does it.
2020-04-21 16:25:26 -07:00
Paulus Schoutsen
41e4dc4336 Cloud do checks during setup (#33507)
* Update cloud to do more tasks during async_setup

* Upgrade hass_nabucasa to 0.33
2020-04-21 16:14:18 -07:00
J. Nick Koston
5635cdb77c Fix setting up multiple UPSes with NUT in 0.108.x (#34427) 2020-04-21 10:47:31 -07:00
Paulus Schoutsen
6e86cbf947 Merge pull request #34323 from home-assistant/rc 2020-04-16 19:21:01 -07:00
Paulus Schoutsen
334fecdf6f Bumped version to 0.108.6 2020-04-16 17:46:39 -07:00
Paulus Schoutsen
317d08b6c1 Update Coordinator: Only schedule a refresh if listenerrs (#34317) 2020-04-16 17:46:23 -07:00
Raman Gupta
059df5e3e2 Abort vizio zeroconf config flow if unique ID is already configured (#34313) 2020-04-16 17:45:26 -07:00
Anders Melchiorsen
de440cf579 Fixes for Sonos media titles (#34311) 2020-04-16 17:45:25 -07:00
Bas Nijholt
81d006499e Pass an argument to kef.update_dsp for async_track_time_interval (#34310) 2020-04-16 17:45:24 -07:00
Daniel Høyer Iversen
0a1e33b7e1 Upgrade broadlink lib to 0.13.1 (#34290) 2020-04-16 17:45:06 -07:00
Raman Gupta
5157c2d10c Skip ignored hosts when checking existing config entries in config flow (#34280) 2020-04-16 17:44:43 -07:00
J. Nick Koston
8a8cbeb4c0 Fix default elkm1 temp units (#34274) 2020-04-16 17:44:42 -07:00
J. Nick Koston
de6fc53ca5 Use config entry id for unique id if serial number is missing (#34154) 2020-04-16 17:44:16 -07:00
36 changed files with 399 additions and 285 deletions

View File

@@ -791,19 +791,18 @@ class CameraCapabilities(AlexaEntity):
yield Alexa(self.hass)
def _check_requirements(self):
"""Check the hass URL for HTTPS scheme and port 443."""
"""Check the hass URL for HTTPS scheme."""
if "stream" not in self.hass.config.components:
_LOGGER.error(
_LOGGER.debug(
"%s requires stream component for AlexaCameraStreamController",
self.entity_id,
)
return False
url = urlparse(network.async_get_external_url(self.hass))
if url.scheme != "https" or (url.port is not None and url.port != 443):
_LOGGER.error(
"%s requires HTTPS support on port 443 for AlexaCameraStreamController",
self.entity_id,
if url.scheme != "https":
_LOGGER.debug(
"%s requires HTTPS for AlexaCameraStreamController", self.entity_id
)
return False

View File

@@ -2,7 +2,6 @@
"domain": "broadlink",
"name": "Broadlink",
"documentation": "https://www.home-assistant.io/integrations/broadlink",
"requirements": ["broadlink==0.13.0"],
"dependencies": [],
"requirements": ["broadlink==0.13.1"],
"codeowners": ["@danielhiversen", "@felipediel"]
}

View File

@@ -10,7 +10,6 @@ from homeassistant.const import (
CONF_MODE,
CONF_NAME,
CONF_REGION,
EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP,
)
from homeassistant.core import callback
@@ -191,12 +190,6 @@ async def async_setup(hass, config):
client = CloudClient(hass, prefs, websession, alexa_conf, google_conf)
cloud = hass.data[DOMAIN] = Cloud(client, **kwargs)
async def _startup(event):
"""Startup event."""
await cloud.start()
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, _startup)
async def _shutdown(event):
"""Shutdown event."""
await cloud.stop()
@@ -230,20 +223,15 @@ async def async_setup(hass, config):
return
loaded = True
hass.async_create_task(
hass.helpers.discovery.async_load_platform(
"binary_sensor", DOMAIN, {}, config
)
)
hass.async_create_task(
hass.helpers.discovery.async_load_platform("stt", DOMAIN, {}, config)
)
hass.async_create_task(
hass.helpers.discovery.async_load_platform("tts", DOMAIN, {}, config)
await hass.helpers.discovery.async_load_platform(
"binary_sensor", DOMAIN, {}, config
)
await hass.helpers.discovery.async_load_platform("stt", DOMAIN, {}, config)
await hass.helpers.discovery.async_load_platform("tts", DOMAIN, {}, config)
cloud.iot.register_on_connect(_on_connect)
await cloud.start()
await http_api.async_setup(hass)
account_link.async_setup(hass)

View File

@@ -2,7 +2,7 @@
"domain": "cloud",
"name": "Home Assistant Cloud",
"documentation": "https://www.home-assistant.io/integrations/cloud",
"requirements": ["hass-nabucasa==0.32.2"],
"requirements": ["hass-nabucasa==0.33.0"],
"dependencies": ["http", "webhook", "alexa"],
"after_dependencies": ["google_assistant"],
"codeowners": ["@home-assistant/cloud"]

View File

@@ -15,6 +15,8 @@ from homeassistant.const import (
CONF_PASSWORD,
CONF_TEMPERATURE_UNIT,
CONF_USERNAME,
TEMP_CELSIUS,
TEMP_FAHRENHEIT,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady
@@ -23,6 +25,8 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import ConfigType
from .const import (
BARE_TEMP_CELSIUS,
BARE_TEMP_FAHRENHEIT,
CONF_AREA,
CONF_AUTO_CONFIGURE,
CONF_COUNTER,
@@ -119,7 +123,10 @@ DEVICE_SCHEMA = vol.Schema(
vol.Optional(CONF_USERNAME, default=""): cv.string,
vol.Optional(CONF_PASSWORD, default=""): cv.string,
vol.Optional(CONF_AUTO_CONFIGURE, default=False): cv.boolean,
vol.Optional(CONF_TEMPERATURE_UNIT, default="F"): cv.temperature_unit,
# cv.temperature_unit will mutate 'C' -> '°C' and 'F' -> '°F'
vol.Optional(
CONF_TEMPERATURE_UNIT, default=BARE_TEMP_FAHRENHEIT
): cv.temperature_unit,
vol.Optional(CONF_AREA, default={}): DEVICE_SCHEMA_SUBDOMAIN,
vol.Optional(CONF_COUNTER, default={}): DEVICE_SCHEMA_SUBDOMAIN,
vol.Optional(CONF_KEYPAD, default={}): DEVICE_SCHEMA_SUBDOMAIN,
@@ -187,7 +194,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
_LOGGER.debug("Setting up elkm1 %s", conf["host"])
config = {"temperature_unit": conf[CONF_TEMPERATURE_UNIT]}
temperature_unit = TEMP_FAHRENHEIT
if conf[CONF_TEMPERATURE_UNIT] in (BARE_TEMP_CELSIUS, TEMP_CELSIUS):
temperature_unit = TEMP_CELSIUS
config = {"temperature_unit": temperature_unit}
if not conf[CONF_AUTO_CONFIGURE]:
# With elkm1-lib==0.7.16 and later auto configure is available

View File

@@ -14,7 +14,7 @@ from homeassistant.components.climate.const import (
SUPPORT_FAN_MODE,
SUPPORT_TARGET_TEMPERATURE_RANGE,
)
from homeassistant.const import PRECISION_WHOLE, STATE_ON, TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.const import PRECISION_WHOLE, STATE_ON
from . import ElkEntity, create_elk_entities
from .const import DOMAIN
@@ -55,7 +55,7 @@ class ElkThermostat(ElkEntity, ClimateDevice):
@property
def temperature_unit(self):
"""Return the temperature unit."""
return TEMP_FAHRENHEIT if self._temperature_unit == "F" else TEMP_CELSIUS
return self._temperature_unit
@property
def current_temperature(self):

View File

@@ -13,6 +13,8 @@ from homeassistant.const import (
CONF_PROTOCOL,
CONF_TEMPERATURE_UNIT,
CONF_USERNAME,
TEMP_CELSIUS,
TEMP_FAHRENHEIT,
)
from homeassistant.util import slugify
@@ -33,7 +35,9 @@ DATA_SCHEMA = vol.Schema(
vol.Optional(CONF_USERNAME, default=""): str,
vol.Optional(CONF_PASSWORD, default=""): str,
vol.Optional(CONF_PREFIX, default=""): str,
vol.Optional(CONF_TEMPERATURE_UNIT, default="F"): vol.In(["F", "C"]),
vol.Optional(CONF_TEMPERATURE_UNIT, default=TEMP_FAHRENHEIT): vol.In(
[TEMP_FAHRENHEIT, TEMP_CELSIUS]
),
}
)

View File

@@ -18,6 +18,9 @@ CONF_ZONE = "zone"
CONF_PREFIX = "prefix"
BARE_TEMP_FAHRENHEIT = "F"
BARE_TEMP_CELSIUS = "C"
ELK_ELEMENTS = {
CONF_AREA: Max.AREAS.value,
CONF_COUNTER: Max.COUNTERS.value,

View File

@@ -357,7 +357,7 @@ class KefMediaPlayer(MediaPlayerDevice):
"""Send next track command."""
await self._speaker.next_track()
async def update_dsp(self) -> None:
async def update_dsp(self, _=None) -> None:
"""Update the DSP settings."""
if self._speaker_type == "LS50" and self._state == STATE_OFF:
# The LSX is able to respond when off the LS50 has to be on.

View File

@@ -1,14 +1,8 @@
"""Support for Modbus."""
import asyncio
import logging
import threading
from async_timeout import timeout
from pymodbus.client.asynchronous import schedulers
from pymodbus.client.asynchronous.serial import AsyncModbusSerialClient as ClientSerial
from pymodbus.client.asynchronous.tcp import AsyncModbusTCPClient as ClientTCP
from pymodbus.client.asynchronous.udp import AsyncModbusUDPClient as ClientUDP
from pymodbus.exceptions import ModbusException
from pymodbus.pdu import ExceptionResponse
from pymodbus.client.sync import ModbusSerialClient, ModbusTcpClient, ModbusUdpClient
from pymodbus.transaction import ModbusRtuFramer
import voluptuous as vol
@@ -42,6 +36,7 @@ from .const import (
_LOGGER = logging.getLogger(__name__)
BASE_SCHEMA = vol.Schema({vol.Optional(CONF_NAME, default=DEFAULT_HUB): cv.string})
SERIAL_SCHEMA = BASE_SCHEMA.extend(
@@ -93,60 +88,55 @@ SERVICE_WRITE_COIL_SCHEMA = vol.Schema(
)
async def async_setup(hass, config):
def setup(hass, config):
"""Set up Modbus component."""
hass.data[DOMAIN] = hub_collect = {}
for client_config in config[DOMAIN]:
hub_collect[client_config[CONF_NAME]] = ModbusHub(client_config, hass.loop)
hub_collect[client_config[CONF_NAME]] = ModbusHub(client_config)
def stop_modbus(event):
"""Stop Modbus service."""
for client in hub_collect.values():
del client
client.close()
def start_modbus():
"""Start Modbus service."""
for client in hub_collect.values():
client.setup()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_modbus)
async def write_register(service):
def write_register(service):
"""Write Modbus registers."""
unit = int(float(service.data[ATTR_UNIT]))
address = int(float(service.data[ATTR_ADDRESS]))
value = service.data[ATTR_VALUE]
client_name = service.data[ATTR_HUB]
if isinstance(value, list):
await hub_collect[client_name].write_registers(
hub_collect[client_name].write_registers(
unit, address, [int(float(i)) for i in value]
)
else:
await hub_collect[client_name].write_register(
unit, address, int(float(value))
)
hub_collect[client_name].write_register(unit, address, int(float(value)))
async def write_coil(service):
def write_coil(service):
"""Write Modbus coil."""
unit = service.data[ATTR_UNIT]
address = service.data[ATTR_ADDRESS]
state = service.data[ATTR_STATE]
client_name = service.data[ATTR_HUB]
await hub_collect[client_name].write_coil(unit, address, state)
hub_collect[client_name].write_coil(unit, address, state)
# do not wait for EVENT_HOMEASSISTANT_START, activate pymodbus now
await hass.async_add_executor_job(start_modbus)
for client in hub_collect.values():
client.setup()
# register function to gracefully stop modbus
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_modbus)
# Register services for modbus
hass.services.async_register(
hass.services.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,
hass.services.register(
DOMAIN, SERVICE_WRITE_COIL, write_coil, schema=SERVICE_WRITE_COIL_SCHEMA
)
return True
@@ -154,13 +144,12 @@ async def async_setup(hass, config):
class ModbusHub:
"""Thread safe wrapper class for pymodbus."""
def __init__(self, client_config, main_loop):
def __init__(self, client_config):
"""Initialize the Modbus hub."""
# generic configuration
self._loop = main_loop
self._client = None
self._lock = asyncio.Lock()
self._lock = threading.Lock()
self._config_name = client_config[CONF_NAME]
self._config_type = client_config[CONF_TYPE]
self._config_port = client_config[CONF_PORT]
@@ -178,136 +167,101 @@ class ModbusHub:
# network configuration
self._config_host = client_config[CONF_HOST]
self._config_delay = client_config[CONF_DELAY]
if self._config_delay > 0:
_LOGGER.warning(
"Parameter delay is accepted but not used in this version"
)
@property
def name(self):
"""Return the name of this hub."""
return self._config_name
async def _connect_delay(self):
if self._config_delay > 0:
await asyncio.sleep(self._config_delay)
self._config_delay = 0
def setup(self):
"""Set up pymodbus client."""
# pylint: disable = E0633
# Client* do deliver loop, client as result but
# pylint does not accept that fact
if self._config_type == "serial":
_, self._client = ClientSerial(
schedulers.ASYNC_IO,
self._client = ModbusSerialClient(
method=self._config_method,
port=self._config_port,
baudrate=self._config_baudrate,
stopbits=self._config_stopbits,
bytesize=self._config_bytesize,
parity=self._config_parity,
loop=self._loop,
timeout=self._config_timeout,
)
elif self._config_type == "rtuovertcp":
_, self._client = ClientTCP(
schedulers.ASYNC_IO,
self._client = ModbusTcpClient(
host=self._config_host,
port=self._config_port,
framer=ModbusRtuFramer,
timeout=self._config_timeout,
loop=self._loop,
)
elif self._config_type == "tcp":
_, self._client = ClientTCP(
schedulers.ASYNC_IO,
self._client = ModbusTcpClient(
host=self._config_host,
port=self._config_port,
timeout=self._config_timeout,
loop=self._loop,
)
elif self._config_type == "udp":
_, self._client = ClientUDP(
schedulers.ASYNC_IO,
self._client = ModbusUdpClient(
host=self._config_host,
port=self._config_port,
timeout=self._config_timeout,
loop=self._loop,
)
else:
assert False
async def _read(self, unit, address, count, func):
"""Read generic with error handling."""
await self._connect_delay()
async with self._lock:
kwargs = {"unit": unit} if unit else {}
try:
async with timeout(self._config_timeout):
result = await func(address, count, **kwargs)
except asyncio.TimeoutError:
result = None
# Connect device
self.connect()
if isinstance(result, (ModbusException, ExceptionResponse)):
_LOGGER.error("Hub %s Exception (%s)", self._config_name, result)
return result
def close(self):
"""Disconnect client."""
with self._lock:
self._client.close()
async def _write(self, unit, address, value, func):
"""Read generic with error handling."""
await self._connect_delay()
async with self._lock:
kwargs = {"unit": unit} if unit else {}
try:
async with timeout(self._config_timeout):
func(address, value, **kwargs)
except asyncio.TimeoutError:
return
def connect(self):
"""Connect client."""
with self._lock:
self._client.connect()
async def read_coils(self, unit, address, count):
def read_coils(self, unit, address, count):
"""Read coils."""
if self._client.protocol is None:
return None
return await self._read(unit, address, count, self._client.protocol.read_coils)
with self._lock:
kwargs = {"unit": unit} if unit else {}
return self._client.read_coils(address, count, **kwargs)
async def read_discrete_inputs(self, unit, address, count):
def read_discrete_inputs(self, unit, address, count):
"""Read discrete inputs."""
if self._client.protocol is None:
return None
return await self._read(
unit, address, count, self._client.protocol.read_discrete_inputs
)
with self._lock:
kwargs = {"unit": unit} if unit else {}
return self._client.read_discrete_inputs(address, count, **kwargs)
async def read_input_registers(self, unit, address, count):
def read_input_registers(self, unit, address, count):
"""Read input registers."""
if self._client.protocol is None:
return None
return await self._read(
unit, address, count, self._client.protocol.read_input_registers
)
with self._lock:
kwargs = {"unit": unit} if unit else {}
return self._client.read_input_registers(address, count, **kwargs)
async def read_holding_registers(self, unit, address, count):
def read_holding_registers(self, unit, address, count):
"""Read holding registers."""
if self._client.protocol is None:
return None
return await self._read(
unit, address, count, self._client.protocol.read_holding_registers
)
with self._lock:
kwargs = {"unit": unit} if unit else {}
return self._client.read_holding_registers(address, count, **kwargs)
async def write_coil(self, unit, address, value):
def write_coil(self, unit, address, value):
"""Write coil."""
if self._client.protocol is None:
return None
return await self._write(unit, address, value, self._client.protocol.write_coil)
with self._lock:
kwargs = {"unit": unit} if unit else {}
self._client.write_coil(address, value, **kwargs)
async def write_register(self, unit, address, value):
def write_register(self, unit, address, value):
"""Write register."""
if self._client.protocol is None:
return None
return await self._write(
unit, address, value, self._client.protocol.write_register
)
with self._lock:
kwargs = {"unit": unit} if unit else {}
self._client.write_register(address, value, **kwargs)
async def write_registers(self, unit, address, values):
def write_registers(self, unit, address, values):
"""Write registers."""
if self._client.protocol is None:
return None
return await self._write(
unit, address, values, self._client.protocol.write_registers
)
with self._lock:
kwargs = {"unit": unit} if unit else {}
self._client.write_registers(address, values, **kwargs)

View File

@@ -2,7 +2,7 @@
import logging
from typing import Optional
from pymodbus.exceptions import ModbusException
from pymodbus.exceptions import ConnectionException, ModbusException
from pymodbus.pdu import ExceptionResponse
import voluptuous as vol
@@ -54,7 +54,7 @@ PLATFORM_SCHEMA = vol.All(
)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Modbus binary sensors."""
sensors = []
for entry in config[CONF_INPUTS]:
@@ -70,7 +70,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
)
)
async_add_entities(sensors)
add_entities(sensors)
class ModbusBinarySensor(BinarySensorDevice):
@@ -107,15 +107,17 @@ class ModbusBinarySensor(BinarySensorDevice):
"""Return True if entity is available."""
return self._available
async def async_update(self):
def update(self):
"""Update the state of the sensor."""
if self._input_type == CALL_TYPE_COIL:
result = await self._hub.read_coils(self._slave, self._address, 1)
else:
result = await self._hub.read_discrete_inputs(self._slave, self._address, 1)
if result is None:
try:
if self._input_type == CALL_TYPE_COIL:
result = self._hub.read_coils(self._slave, self._address, 1)
else:
result = self._hub.read_discrete_inputs(self._slave, self._address, 1)
except ConnectionException:
self._available = False
return
if isinstance(result, (ModbusException, ExceptionResponse)):
self._available = False
return

View File

@@ -3,7 +3,7 @@ import logging
import struct
from typing import Optional
from pymodbus.exceptions import ModbusException
from pymodbus.exceptions import ConnectionException, ModbusException
from pymodbus.pdu import ExceptionResponse
import voluptuous as vol
@@ -72,7 +72,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Modbus Thermostat Platform."""
name = config[CONF_NAME]
modbus_slave = config[CONF_SLAVE]
@@ -91,7 +91,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
hub_name = config[CONF_HUB]
hub = hass.data[MODBUS_DOMAIN][hub_name]
async_add_entities(
add_entities(
[
ModbusThermostat(
hub,
@@ -170,12 +170,12 @@ class ModbusThermostat(ClimateDevice):
"""Return the list of supported features."""
return SUPPORT_TARGET_TEMPERATURE
async def async_update(self):
def update(self):
"""Update Target & Current Temperature."""
self._target_temperature = await self._read_register(
self._target_temperature = self._read_register(
CALL_TYPE_REGISTER_HOLDING, self._target_temperature_register
)
self._current_temperature = await self._read_register(
self._current_temperature = self._read_register(
self._current_temperature_register_type, self._current_temperature_register
)
@@ -224,7 +224,7 @@ class ModbusThermostat(ClimateDevice):
"""Return the supported step of target temperature."""
return self._temp_step
async def set_temperature(self, **kwargs):
def set_temperature(self, **kwargs):
"""Set new target temperature."""
target_temperature = int(
(kwargs.get(ATTR_TEMPERATURE) - self._offset) / self._scale
@@ -233,26 +233,28 @@ class ModbusThermostat(ClimateDevice):
return
byte_string = struct.pack(self._structure, target_temperature)
register_value = struct.unpack(">h", byte_string[0:2])[0]
await self._write_register(self._target_temperature_register, register_value)
self._write_register(self._target_temperature_register, register_value)
@property
def available(self) -> bool:
"""Return True if entity is available."""
return self._available
async def _read_register(self, register_type, register) -> Optional[float]:
def _read_register(self, register_type, register) -> Optional[float]:
"""Read register using the Modbus hub slave."""
if register_type == CALL_TYPE_REGISTER_INPUT:
result = await self._hub.read_input_registers(
self._slave, register, self._count
)
else:
result = await self._hub.read_holding_registers(
self._slave, register, self._count
)
if result is None:
try:
if register_type == CALL_TYPE_REGISTER_INPUT:
result = self._hub.read_input_registers(
self._slave, register, self._count
)
else:
result = self._hub.read_holding_registers(
self._slave, register, self._count
)
except ConnectionException:
self._available = False
return
if isinstance(result, (ModbusException, ExceptionResponse)):
self._available = False
return
@@ -269,7 +271,12 @@ class ModbusThermostat(ClimateDevice):
return register_value
async def _write_register(self, register, value):
def _write_register(self, register, value):
"""Write holding register using the Modbus hub slave."""
await self._hub.write_registers(self._slave, register, [value, 0])
try:
self._hub.write_registers(self._slave, register, [value, 0])
except ConnectionException:
self._available = False
return
self._available = True

View File

@@ -3,6 +3,5 @@
"name": "Modbus",
"documentation": "https://www.home-assistant.io/integrations/modbus",
"requirements": ["pymodbus==2.3.0"],
"dependencies": [],
"codeowners": ["@adamchengtkc", "@janiversen"]
}

View File

@@ -3,7 +3,7 @@ import logging
import struct
from typing import Any, Optional, Union
from pymodbus.exceptions import ModbusException
from pymodbus.exceptions import ConnectionException, ModbusException
from pymodbus.pdu import ExceptionResponse
import voluptuous as vol
@@ -89,7 +89,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Modbus sensors."""
sensors = []
data_types = {DATA_TYPE_INT: {1: "h", 2: "i", 4: "q"}}
@@ -148,7 +148,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
if not sensors:
return False
async_add_entities(sensors)
add_entities(sensors)
class ModbusRegisterSensor(RestoreEntity):
@@ -219,19 +219,21 @@ class ModbusRegisterSensor(RestoreEntity):
"""Return True if entity is available."""
return self._available
async def async_update(self):
def update(self):
"""Update the state of the sensor."""
if self._register_type == CALL_TYPE_REGISTER_INPUT:
result = await self._hub.read_input_registers(
self._slave, self._register, self._count
)
else:
result = await self._hub.read_holding_registers(
self._slave, self._register, self._count
)
if result is None:
try:
if self._register_type == CALL_TYPE_REGISTER_INPUT:
result = self._hub.read_input_registers(
self._slave, self._register, self._count
)
else:
result = self._hub.read_holding_registers(
self._slave, self._register, self._count
)
except ConnectionException:
self._available = False
return
if isinstance(result, (ModbusException, ExceptionResponse)):
self._available = False
return

View File

@@ -2,7 +2,7 @@
import logging
from typing import Optional
from pymodbus.exceptions import ModbusException
from pymodbus.exceptions import ConnectionException, ModbusException
from pymodbus.pdu import ExceptionResponse
import voluptuous as vol
@@ -76,7 +76,7 @@ PLATFORM_SCHEMA = vol.All(
)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Read configuration and create Modbus devices."""
switches = []
if CONF_COILS in config:
@@ -109,7 +109,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
)
)
async_add_entities(switches)
add_entities(switches)
class ModbusCoilSwitch(ToggleEntity, RestoreEntity):
@@ -146,24 +146,26 @@ class ModbusCoilSwitch(ToggleEntity, RestoreEntity):
"""Return True if entity is available."""
return self._available
async def turn_on(self, **kwargs):
def turn_on(self, **kwargs):
"""Set switch on."""
await self._write_coil(self._coil, True)
self._write_coil(self._coil, True)
async def turn_off(self, **kwargs):
def turn_off(self, **kwargs):
"""Set switch off."""
await self._write_coil(self._coil, False)
self._write_coil(self._coil, False)
async def async_update(self):
def update(self):
"""Update the state of the switch."""
self._is_on = await self._read_coil(self._coil)
self._is_on = self._read_coil(self._coil)
async def _read_coil(self, coil) -> Optional[bool]:
def _read_coil(self, coil) -> Optional[bool]:
"""Read coil using the Modbus hub slave."""
result = await self._hub.read_coils(self._slave, coil, 1)
if result is None:
try:
result = self._hub.read_coils(self._slave, coil, 1)
except ConnectionException:
self._available = False
return
if isinstance(result, (ModbusException, ExceptionResponse)):
self._available = False
return
@@ -173,9 +175,14 @@ class ModbusCoilSwitch(ToggleEntity, RestoreEntity):
return value
async def _write_coil(self, coil, value):
def _write_coil(self, coil, value):
"""Write coil using the Modbus hub slave."""
await self._hub.write_coil(self._slave, coil, value)
try:
self._hub.write_coil(self._slave, coil, value)
except ConnectionException:
self._available = False
return
self._available = True
@@ -221,21 +228,21 @@ class ModbusRegisterSwitch(ModbusCoilSwitch):
self._is_on = None
async def turn_on(self, **kwargs):
def turn_on(self, **kwargs):
"""Set switch on."""
# Only holding register is writable
if self._register_type == CALL_TYPE_REGISTER_HOLDING:
await self._write_register(self._command_on)
self._write_register(self._command_on)
if not self._verify_state:
self._is_on = True
async def turn_off(self, **kwargs):
def turn_off(self, **kwargs):
"""Set switch off."""
# Only holding register is writable
if self._register_type == CALL_TYPE_REGISTER_HOLDING:
await self._write_register(self._command_off)
self._write_register(self._command_off)
if not self._verify_state:
self._is_on = False
@@ -244,12 +251,12 @@ class ModbusRegisterSwitch(ModbusCoilSwitch):
"""Return True if entity is available."""
return self._available
async def async_update(self):
def update(self):
"""Update the state of the switch."""
if not self._verify_state:
return
value = await self._read_register()
value = self._read_register()
if value == self._state_on:
self._is_on = True
elif value == self._state_off:
@@ -263,18 +270,18 @@ class ModbusRegisterSwitch(ModbusCoilSwitch):
value,
)
async def _read_register(self) -> Optional[int]:
if self._register_type == CALL_TYPE_REGISTER_INPUT:
result = await self._hub.read_input_registers(
self._slave, self._register, 1
)
else:
result = await self._hub.read_holding_registers(
self._slave, self._register, 1
)
if result is None:
def _read_register(self) -> Optional[int]:
try:
if self._register_type == CALL_TYPE_REGISTER_INPUT:
result = self._hub.read_input_registers(self._slave, self._register, 1)
else:
result = self._hub.read_holding_registers(
self._slave, self._register, 1
)
except ConnectionException:
self._available = False
return
if isinstance(result, (ModbusException, ExceptionResponse)):
self._available = False
return
@@ -284,7 +291,12 @@ class ModbusRegisterSwitch(ModbusCoilSwitch):
return value
async def _write_register(self, value):
def _write_register(self, value):
"""Write holding register using the Modbus hub slave."""
await self._hub.write_register(self._slave, self._register, value)
try:
self._hub.write_register(self._slave, self._register, value)
except ConnectionException:
self._available = False
return
self._available = True

View File

@@ -60,6 +60,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
username = conf[CONF_USERNAME]
password = conf[CONF_PASSWORD]
state_file = hass.config.path(f"nexia_config_{username}.conf")
try:
nexia_home = await hass.async_add_executor_job(
partial(
@@ -67,6 +69,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
username=username,
password=password,
device_name=hass.config.location_name,
state_file=state_file,
)
)
except ConnectTimeout as ex:

View File

@@ -20,6 +20,8 @@ async def validate_input(hass: core.HomeAssistant, data):
Data has the keys from DATA_SCHEMA with values provided by the user.
"""
state_file = hass.config.path(f"nexia_config_{data[CONF_USERNAME]}.conf")
try:
nexia_home = NexiaHome(
username=data[CONF_USERNAME],
@@ -27,6 +29,7 @@ async def validate_input(hass: core.HomeAssistant, data):
auto_login=False,
auto_update=False,
device_name=hass.config.location_name,
state_file=state_file,
)
await hass.async_add_executor_job(nexia_home.login)
except ConnectTimeout as ex:

View File

@@ -1,7 +1,7 @@
{
"domain": "nexia",
"name": "Nexia",
"requirements": ["nexia==0.9.1"],
"requirements": ["nexia==0.9.2"],
"codeowners": ["@ryannazaretian", "@bdraco"],
"documentation": "https://www.home-assistant.io/integrations/nexia",
"config_flow": true

View File

@@ -58,10 +58,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
_LOGGER.debug("NUT Sensors Available: %s", status)
unique_id = _unique_id_from_status(status)
if unique_id is None:
unique_id = entry.entry_id
hass.data[DOMAIN][entry.entry_id] = {
PYNUT_DATA: data,
PYNUT_STATUS: status,
PYNUT_UNIQUE_ID: _unique_id_from_status(status),
PYNUT_UNIQUE_ID: unique_id,
PYNUT_MANUFACTURER: _manufacturer_from_status(status),
PYNUT_MODEL: _model_from_status(status),
PYNUT_FIRMWARE: _firmware_from_status(status),

View File

@@ -99,7 +99,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
def _host_port_alias_already_configured(self, host, port, alias):
"""See if we already have a nut entry matching user input configured."""
existing_host_port_aliases = {
_format_host_port_alias(host, port, alias)
_format_host_port_alias(
entry.data[CONF_HOST], entry.data[CONF_PORT], entry.data.get(CONF_ALIAS)
)
for entry in self._async_current_entries()
}
return _format_host_port_alias(host, port, alias) in existing_host_port_aliases

View File

@@ -419,7 +419,7 @@ class SonosEntity(MediaPlayerDevice):
if self._status in ("PAUSED_PLAYBACK", "STOPPED",):
# Sonos can consider itself "paused" but without having media loaded
# (happens if playing Spotify and via Spotify app you pick another device to play on)
if self._media_title is None:
if self.media_title is None:
return STATE_IDLE
return STATE_PAUSED
if self._status in ("PLAYING", "TRANSITIONING"):
@@ -614,12 +614,19 @@ class SonosEntity(MediaPlayerDevice):
except (TypeError, KeyError, AttributeError):
pass
# Radios without tagging can have part of the radio URI as title.
# Non-playing radios will not have a current title. In these cases we
# try to use the radio name instead.
# Non-playing radios will not have a current title. Radios without tagging
# can have part of the radio URI as title. In these cases we try to use the
# radio name instead.
try:
if self._media_title in self._uri or self.state != STATE_PLAYING:
self._media_title = variables["enqueued_transport_uri_meta_data"].title
uri_meta_data = variables["enqueued_transport_uri_meta_data"]
if isinstance(
uri_meta_data, pysonos.data_structures.DidlAudioBroadcast
) and (
self.state != STATE_PLAYING
or self.soco.is_radio_uri(self._media_title)
or self._media_title in self._uri
):
self._media_title = uri_meta_data.title
except (TypeError, KeyError, AttributeError):
pass

View File

@@ -8,7 +8,12 @@ import voluptuous as vol
from homeassistant import config_entries
from homeassistant.components.media_player import DEVICE_CLASS_SPEAKER, DEVICE_CLASS_TV
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_ZEROCONF, ConfigEntry
from homeassistant.config_entries import (
SOURCE_IGNORE,
SOURCE_IMPORT,
SOURCE_ZEROCONF,
ConfigEntry,
)
from homeassistant.const import (
CONF_ACCESS_TOKEN,
CONF_DEVICE_CLASS,
@@ -198,8 +203,13 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
# Check if new config entry matches any existing config entries
for entry in self.hass.config_entries.async_entries(DOMAIN):
# If source is ignore bypass host and name check and continue through loop
if entry.source == SOURCE_IGNORE:
continue
if _host_is_same(entry.data[CONF_HOST], user_input[CONF_HOST]):
errors[CONF_HOST] = "host_exists"
if entry.data[CONF_NAME] == user_input[CONF_NAME]:
errors[CONF_NAME] = "name_exists"
@@ -270,6 +280,10 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Import a config entry from configuration.yaml."""
# Check if new config entry matches any existing config entries
for entry in self.hass.config_entries.async_entries(DOMAIN):
# If source is ignore bypass host check and continue through loop
if entry.source == SOURCE_IGNORE:
continue
if _host_is_same(entry.data[CONF_HOST], import_config[CONF_HOST]):
updated_options = {}
updated_data = {}
@@ -327,6 +341,7 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
await self.async_set_unique_id(
unique_id=discovery_info[CONF_HOST].split(":")[0], raise_on_progress=True
)
self._abort_if_unique_id_configured()
discovery_info[
CONF_HOST
@@ -334,6 +349,10 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
# Check if new config entry matches any existing config entries and abort if so
for entry in self.hass.config_entries.async_entries(DOMAIN):
# If source is ignore bypass host check and continue through loop
if entry.source == SOURCE_IGNORE:
continue
if _host_is_same(entry.data[CONF_HOST], discovery_info[CONF_HOST]):
return self.async_abort(reason="already_setup")

View File

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

View File

@@ -163,7 +163,8 @@ class DataUpdateCoordinator:
self.name,
monotonic() - start,
)
self._schedule_refresh()
if self._listeners:
self._schedule_refresh()
for update_callback in self._listeners:
update_callback()

View File

@@ -11,7 +11,7 @@ ciso8601==2.1.3
cryptography==2.8
defusedxml==0.6.0
distro==1.4.0
hass-nabucasa==0.32.2
hass-nabucasa==0.33.0
home-assistant-frontend==20200407.2
importlib-metadata==1.5.0
jinja2>=2.11.1

View File

@@ -355,7 +355,7 @@ boto3==1.9.252
bravia-tv==1.0.1
# homeassistant.components.broadlink
broadlink==0.13.0
broadlink==0.13.1
# homeassistant.components.brother
brother==0.1.11
@@ -674,7 +674,7 @@ habitipy==0.2.0
hangups==0.4.9
# homeassistant.components.cloud
hass-nabucasa==0.32.2
hass-nabucasa==0.33.0
# homeassistant.components.mqtt
hbmqtt==0.9.5
@@ -922,7 +922,7 @@ netdisco==2.6.0
neurio==0.3.1
# homeassistant.components.nexia
nexia==0.9.1
nexia==0.9.2
# homeassistant.components.nextcloud
nextcloudmonitor==1.1.0

View File

@@ -137,7 +137,7 @@ bellows-homeassistant==0.15.2
bomradarloop==0.1.4
# homeassistant.components.broadlink
broadlink==0.13.0
broadlink==0.13.1
# homeassistant.components.brother
brother==0.1.11
@@ -264,7 +264,7 @@ ha-ffmpeg==2.0
hangups==0.4.9
# homeassistant.components.cloud
hass-nabucasa==0.32.2
hass-nabucasa==0.33.0
# homeassistant.components.mqtt
hbmqtt==0.9.5
@@ -357,7 +357,7 @@ nessclient==0.9.15
netdisco==2.6.0
# homeassistant.components.nexia
nexia==0.9.1
nexia==0.9.2
# homeassistant.components.nsw_fuel_station
nsw-fuel-api-client==1.0.10

View File

@@ -3806,9 +3806,9 @@ async def test_camera_discovery_without_stream(hass):
"url,result",
[
("http://nohttpswrongport.org:8123", 2),
("https://httpswrongport.org:8123", 2),
("http://nohttpsport443.org:443", 2),
("tls://nohttpsport443.org:443", 2),
("https://httpsnnonstandport.org:8123", 3),
("https://correctschemaandport.org:443", 3),
("https://correctschemaandport.org", 3),
],

View File

@@ -1,24 +1,23 @@
"""Tests for the cloud binary sensor."""
from unittest.mock import Mock
from asynctest import patch
from homeassistant.components.cloud.const import DISPATCHER_REMOTE_UPDATE
from homeassistant.setup import async_setup_component
async def test_remote_connection_sensor(hass):
"""Test the remote connection sensor."""
from homeassistant.components.cloud import binary_sensor as bin_sensor
bin_sensor.WAIT_UNTIL_CHANGE = 0
assert await async_setup_component(hass, "cloud", {"cloud": {}})
await hass.async_block_till_done()
assert hass.states.get("binary_sensor.remote_ui") is None
# Fake connection/discovery
org_cloud = hass.data["cloud"]
await org_cloud.iot._on_connect[-1]()
await hass.helpers.discovery.async_load_platform(
"binary_sensor", "cloud", {}, {"cloud": {}}
)
# Mock test env
cloud = hass.data["cloud"] = Mock()
@@ -29,17 +28,18 @@ async def test_remote_connection_sensor(hass):
assert state is not None
assert state.state == "unavailable"
cloud.remote.is_connected = False
cloud.remote.certificate = object()
hass.helpers.dispatcher.async_dispatcher_send(DISPATCHER_REMOTE_UPDATE, {})
await hass.async_block_till_done()
with patch("homeassistant.components.cloud.binary_sensor.WAIT_UNTIL_CHANGE", 0):
cloud.remote.is_connected = False
cloud.remote.certificate = object()
hass.helpers.dispatcher.async_dispatcher_send(DISPATCHER_REMOTE_UPDATE, {})
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.remote_ui")
assert state.state == "off"
state = hass.states.get("binary_sensor.remote_ui")
assert state.state == "off"
cloud.remote.is_connected = True
hass.helpers.dispatcher.async_dispatcher_send(DISPATCHER_REMOTE_UPDATE, {})
await hass.async_block_till_done()
cloud.remote.is_connected = True
hass.helpers.dispatcher.async_dispatcher_send(DISPATCHER_REMOTE_UPDATE, {})
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.remote_ui")
assert state.state == "on"
state = hass.states.get("binary_sensor.remote_ui")
assert state.state == "on"

View File

@@ -6,7 +6,7 @@ import pytest
from homeassistant.components import cloud
from homeassistant.components.cloud.const import DOMAIN
from homeassistant.components.cloud.prefs import STORAGE_KEY
from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import Context
from homeassistant.exceptions import Unauthorized
from homeassistant.setup import async_setup_component
@@ -103,12 +103,6 @@ async def test_remote_services(hass, mock_cloud_fixture, hass_read_only_user):
async def test_startup_shutdown_events(hass, mock_cloud_fixture):
"""Test if the cloud will start on startup event."""
with patch("hass_nabucasa.Cloud.start", return_value=mock_coro()) as mock_start:
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
await hass.async_block_till_done()
assert mock_start.called
with patch("hass_nabucasa.Cloud.stop", return_value=mock_coro()) as mock_stop:
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
await hass.async_block_till_done()

View File

@@ -39,7 +39,7 @@ async def test_form_user_with_secure_elk(hass):
"address": "1.2.3.4",
"username": "test-username",
"password": "test-password",
"temperature_unit": "F",
"temperature_unit": "°F",
"prefix": "",
},
)
@@ -51,7 +51,7 @@ async def test_form_user_with_secure_elk(hass):
"host": "elks://1.2.3.4",
"password": "test-password",
"prefix": "",
"temperature_unit": "F",
"temperature_unit": "°F",
"username": "test-username",
}
await hass.async_block_till_done()
@@ -82,7 +82,7 @@ async def test_form_user_with_non_secure_elk(hass):
{
"protocol": "non-secure",
"address": "1.2.3.4",
"temperature_unit": "F",
"temperature_unit": "°F",
"prefix": "guest_house",
},
)
@@ -95,7 +95,7 @@ async def test_form_user_with_non_secure_elk(hass):
"prefix": "guest_house",
"username": "",
"password": "",
"temperature_unit": "F",
"temperature_unit": "°F",
}
await hass.async_block_till_done()
assert len(mock_setup.mock_calls) == 1
@@ -125,7 +125,7 @@ async def test_form_user_with_serial_elk(hass):
{
"protocol": "serial",
"address": "/dev/ttyS0:115200",
"temperature_unit": "F",
"temperature_unit": "°C",
"prefix": "",
},
)
@@ -138,7 +138,7 @@ async def test_form_user_with_serial_elk(hass):
"prefix": "",
"username": "",
"password": "",
"temperature_unit": "F",
"temperature_unit": "°C",
}
await hass.async_block_till_done()
assert len(mock_setup.mock_calls) == 1
@@ -166,7 +166,7 @@ async def test_form_cannot_connect(hass):
"address": "1.2.3.4",
"username": "test-username",
"password": "test-password",
"temperature_unit": "F",
"temperature_unit": "°F",
"prefix": "",
},
)
@@ -193,7 +193,7 @@ async def test_form_invalid_auth(hass):
"address": "1.2.3.4",
"username": "test-username",
"password": "test-password",
"temperature_unit": "F",
"temperature_unit": "°F",
"prefix": "",
},
)

View File

@@ -40,19 +40,11 @@ class ReadResult:
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
@@ -69,9 +61,9 @@ async def run_test(
# 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
use_mock_hub.read_input_registers.return_value = read_result
else:
use_mock_hub.read_holding_registers = simulate_read_registers
use_mock_hub.read_holding_registers.return_value = read_result
# Initialize sensor
now = dt_util.utcnow()

View File

@@ -4,6 +4,8 @@ from asynctest import MagicMock, patch
from homeassistant import config_entries, setup
from homeassistant.components.nut.const import DOMAIN
from tests.common import MockConfigEntry
def _get_mock_pynutclient(list_vars=None):
pynutclient = MagicMock()
@@ -62,6 +64,12 @@ async def test_form_import(hass):
"""Test we get the form with import source."""
await setup.async_setup_component(hass, "persistent_notification", {})
config_entry = MockConfigEntry(
domain=DOMAIN,
data={"host": "2.2.2.2", "port": 123, "resources": ["battery.charge"]},
)
config_entry.add_to_hass(hass)
mock_pynut = _get_mock_pynutclient(list_vars={"battery.voltage": "serial"})
with patch(
@@ -92,7 +100,7 @@ async def test_form_import(hass):
}
await hass.async_block_till_done()
assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 2
async def test_form_cannot_connect(hass):

View File

@@ -174,6 +174,7 @@ MOCK_INCLUDE_APPS = {
CONF_INCLUDE_OR_EXCLUDE: CONF_INCLUDE.title(),
CONF_APPS_TO_INCLUDE_OR_EXCLUDE: [CURRENT_APP],
}
MOCK_INCLUDE_NO_APPS = {
CONF_INCLUDE_OR_EXCLUDE: CONF_INCLUDE.title(),
CONF_APPS_TO_INCLUDE_OR_EXCLUDE: [],

View File

@@ -17,7 +17,12 @@ from homeassistant.components.vizio.const import (
DOMAIN,
VIZIO_SCHEMA,
)
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER, SOURCE_ZEROCONF
from homeassistant.config_entries import (
SOURCE_IGNORE,
SOURCE_IMPORT,
SOURCE_USER,
SOURCE_ZEROCONF,
)
from homeassistant.const import (
CONF_ACCESS_TOKEN,
CONF_DEVICE_CLASS,
@@ -46,6 +51,7 @@ from .const import (
NAME2,
UNIQUE_ID,
VOLUME_STEP,
ZEROCONF_HOST,
)
from tests.common import MockConfigEntry
@@ -383,6 +389,26 @@ async def test_user_invalid_pin(
assert result["errors"] == {CONF_PIN: "complete_pairing_failed"}
async def test_user_ignore(
hass: HomeAssistantType,
vizio_connect: pytest.fixture,
vizio_bypass_setup: pytest.fixture,
) -> None:
"""Test user config flow doesn't throw an error when there's an existing ignored source."""
entry = MockConfigEntry(
domain=DOMAIN,
data=MOCK_SPEAKER_CONFIG,
options={CONF_VOLUME_STEP: VOLUME_STEP},
source=SOURCE_IGNORE,
)
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=MOCK_SPEAKER_CONFIG
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
async def test_import_flow_minimum_fields(
hass: HomeAssistantType,
vizio_connect: pytest.fixture,
@@ -679,6 +705,29 @@ async def test_import_error(
assert len(vizio_log_list) == 1
async def test_import_ignore(
hass: HomeAssistantType,
vizio_connect: pytest.fixture,
vizio_bypass_setup: pytest.fixture,
) -> None:
"""Test import config flow doesn't throw an error when there's an existing ignored source."""
entry = MockConfigEntry(
domain=DOMAIN,
data=MOCK_SPEAKER_CONFIG,
options={CONF_VOLUME_STEP: VOLUME_STEP},
source=SOURCE_IGNORE,
)
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data=vol.Schema(VIZIO_SCHEMA)(MOCK_SPEAKER_CONFIG),
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
async def test_zeroconf_flow(
hass: HomeAssistantType,
vizio_connect: pytest.fixture,
@@ -756,3 +805,51 @@ async def test_zeroconf_dupe_fail(
# Flow should abort because device is already setup
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "already_in_progress"
async def test_zeroconf_ignore(
hass: HomeAssistantType,
vizio_connect: pytest.fixture,
vizio_bypass_setup: pytest.fixture,
vizio_guess_device_type: pytest.fixture,
) -> None:
"""Test zeroconf discovery doesn't throw an error when there's an existing ignored source."""
entry = MockConfigEntry(
domain=DOMAIN,
data=MOCK_SPEAKER_CONFIG,
options={CONF_VOLUME_STEP: VOLUME_STEP},
source=SOURCE_IGNORE,
)
entry.add_to_hass(hass)
discovery_info = MOCK_ZEROCONF_SERVICE_INFO.copy()
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_ZEROCONF}, data=discovery_info
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
async def test_zeroconf_abort_when_ignored(
hass: HomeAssistantType,
vizio_connect: pytest.fixture,
vizio_bypass_setup: pytest.fixture,
vizio_guess_device_type: pytest.fixture,
) -> None:
"""Test zeroconf discovery aborts when the same host has been ignored."""
entry = MockConfigEntry(
domain=DOMAIN,
data=MOCK_SPEAKER_CONFIG,
options={CONF_VOLUME_STEP: VOLUME_STEP},
source=SOURCE_IGNORE,
unique_id=ZEROCONF_HOST,
)
entry.add_to_hass(hass)
discovery_info = MOCK_ZEROCONF_SERVICE_INFO.copy()
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_ZEROCONF}, data=discovery_info
)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"

View File

@@ -40,6 +40,8 @@ async def test_async_refresh(crd):
await crd.async_refresh()
assert crd.data == 1
assert crd.last_update_success is True
# Make sure we didn't schedule a refresh because we have 0 listeners
assert crd._unsub_refresh is None
updates = []