mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Fix modbus sync/async issues (#34043)
* add pyserial to manifest pymodbus is very developer oriented and assumes every developer adapt the requierements.txt to his/hers needs. Our requirements.txt is different it contains all posibilities allowing user to later change configuration without having to install extra packages. As a consequence manifest.json needs to include the pyserial. * modbus: make truly async client creation Make hass call listen_once async. Integrate content of start_modbus into async_setup. Do not use the boiler plate create tcp client function from pymodbus as it is sync, and also does not work well with asyncio, instead call the init_<type> directly, since that is async. * both component/modbus and component/serial uses pyserial-async but with slighty different version requirements. Combined the 2. * Review 1 * Review 2 * Review @staticmethod is no good, because the function uses class variables. * Review Pytest is sometimes a bit cryptic, lets hope this does it.
This commit is contained in:
parent
d454c6a43d
commit
f8f8dddca7
@ -3,13 +3,21 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from async_timeout import timeout
|
from async_timeout import timeout
|
||||||
from pymodbus.client.asynchronous import schedulers
|
from pymodbus.client.asynchronous.asyncio import (
|
||||||
from pymodbus.client.asynchronous.serial import AsyncModbusSerialClient as ClientSerial
|
AsyncioModbusSerialClient,
|
||||||
from pymodbus.client.asynchronous.tcp import AsyncModbusTCPClient as ClientTCP
|
ModbusClientProtocol,
|
||||||
from pymodbus.client.asynchronous.udp import AsyncModbusUDPClient as ClientUDP
|
init_tcp_client,
|
||||||
|
init_udp_client,
|
||||||
|
)
|
||||||
from pymodbus.exceptions import ModbusException
|
from pymodbus.exceptions import ModbusException
|
||||||
|
from pymodbus.factory import ClientDecoder
|
||||||
from pymodbus.pdu import ExceptionResponse
|
from pymodbus.pdu import ExceptionResponse
|
||||||
from pymodbus.transaction import ModbusRtuFramer
|
from pymodbus.transaction import (
|
||||||
|
ModbusAsciiFramer,
|
||||||
|
ModbusBinaryFramer,
|
||||||
|
ModbusRtuFramer,
|
||||||
|
ModbusSocketFramer,
|
||||||
|
)
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
@ -105,13 +113,6 @@ async def async_setup(hass, config):
|
|||||||
for client in hub_collect.values():
|
for client in hub_collect.values():
|
||||||
del client
|
del client
|
||||||
|
|
||||||
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):
|
async def write_register(service):
|
||||||
"""Write Modbus registers."""
|
"""Write Modbus registers."""
|
||||||
unit = int(float(service.data[ATTR_UNIT]))
|
unit = int(float(service.data[ATTR_UNIT]))
|
||||||
@ -136,7 +137,11 @@ async def async_setup(hass, config):
|
|||||||
await hub_collect[client_name].write_coil(unit, address, state)
|
await hub_collect[client_name].write_coil(unit, address, state)
|
||||||
|
|
||||||
# do not wait for EVENT_HOMEASSISTANT_START, activate pymodbus now
|
# do not wait for EVENT_HOMEASSISTANT_START, activate pymodbus now
|
||||||
await hass.async_add_executor_job(start_modbus)
|
for client in hub_collect.values():
|
||||||
|
await client.setup(hass)
|
||||||
|
|
||||||
|
# register function to gracefully stop modbus
|
||||||
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_modbus)
|
||||||
|
|
||||||
# Register services for modbus
|
# Register services for modbus
|
||||||
hass.services.async_register(
|
hass.services.async_register(
|
||||||
@ -189,47 +194,55 @@ class ModbusHub:
|
|||||||
await asyncio.sleep(self._config_delay)
|
await asyncio.sleep(self._config_delay)
|
||||||
self._config_delay = 0
|
self._config_delay = 0
|
||||||
|
|
||||||
def setup(self):
|
@staticmethod
|
||||||
"""Set up pymodbus client."""
|
def _framer(method):
|
||||||
# pylint: disable = E0633
|
if method == "ascii":
|
||||||
# Client* do deliver loop, client as result but
|
framer = ModbusAsciiFramer(ClientDecoder())
|
||||||
# pylint does not accept that fact
|
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."""
|
||||||
if self._config_type == "serial":
|
if self._config_type == "serial":
|
||||||
_, self._client = ClientSerial(
|
# reconnect ??
|
||||||
schedulers.ASYNC_IO,
|
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,
|
||||||
loop=self._loop,
|
stopbits=self._config_stopbits,
|
||||||
)
|
)
|
||||||
|
await self._client.connect()
|
||||||
elif self._config_type == "rtuovertcp":
|
elif self._config_type == "rtuovertcp":
|
||||||
_, self._client = ClientTCP(
|
# framer ModbusRtuFramer ??
|
||||||
schedulers.ASYNC_IO,
|
# 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,
|
|
||||||
loop=self._loop,
|
|
||||||
)
|
)
|
||||||
elif self._config_type == "tcp":
|
elif self._config_type == "tcp":
|
||||||
_, self._client = ClientTCP(
|
# framer ??
|
||||||
schedulers.ASYNC_IO,
|
# 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,
|
|
||||||
loop=self._loop,
|
|
||||||
)
|
)
|
||||||
elif self._config_type == "udp":
|
elif self._config_type == "udp":
|
||||||
_, self._client = ClientUDP(
|
# framer ??
|
||||||
schedulers.ASYNC_IO,
|
# 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,
|
|
||||||
loop=self._loop,
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
assert False
|
assert False
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"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"]
|
||||||
}
|
}
|
||||||
|
@ -1528,6 +1528,7 @@ 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
|
||||||
|
|
||||||
|
@ -597,6 +597,10 @@ 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
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user