Rollback modbus to version 0.107.7 keep new functionality (#34287)

* Rollback modbus to version 0.107.7

Update manifest to not use async.

Rollback entities to sync version.

Keep newer modifications apart from async.

Rollback __init__ to sync version but keep the new functionality.

add async sub directory

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

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

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

* do not publish async version

The async version will be made available in a forked repo, until
it is ready to replace the production code.
This commit is contained in:
jan iversen 2020-04-17 09:55:57 +02:00 committed by GitHub
parent 9aee91b98c
commit 8277ebcbe1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 180 additions and 230 deletions

View File

@ -1,23 +1,9 @@
"""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.asyncio import ( from pymodbus.transaction import ModbusRtuFramer
AsyncioModbusSerialClient,
ModbusClientProtocol,
init_tcp_client,
init_udp_client,
)
from pymodbus.exceptions import ModbusException
from pymodbus.factory import ClientDecoder
from pymodbus.pdu import ExceptionResponse
from pymodbus.transaction import (
ModbusAsciiFramer,
ModbusBinaryFramer,
ModbusRtuFramer,
ModbusSocketFramer,
)
import voluptuous as vol import voluptuous as vol
from homeassistant.const import ( from homeassistant.const import (
@ -50,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(
@ -101,57 +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()
async def write_register(service): 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
for client in hub_collect.values(): for client in hub_collect.values():
await client.setup(hass) client.setup()
# register function to gracefully stop modbus # register function to gracefully stop modbus
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, 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
@ -159,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]
@ -183,144 +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): def setup(self):
if self._config_delay > 0:
await asyncio.sleep(self._config_delay)
self._config_delay = 0
@staticmethod
def _framer(method):
if method == "ascii":
framer = ModbusAsciiFramer(ClientDecoder())
elif method == "rtu":
framer = ModbusRtuFramer(ClientDecoder())
elif method == "binary":
framer = ModbusBinaryFramer(ClientDecoder())
elif method == "socket":
framer = ModbusSocketFramer(ClientDecoder())
else:
framer = None
return framer
async def setup(self, hass):
"""Set up pymodbus client.""" """Set up pymodbus client."""
if self._config_type == "serial": if self._config_type == "serial":
# reconnect ?? self._client = ModbusSerialClient(
framer = self._framer(self._config_method) method=self._config_method,
port=self._config_port,
# just a class creation no IO or other slow items
self._client = AsyncioModbusSerialClient(
self._config_port,
protocol_class=ModbusClientProtocol,
framer=framer,
loop=self._loop,
baudrate=self._config_baudrate, baudrate=self._config_baudrate,
stopbits=self._config_stopbits,
bytesize=self._config_bytesize, bytesize=self._config_bytesize,
parity=self._config_parity, parity=self._config_parity,
stopbits=self._config_stopbits, timeout=self._config_timeout,
) )
await self._client.connect()
elif self._config_type == "rtuovertcp": elif self._config_type == "rtuovertcp":
# framer ModbusRtuFramer ?? self._client = ModbusTcpClient(
# timeout ?? host=self._config_host,
self._client = await init_tcp_client( port=self._config_port,
None, self._loop, self._config_host, self._config_port framer=ModbusRtuFramer,
timeout=self._config_timeout,
) )
elif self._config_type == "tcp": elif self._config_type == "tcp":
# framer ?? self._client = ModbusTcpClient(
# timeout ?? host=self._config_host,
self._client = await init_tcp_client( port=self._config_port,
None, self._loop, self._config_host, self._config_port timeout=self._config_timeout,
) )
elif self._config_type == "udp": elif self._config_type == "udp":
# framer ?? self._client = ModbusUdpClient(
# timeout ?? host=self._config_host,
self._client = await init_udp_client( port=self._config_port,
None, self._loop, self._config_host, self._config_port timeout=self._config_timeout,
) )
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."""
try:
if self._input_type == CALL_TYPE_COIL: if self._input_type == CALL_TYPE_COIL:
result = await self._hub.read_coils(self._slave, self._address, 1) result = self._hub.read_coils(self._slave, self._address, 1)
else: else:
result = await self._hub.read_discrete_inputs(self._slave, self._address, 1) result = self._hub.read_discrete_inputs(self._slave, self._address, 1)
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

@ -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."""
try:
if register_type == CALL_TYPE_REGISTER_INPUT: if register_type == CALL_TYPE_REGISTER_INPUT:
result = await self._hub.read_input_registers( result = self._hub.read_input_registers(
self._slave, register, self._count self._slave, register, self._count
) )
else: else:
result = await self._hub.read_holding_registers( result = self._hub.read_holding_registers(
self._slave, register, self._count 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

@ -2,7 +2,6 @@
"domain": "modbus", "domain": "modbus",
"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"],
"pyserial-asyncio==0.4"],
"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."""
try:
if self._register_type == CALL_TYPE_REGISTER_INPUT: if self._register_type == CALL_TYPE_REGISTER_INPUT:
result = await self._hub.read_input_registers( result = self._hub.read_input_registers(
self._slave, self._register, self._count self._slave, self._register, self._count
) )
else: else:
result = await self._hub.read_holding_registers( result = self._hub.read_holding_registers(
self._slave, self._register, self._count 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
@ -246,7 +248,7 @@ class ModbusRegisterSensor(RestoreEntity):
if isinstance(val, int): if isinstance(val, int):
self._value = str(val) self._value = str(val)
if self._precision > 0: if self._precision > 0:
self._value += f".{'0' * self._precision}" self._value += "." + "0" * self._precision
else: else:
self._value = f"{val:.{self._precision}f}" self._value = f"{val:.{self._precision}f}"

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]:
try:
if self._register_type == CALL_TYPE_REGISTER_INPUT: if self._register_type == CALL_TYPE_REGISTER_INPUT:
result = await self._hub.read_input_registers( result = self._hub.read_input_registers(self._slave, self._register, 1)
self._slave, self._register, 1
)
else: else:
result = await self._hub.read_holding_registers( result = self._hub.read_holding_registers(
self._slave, self._register, 1 self._slave, self._register, 1
) )
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
@ -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

@ -1531,7 +1531,6 @@ pysdcp==1
# homeassistant.components.sensibo # homeassistant.components.sensibo
pysensibo==1.0.3 pysensibo==1.0.3
# homeassistant.components.modbus
# homeassistant.components.serial # homeassistant.components.serial
pyserial-asyncio==0.4 pyserial-asyncio==0.4

View File

@ -600,10 +600,6 @@ pyps4-2ndscreen==1.0.7
# homeassistant.components.qwikswitch # homeassistant.components.qwikswitch
pyqwikswitch==0.93 pyqwikswitch==0.93
# homeassistant.components.modbus
# homeassistant.components.serial
pyserial-asyncio==0.4
# homeassistant.components.signal_messenger # homeassistant.components.signal_messenger
pysignalclirestapi==0.2.4 pysignalclirestapi==0.2.4

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()