forked from home-assistant/core
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b757e4c22 | ||
|
|
61620dec30 | ||
|
|
12e92d504d | ||
|
|
16e1b3772c | ||
|
|
59686274d7 | ||
|
|
945567150d | ||
|
|
eedfca6623 | ||
|
|
41e4dc4336 | ||
|
|
5635cdb77c | ||
|
|
6e86cbf947 | ||
|
|
334fecdf6f | ||
|
|
317d08b6c1 | ||
|
|
059df5e3e2 | ||
|
|
de440cf579 | ||
|
|
81d006499e | ||
|
|
0a1e33b7e1 | ||
|
|
5157c2d10c | ||
|
|
8a8cbeb4c0 | ||
|
|
de6fc53ca5 |
@@ -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
|
||||
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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]
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,6 +3,5 @@
|
||||
"name": "Modbus",
|
||||
"documentation": "https://www.home-assistant.io/integrations/modbus",
|
||||
"requirements": ["pymodbus==2.3.0"],
|
||||
"dependencies": [],
|
||||
"codeowners": ["@adamchengtkc", "@janiversen"]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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": "",
|
||||
},
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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: [],
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
|
||||
Reference in New Issue
Block a user