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.pull/34337/head
parent
9aee91b98c
commit
8277ebcbe1
|
@ -1,23 +1,9 @@
|
|||
"""Support for Modbus."""
|
||||
import asyncio
|
||||
import logging
|
||||
import threading
|
||||
|
||||
from async_timeout import timeout
|
||||
from pymodbus.client.asynchronous.asyncio import (
|
||||
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,
|
||||
)
|
||||
from pymodbus.client.sync import ModbusSerialClient, ModbusTcpClient, ModbusUdpClient
|
||||
from pymodbus.transaction import ModbusRtuFramer
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
|
@ -50,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(
|
||||
|
@ -101,57 +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()
|
||||
|
||||
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
|
||||
for client in hub_collect.values():
|
||||
await client.setup(hass)
|
||||
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
|
||||
|
||||
|
@ -159,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]
|
||||
|
@ -183,144 +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
|
||||
|
||||
@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):
|
||||
def setup(self):
|
||||
"""Set up pymodbus client."""
|
||||
if self._config_type == "serial":
|
||||
# reconnect ??
|
||||
framer = self._framer(self._config_method)
|
||||
|
||||
# just a class creation no IO or other slow items
|
||||
self._client = AsyncioModbusSerialClient(
|
||||
self._config_port,
|
||||
protocol_class=ModbusClientProtocol,
|
||||
framer=framer,
|
||||
loop=self._loop,
|
||||
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,
|
||||
stopbits=self._config_stopbits,
|
||||
timeout=self._config_timeout,
|
||||
)
|
||||
await self._client.connect()
|
||||
elif self._config_type == "rtuovertcp":
|
||||
# framer ModbusRtuFramer ??
|
||||
# timeout ??
|
||||
self._client = await init_tcp_client(
|
||||
None, self._loop, self._config_host, self._config_port
|
||||
self._client = ModbusTcpClient(
|
||||
host=self._config_host,
|
||||
port=self._config_port,
|
||||
framer=ModbusRtuFramer,
|
||||
timeout=self._config_timeout,
|
||||
)
|
||||
elif self._config_type == "tcp":
|
||||
# framer ??
|
||||
# timeout ??
|
||||
self._client = await init_tcp_client(
|
||||
None, self._loop, self._config_host, self._config_port
|
||||
self._client = ModbusTcpClient(
|
||||
host=self._config_host,
|
||||
port=self._config_port,
|
||||
timeout=self._config_timeout,
|
||||
)
|
||||
elif self._config_type == "udp":
|
||||
# framer ??
|
||||
# timeout ??
|
||||
self._client = await init_udp_client(
|
||||
None, self._loop, self._config_host, self._config_port
|
||||
self._client = ModbusUdpClient(
|
||||
host=self._config_host,
|
||||
port=self._config_port,
|
||||
timeout=self._config_timeout,
|
||||
)
|
||||
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."""
|
||||
try:
|
||||
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:
|
||||
result = await self._hub.read_discrete_inputs(self._slave, self._address, 1)
|
||||
if result is None:
|
||||
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."""
|
||||
try:
|
||||
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
|
||||
)
|
||||
else:
|
||||
result = await self._hub.read_holding_registers(
|
||||
result = self._hub.read_holding_registers(
|
||||
self._slave, register, self._count
|
||||
)
|
||||
if result is None:
|
||||
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
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
"domain": "modbus",
|
||||
"name": "Modbus",
|
||||
"documentation": "https://www.home-assistant.io/integrations/modbus",
|
||||
"requirements": ["pymodbus==2.3.0",
|
||||
"pyserial-asyncio==0.4"],
|
||||
"requirements": ["pymodbus==2.3.0"],
|
||||
"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."""
|
||||
try:
|
||||
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
|
||||
)
|
||||
else:
|
||||
result = await self._hub.read_holding_registers(
|
||||
result = self._hub.read_holding_registers(
|
||||
self._slave, self._register, self._count
|
||||
)
|
||||
if result is None:
|
||||
except ConnectionException:
|
||||
self._available = False
|
||||
return
|
||||
|
||||
if isinstance(result, (ModbusException, ExceptionResponse)):
|
||||
self._available = False
|
||||
return
|
||||
|
@ -246,7 +248,7 @@ class ModbusRegisterSensor(RestoreEntity):
|
|||
if isinstance(val, int):
|
||||
self._value = str(val)
|
||||
if self._precision > 0:
|
||||
self._value += f".{'0' * self._precision}"
|
||||
self._value += "." + "0" * self._precision
|
||||
else:
|
||||
self._value = f"{val:.{self._precision}f}"
|
||||
|
||||
|
|
|
@ -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]:
|
||||
def _read_register(self) -> Optional[int]:
|
||||
try:
|
||||
if self._register_type == CALL_TYPE_REGISTER_INPUT:
|
||||
result = await self._hub.read_input_registers(
|
||||
self._slave, self._register, 1
|
||||
)
|
||||
result = self._hub.read_input_registers(self._slave, self._register, 1)
|
||||
else:
|
||||
result = await self._hub.read_holding_registers(
|
||||
result = self._hub.read_holding_registers(
|
||||
self._slave, self._register, 1
|
||||
)
|
||||
if result is None:
|
||||
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
|
||||
|
|
|
@ -1531,7 +1531,6 @@ pysdcp==1
|
|||
# homeassistant.components.sensibo
|
||||
pysensibo==1.0.3
|
||||
|
||||
# homeassistant.components.modbus
|
||||
# homeassistant.components.serial
|
||||
pyserial-asyncio==0.4
|
||||
|
||||
|
|
|
@ -600,10 +600,6 @@ pyps4-2ndscreen==1.0.7
|
|||
# homeassistant.components.qwikswitch
|
||||
pyqwikswitch==0.93
|
||||
|
||||
# homeassistant.components.modbus
|
||||
# homeassistant.components.serial
|
||||
pyserial-asyncio==0.4
|
||||
|
||||
# homeassistant.components.signal_messenger
|
||||
pysignalclirestapi==0.2.4
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue