commit
9b757e4c22
|
@ -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
|
||||
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"""Constants used by Home Assistant components."""
|
||||
MAJOR_VERSION = 0
|
||||
MINOR_VERSION = 108
|
||||
PATCH_VERSION = "6"
|
||||
PATCH_VERSION = "7"
|
||||
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__ = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER = (3, 7, 0)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue