Add Modbus cover (#33642)
* Add Modbus cover * Fix improper commands written for Modbus cover via coil * Make changes per review comments * Fix default hub not defined Since support for multiple hubs was added, the default hub option was not implemented correctly. Now I added necessary logic to make it work. First hub in a list will be used as a default hub. * Move Cover config under Modbus section * Revert setting up a default hub alias * Make hub mandatory for Cover * Add default scan interval * Read scan_interval from discovery info * Fix linter error * Use default scan interval from Cover platform * Handle polling for Modbus cover directly inside entity * Move covers under hub config * Fix for review comment * Call update() from Cover actuator methods * Fix time validation
This commit is contained in:
parent
2f4aa35ca6
commit
1d41f024cf
5 changed files with 323 additions and 10 deletions
|
@ -262,7 +262,7 @@ homeassistant/components/min_max/* @fabaff
|
||||||
homeassistant/components/minecraft_server/* @elmurato
|
homeassistant/components/minecraft_server/* @elmurato
|
||||||
homeassistant/components/minio/* @tkislan
|
homeassistant/components/minio/* @tkislan
|
||||||
homeassistant/components/mobile_app/* @robbiet480
|
homeassistant/components/mobile_app/* @robbiet480
|
||||||
homeassistant/components/modbus/* @adamchengtkc @janiversen
|
homeassistant/components/modbus/* @adamchengtkc @janiversen @vzahradnik
|
||||||
homeassistant/components/monoprice/* @etsinko @OnFreund
|
homeassistant/components/monoprice/* @etsinko @OnFreund
|
||||||
homeassistant/components/moon/* @fabaff
|
homeassistant/components/moon/* @fabaff
|
||||||
homeassistant/components/mpd/* @fabaff
|
homeassistant/components/mpd/* @fabaff
|
||||||
|
|
|
@ -6,29 +6,50 @@ from pymodbus.client.sync import ModbusSerialClient, ModbusTcpClient, ModbusUdpC
|
||||||
from pymodbus.transaction import ModbusRtuFramer
|
from pymodbus.transaction import ModbusRtuFramer
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.components.cover import (
|
||||||
|
DEVICE_CLASSES_SCHEMA as COVER_DEVICE_CLASSES_SCHEMA,
|
||||||
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_STATE,
|
ATTR_STATE,
|
||||||
|
CONF_COVERS,
|
||||||
CONF_DELAY,
|
CONF_DELAY,
|
||||||
|
CONF_DEVICE_CLASS,
|
||||||
CONF_HOST,
|
CONF_HOST,
|
||||||
CONF_METHOD,
|
CONF_METHOD,
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
CONF_PORT,
|
CONF_PORT,
|
||||||
|
CONF_SCAN_INTERVAL,
|
||||||
|
CONF_SLAVE,
|
||||||
CONF_TIMEOUT,
|
CONF_TIMEOUT,
|
||||||
CONF_TYPE,
|
CONF_TYPE,
|
||||||
EVENT_HOMEASSISTANT_STOP,
|
EVENT_HOMEASSISTANT_STOP,
|
||||||
)
|
)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.helpers.discovery import load_platform
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_ADDRESS,
|
ATTR_ADDRESS,
|
||||||
ATTR_HUB,
|
ATTR_HUB,
|
||||||
ATTR_UNIT,
|
ATTR_UNIT,
|
||||||
ATTR_VALUE,
|
ATTR_VALUE,
|
||||||
|
CALL_TYPE_COIL,
|
||||||
|
CALL_TYPE_REGISTER_HOLDING,
|
||||||
|
CALL_TYPE_REGISTER_INPUT,
|
||||||
CONF_BAUDRATE,
|
CONF_BAUDRATE,
|
||||||
CONF_BYTESIZE,
|
CONF_BYTESIZE,
|
||||||
|
CONF_INPUT_TYPE,
|
||||||
CONF_PARITY,
|
CONF_PARITY,
|
||||||
|
CONF_REGISTER,
|
||||||
|
CONF_STATE_CLOSED,
|
||||||
|
CONF_STATE_CLOSING,
|
||||||
|
CONF_STATE_OPEN,
|
||||||
|
CONF_STATE_OPENING,
|
||||||
|
CONF_STATUS_REGISTER,
|
||||||
|
CONF_STATUS_REGISTER_TYPE,
|
||||||
CONF_STOPBITS,
|
CONF_STOPBITS,
|
||||||
DEFAULT_HUB,
|
DEFAULT_HUB,
|
||||||
|
DEFAULT_SCAN_INTERVAL,
|
||||||
|
DEFAULT_SLAVE,
|
||||||
MODBUS_DOMAIN as DOMAIN,
|
MODBUS_DOMAIN as DOMAIN,
|
||||||
SERVICE_WRITE_COIL,
|
SERVICE_WRITE_COIL,
|
||||||
SERVICE_WRITE_REGISTER,
|
SERVICE_WRITE_REGISTER,
|
||||||
|
@ -36,9 +57,33 @@ 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})
|
||||||
|
|
||||||
|
COVERS_SCHEMA = vol.All(
|
||||||
|
cv.has_at_least_one_key(CALL_TYPE_COIL, CONF_REGISTER),
|
||||||
|
vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_NAME): cv.string,
|
||||||
|
vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): vol.All(
|
||||||
|
cv.time_period, lambda value: value.total_seconds()
|
||||||
|
),
|
||||||
|
vol.Optional(CONF_DEVICE_CLASS): COVER_DEVICE_CLASSES_SCHEMA,
|
||||||
|
vol.Optional(CONF_SLAVE, default=DEFAULT_SLAVE): cv.positive_int,
|
||||||
|
vol.Optional(CONF_STATE_CLOSED, default=0): cv.positive_int,
|
||||||
|
vol.Optional(CONF_STATE_CLOSING, default=3): cv.positive_int,
|
||||||
|
vol.Optional(CONF_STATE_OPEN, default=1): cv.positive_int,
|
||||||
|
vol.Optional(CONF_STATE_OPENING, default=2): cv.positive_int,
|
||||||
|
vol.Optional(CONF_STATUS_REGISTER): cv.positive_int,
|
||||||
|
vol.Optional(
|
||||||
|
CONF_STATUS_REGISTER_TYPE,
|
||||||
|
default=CALL_TYPE_REGISTER_HOLDING,
|
||||||
|
): vol.In([CALL_TYPE_REGISTER_HOLDING, CALL_TYPE_REGISTER_INPUT]),
|
||||||
|
vol.Exclusive(CALL_TYPE_COIL, CONF_INPUT_TYPE): cv.positive_int,
|
||||||
|
vol.Exclusive(CONF_REGISTER, CONF_INPUT_TYPE): cv.positive_int,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
SERIAL_SCHEMA = BASE_SCHEMA.extend(
|
SERIAL_SCHEMA = BASE_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_BAUDRATE): cv.positive_int,
|
vol.Required(CONF_BAUDRATE): cv.positive_int,
|
||||||
|
@ -49,6 +94,7 @@ SERIAL_SCHEMA = BASE_SCHEMA.extend(
|
||||||
vol.Required(CONF_STOPBITS): vol.Any(1, 2),
|
vol.Required(CONF_STOPBITS): vol.Any(1, 2),
|
||||||
vol.Required(CONF_TYPE): "serial",
|
vol.Required(CONF_TYPE): "serial",
|
||||||
vol.Optional(CONF_TIMEOUT, default=3): cv.socket_timeout,
|
vol.Optional(CONF_TIMEOUT, default=3): cv.socket_timeout,
|
||||||
|
vol.Optional(CONF_COVERS): vol.All(cv.ensure_list, [COVERS_SCHEMA]),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -59,14 +105,10 @@ ETHERNET_SCHEMA = BASE_SCHEMA.extend(
|
||||||
vol.Required(CONF_TYPE): vol.Any("tcp", "udp", "rtuovertcp"),
|
vol.Required(CONF_TYPE): vol.Any("tcp", "udp", "rtuovertcp"),
|
||||||
vol.Optional(CONF_TIMEOUT, default=3): cv.socket_timeout,
|
vol.Optional(CONF_TIMEOUT, default=3): cv.socket_timeout,
|
||||||
vol.Optional(CONF_DELAY, default=0): cv.positive_int,
|
vol.Optional(CONF_DELAY, default=0): cv.positive_int,
|
||||||
|
vol.Optional(CONF_COVERS): vol.All(cv.ensure_list, [COVERS_SCHEMA]),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
|
||||||
{DOMAIN: vol.All(cv.ensure_list, [vol.Any(SERIAL_SCHEMA, ETHERNET_SCHEMA)])},
|
|
||||||
extra=vol.ALLOW_EXTRA,
|
|
||||||
)
|
|
||||||
|
|
||||||
SERVICE_WRITE_REGISTER_SCHEMA = vol.Schema(
|
SERVICE_WRITE_REGISTER_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Optional(ATTR_HUB, default=DEFAULT_HUB): cv.string,
|
vol.Optional(ATTR_HUB, default=DEFAULT_HUB): cv.string,
|
||||||
|
@ -87,13 +129,30 @@ SERVICE_WRITE_COIL_SCHEMA = vol.Schema(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
DOMAIN: vol.All(
|
||||||
|
cv.ensure_list,
|
||||||
|
[
|
||||||
|
vol.Any(SERIAL_SCHEMA, ETHERNET_SCHEMA),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
},
|
||||||
|
extra=vol.ALLOW_EXTRA,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def 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 conf_hub in config[DOMAIN]:
|
||||||
hub_collect[client_config[CONF_NAME]] = ModbusHub(client_config)
|
hub_collect[conf_hub[CONF_NAME]] = ModbusHub(conf_hub)
|
||||||
|
|
||||||
|
# load platforms
|
||||||
|
for component, conf_key in (("cover", CONF_COVERS),):
|
||||||
|
if conf_key in conf_hub:
|
||||||
|
load_platform(hass, component, DOMAIN, conf_hub, config)
|
||||||
|
|
||||||
def stop_modbus(event):
|
def stop_modbus(event):
|
||||||
"""Stop Modbus service."""
|
"""Stop Modbus service."""
|
||||||
|
|
|
@ -46,6 +46,7 @@ ATTR_UNIT = "unit"
|
||||||
ATTR_VALUE = "value"
|
ATTR_VALUE = "value"
|
||||||
SERVICE_WRITE_COIL = "write_coil"
|
SERVICE_WRITE_COIL = "write_coil"
|
||||||
SERVICE_WRITE_REGISTER = "write_register"
|
SERVICE_WRITE_REGISTER = "write_register"
|
||||||
|
DEFAULT_SCAN_INTERVAL = 15 # seconds
|
||||||
|
|
||||||
# binary_sensor.py
|
# binary_sensor.py
|
||||||
CONF_INPUTS = "inputs"
|
CONF_INPUTS = "inputs"
|
||||||
|
@ -71,3 +72,12 @@ CONF_UNIT = "temperature_unit"
|
||||||
CONF_MAX_TEMP = "max_temp"
|
CONF_MAX_TEMP = "max_temp"
|
||||||
CONF_MIN_TEMP = "min_temp"
|
CONF_MIN_TEMP = "min_temp"
|
||||||
CONF_STEP = "temp_step"
|
CONF_STEP = "temp_step"
|
||||||
|
|
||||||
|
# cover.py
|
||||||
|
CONF_STATE_OPEN = "state_open"
|
||||||
|
CONF_STATE_CLOSED = "state_closed"
|
||||||
|
CONF_STATE_OPENING = "state_opening"
|
||||||
|
CONF_STATE_CLOSING = "state_closing"
|
||||||
|
CONF_STATUS_REGISTER = "status_register"
|
||||||
|
CONF_STATUS_REGISTER_TYPE = "status_register_type"
|
||||||
|
DEFAULT_SLAVE = 1
|
||||||
|
|
244
homeassistant/components/modbus/cover.py
Normal file
244
homeassistant/components/modbus/cover.py
Normal file
|
@ -0,0 +1,244 @@
|
||||||
|
"""Support for Modbus covers."""
|
||||||
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
|
from pymodbus.exceptions import ConnectionException, ModbusException
|
||||||
|
from pymodbus.pdu import ExceptionResponse
|
||||||
|
|
||||||
|
from homeassistant.components.cover import SUPPORT_CLOSE, SUPPORT_OPEN, CoverEntity
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_COVERS,
|
||||||
|
CONF_DEVICE_CLASS,
|
||||||
|
CONF_NAME,
|
||||||
|
CONF_SCAN_INTERVAL,
|
||||||
|
CONF_SLAVE,
|
||||||
|
)
|
||||||
|
from homeassistant.helpers.event import async_track_time_interval
|
||||||
|
from homeassistant.helpers.restore_state import RestoreEntity
|
||||||
|
from homeassistant.helpers.typing import (
|
||||||
|
ConfigType,
|
||||||
|
DiscoveryInfoType,
|
||||||
|
HomeAssistantType,
|
||||||
|
)
|
||||||
|
|
||||||
|
from . import ModbusHub
|
||||||
|
from .const import (
|
||||||
|
CALL_TYPE_COIL,
|
||||||
|
CALL_TYPE_REGISTER_HOLDING,
|
||||||
|
CALL_TYPE_REGISTER_INPUT,
|
||||||
|
CONF_REGISTER,
|
||||||
|
CONF_STATE_CLOSED,
|
||||||
|
CONF_STATE_CLOSING,
|
||||||
|
CONF_STATE_OPEN,
|
||||||
|
CONF_STATE_OPENING,
|
||||||
|
CONF_STATUS_REGISTER,
|
||||||
|
CONF_STATUS_REGISTER_TYPE,
|
||||||
|
MODBUS_DOMAIN,
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_platform(
|
||||||
|
hass: HomeAssistantType,
|
||||||
|
config: ConfigType,
|
||||||
|
async_add_entities,
|
||||||
|
discovery_info: Optional[DiscoveryInfoType] = None,
|
||||||
|
):
|
||||||
|
"""Read configuration and create Modbus cover."""
|
||||||
|
if discovery_info is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
covers = []
|
||||||
|
for cover in discovery_info[CONF_COVERS]:
|
||||||
|
hub: ModbusHub = hass.data[MODBUS_DOMAIN][discovery_info[CONF_NAME]]
|
||||||
|
covers.append(ModbusCover(hub, cover))
|
||||||
|
|
||||||
|
async_add_entities(covers)
|
||||||
|
|
||||||
|
|
||||||
|
class ModbusCover(CoverEntity, RestoreEntity):
|
||||||
|
"""Representation of a Modbus cover."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
hub: ModbusHub,
|
||||||
|
config: Dict[str, Any],
|
||||||
|
):
|
||||||
|
"""Initialize the modbus cover."""
|
||||||
|
self._hub: ModbusHub = hub
|
||||||
|
self._coil = config.get(CALL_TYPE_COIL)
|
||||||
|
self._device_class = config.get(CONF_DEVICE_CLASS)
|
||||||
|
self._name = config[CONF_NAME]
|
||||||
|
self._register = config.get(CONF_REGISTER)
|
||||||
|
self._slave = config[CONF_SLAVE]
|
||||||
|
self._state_closed = config[CONF_STATE_CLOSED]
|
||||||
|
self._state_closing = config[CONF_STATE_CLOSING]
|
||||||
|
self._state_open = config[CONF_STATE_OPEN]
|
||||||
|
self._state_opening = config[CONF_STATE_OPENING]
|
||||||
|
self._status_register = config.get(CONF_STATUS_REGISTER)
|
||||||
|
self._status_register_type = config[CONF_STATUS_REGISTER_TYPE]
|
||||||
|
self._scan_interval = timedelta(seconds=config[CONF_SCAN_INTERVAL])
|
||||||
|
self._value = None
|
||||||
|
self._available = True
|
||||||
|
|
||||||
|
# If we read cover status from coil, and not from optional status register,
|
||||||
|
# we interpret boolean value False as closed cover, and value True as open cover.
|
||||||
|
# Intermediate states are not supported in such a setup.
|
||||||
|
if self._coil is not None and self._status_register is None:
|
||||||
|
self._state_closed = False
|
||||||
|
self._state_open = True
|
||||||
|
self._state_closing = None
|
||||||
|
self._state_opening = None
|
||||||
|
|
||||||
|
# If we read cover status from the main register (i.e., an optional
|
||||||
|
# status register is not specified), we need to make sure the register_type
|
||||||
|
# is set to "holding".
|
||||||
|
if self._register is not None and self._status_register is None:
|
||||||
|
self._status_register = self._register
|
||||||
|
self._status_register_type = CALL_TYPE_REGISTER_HOLDING
|
||||||
|
|
||||||
|
async def async_added_to_hass(self):
|
||||||
|
"""Handle entity which will be added."""
|
||||||
|
state = await self.async_get_last_state()
|
||||||
|
if not state:
|
||||||
|
return
|
||||||
|
self._value = state.state
|
||||||
|
|
||||||
|
async_track_time_interval(
|
||||||
|
self.hass, lambda arg: self._update(), self._scan_interval
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_class(self) -> Optional[str]:
|
||||||
|
"""Return the device class of the sensor."""
|
||||||
|
return self._device_class
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the switch."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_features(self):
|
||||||
|
"""Flag supported features."""
|
||||||
|
return SUPPORT_OPEN | SUPPORT_CLOSE
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self) -> bool:
|
||||||
|
"""Return True if entity is available."""
|
||||||
|
return self._available
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_opening(self):
|
||||||
|
"""Return if the cover is opening or not."""
|
||||||
|
return self._value == self._state_opening
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_closing(self):
|
||||||
|
"""Return if the cover is closing or not."""
|
||||||
|
return self._value == self._state_closing
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_closed(self):
|
||||||
|
"""Return if the cover is closed or not."""
|
||||||
|
return self._value == self._state_closed
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
"""Return True if entity has to be polled for state.
|
||||||
|
|
||||||
|
False if entity pushes its state to HA.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Handle polling directly in this entity
|
||||||
|
return False
|
||||||
|
|
||||||
|
def open_cover(self, **kwargs: Any) -> None:
|
||||||
|
"""Open cover."""
|
||||||
|
if self._coil is not None:
|
||||||
|
self._write_coil(True)
|
||||||
|
else:
|
||||||
|
self._write_register(self._state_open)
|
||||||
|
|
||||||
|
self._update()
|
||||||
|
|
||||||
|
def close_cover(self, **kwargs: Any) -> None:
|
||||||
|
"""Close cover."""
|
||||||
|
if self._coil is not None:
|
||||||
|
self._write_coil(False)
|
||||||
|
else:
|
||||||
|
self._write_register(self._state_closed)
|
||||||
|
|
||||||
|
self._update()
|
||||||
|
|
||||||
|
def _update(self):
|
||||||
|
"""Update the state of the cover."""
|
||||||
|
if self._coil is not None and self._status_register is None:
|
||||||
|
self._value = self._read_coil()
|
||||||
|
else:
|
||||||
|
self._value = self._read_status_register()
|
||||||
|
|
||||||
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
|
def _read_status_register(self) -> Optional[int]:
|
||||||
|
"""Read status register using the Modbus hub slave."""
|
||||||
|
try:
|
||||||
|
if self._status_register_type == CALL_TYPE_REGISTER_INPUT:
|
||||||
|
result = self._hub.read_input_registers(
|
||||||
|
self._slave, self._status_register, 1
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
result = self._hub.read_holding_registers(
|
||||||
|
self._slave, self._status_register, 1
|
||||||
|
)
|
||||||
|
except ConnectionException:
|
||||||
|
self._available = False
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(result, (ModbusException, ExceptionResponse)):
|
||||||
|
self._available = False
|
||||||
|
return
|
||||||
|
|
||||||
|
value = int(result.registers[0])
|
||||||
|
self._available = True
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
def _write_register(self, value):
|
||||||
|
"""Write holding register using the Modbus hub slave."""
|
||||||
|
try:
|
||||||
|
self._hub.write_register(self._slave, self._register, value)
|
||||||
|
except ConnectionException:
|
||||||
|
self._available = False
|
||||||
|
return
|
||||||
|
|
||||||
|
self._available = True
|
||||||
|
|
||||||
|
def _read_coil(self) -> Optional[bool]:
|
||||||
|
"""Read coil using the Modbus hub slave."""
|
||||||
|
try:
|
||||||
|
result = self._hub.read_coils(self._slave, self._coil, 1)
|
||||||
|
except ConnectionException:
|
||||||
|
self._available = False
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(result, (ModbusException, ExceptionResponse)):
|
||||||
|
self._available = False
|
||||||
|
return
|
||||||
|
|
||||||
|
value = bool(result.bits[0])
|
||||||
|
self._available = True
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
def _write_coil(self, value):
|
||||||
|
"""Write coil using the Modbus hub slave."""
|
||||||
|
try:
|
||||||
|
self._hub.write_coil(self._slave, self._coil, value)
|
||||||
|
except ConnectionException:
|
||||||
|
self._available = False
|
||||||
|
return
|
||||||
|
|
||||||
|
self._available = True
|
|
@ -3,5 +3,5 @@
|
||||||
"name": "Modbus",
|
"name": "Modbus",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/modbus",
|
"documentation": "https://www.home-assistant.io/integrations/modbus",
|
||||||
"requirements": ["pymodbus==2.3.0"],
|
"requirements": ["pymodbus==2.3.0"],
|
||||||
"codeowners": ["@adamchengtkc", "@janiversen"]
|
"codeowners": ["@adamchengtkc", "@janiversen", "@vzahradnik"]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue