Merge pull request #34517 from home-assistant/rc

This commit is contained in:
Paulus Schoutsen 2020-04-21 17:26:36 -07:00 committed by GitHub
commit 9b757e4c22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 222 additions and 257 deletions

View File

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

View File

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

View File

@ -2,7 +2,7 @@
"domain": "cloud", "domain": "cloud",
"name": "Home Assistant Cloud", "name": "Home Assistant Cloud",
"documentation": "https://www.home-assistant.io/integrations/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"], "dependencies": ["http", "webhook", "alexa"],
"after_dependencies": ["google_assistant"], "after_dependencies": ["google_assistant"],
"codeowners": ["@home-assistant/cloud"] "codeowners": ["@home-assistant/cloud"]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -60,6 +60,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
username = conf[CONF_USERNAME] username = conf[CONF_USERNAME]
password = conf[CONF_PASSWORD] password = conf[CONF_PASSWORD]
state_file = hass.config.path(f"nexia_config_{username}.conf")
try: try:
nexia_home = await hass.async_add_executor_job( nexia_home = await hass.async_add_executor_job(
partial( partial(
@ -67,6 +69,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
username=username, username=username,
password=password, password=password,
device_name=hass.config.location_name, device_name=hass.config.location_name,
state_file=state_file,
) )
) )
except ConnectTimeout as ex: 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. 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: try:
nexia_home = NexiaHome( nexia_home = NexiaHome(
username=data[CONF_USERNAME], username=data[CONF_USERNAME],
@ -27,6 +29,7 @@ async def validate_input(hass: core.HomeAssistant, data):
auto_login=False, auto_login=False,
auto_update=False, auto_update=False,
device_name=hass.config.location_name, device_name=hass.config.location_name,
state_file=state_file,
) )
await hass.async_add_executor_job(nexia_home.login) await hass.async_add_executor_job(nexia_home.login)
except ConnectTimeout as ex: except ConnectTimeout as ex:

View File

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

View File

@ -99,7 +99,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
def _host_port_alias_already_configured(self, host, port, alias): def _host_port_alias_already_configured(self, host, port, alias):
"""See if we already have a nut entry matching user input configured.""" """See if we already have a nut entry matching user input configured."""
existing_host_port_aliases = { 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() for entry in self._async_current_entries()
} }
return _format_host_port_alias(host, port, alias) in existing_host_port_aliases return _format_host_port_alias(host, port, alias) in existing_host_port_aliases

View File

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

View File

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

View File

@ -674,7 +674,7 @@ habitipy==0.2.0
hangups==0.4.9 hangups==0.4.9
# homeassistant.components.cloud # homeassistant.components.cloud
hass-nabucasa==0.32.2 hass-nabucasa==0.33.0
# homeassistant.components.mqtt # homeassistant.components.mqtt
hbmqtt==0.9.5 hbmqtt==0.9.5
@ -922,7 +922,7 @@ netdisco==2.6.0
neurio==0.3.1 neurio==0.3.1
# homeassistant.components.nexia # homeassistant.components.nexia
nexia==0.9.1 nexia==0.9.2
# homeassistant.components.nextcloud # homeassistant.components.nextcloud
nextcloudmonitor==1.1.0 nextcloudmonitor==1.1.0

View File

@ -264,7 +264,7 @@ ha-ffmpeg==2.0
hangups==0.4.9 hangups==0.4.9
# homeassistant.components.cloud # homeassistant.components.cloud
hass-nabucasa==0.32.2 hass-nabucasa==0.33.0
# homeassistant.components.mqtt # homeassistant.components.mqtt
hbmqtt==0.9.5 hbmqtt==0.9.5
@ -357,7 +357,7 @@ nessclient==0.9.15
netdisco==2.6.0 netdisco==2.6.0
# homeassistant.components.nexia # homeassistant.components.nexia
nexia==0.9.1 nexia==0.9.2
# homeassistant.components.nsw_fuel_station # homeassistant.components.nsw_fuel_station
nsw-fuel-api-client==1.0.10 nsw-fuel-api-client==1.0.10

View File

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

View File

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

View File

@ -6,7 +6,7 @@ import pytest
from homeassistant.components import cloud from homeassistant.components import cloud
from homeassistant.components.cloud.const import DOMAIN from homeassistant.components.cloud.const import DOMAIN
from homeassistant.components.cloud.prefs import STORAGE_KEY 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.core import Context
from homeassistant.exceptions import Unauthorized from homeassistant.exceptions import Unauthorized
from homeassistant.setup import async_setup_component 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): async def test_startup_shutdown_events(hass, mock_cloud_fixture):
"""Test if the cloud will start on startup event.""" """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: with patch("hass_nabucasa.Cloud.stop", return_value=mock_coro()) as mock_stop:
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
await hass.async_block_till_done() await hass.async_block_till_done()

View File

@ -40,19 +40,11 @@ class ReadResult:
self.registers = register_words self.registers = register_words
read_result = None
async def run_test( async def run_test(
hass, use_mock_hub, register_config, entity_domain, register_words, expected hass, use_mock_hub, register_config, entity_domain, register_words, expected
): ):
"""Run test for given config and check that sensor outputs expected result.""" """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 # Full sensor configuration
sensor_name = "modbus_test_sensor" sensor_name = "modbus_test_sensor"
scan_interval = 5 scan_interval = 5
@ -69,9 +61,9 @@ async def run_test(
# Setup inputs for the sensor # Setup inputs for the sensor
read_result = ReadResult(register_words) read_result = ReadResult(register_words)
if register_config.get(CONF_REGISTER_TYPE) == CALL_TYPE_REGISTER_INPUT: 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: else:
use_mock_hub.read_holding_registers = simulate_read_registers use_mock_hub.read_holding_registers.return_value = read_result
# Initialize sensor # Initialize sensor
now = dt_util.utcnow() now = dt_util.utcnow()

View File

@ -4,6 +4,8 @@ from asynctest import MagicMock, patch
from homeassistant import config_entries, setup from homeassistant import config_entries, setup
from homeassistant.components.nut.const import DOMAIN from homeassistant.components.nut.const import DOMAIN
from tests.common import MockConfigEntry
def _get_mock_pynutclient(list_vars=None): def _get_mock_pynutclient(list_vars=None):
pynutclient = MagicMock() pynutclient = MagicMock()
@ -62,6 +64,12 @@ async def test_form_import(hass):
"""Test we get the form with import source.""" """Test we get the form with import source."""
await setup.async_setup_component(hass, "persistent_notification", {}) 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"}) mock_pynut = _get_mock_pynutclient(list_vars={"battery.voltage": "serial"})
with patch( with patch(
@ -92,7 +100,7 @@ async def test_form_import(hass):
} }
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(mock_setup.mock_calls) == 1 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): async def test_form_cannot_connect(hass):