2020-09-26 16:11:51 +00:00
|
|
|
"""Support for Modbus covers."""
|
2021-03-18 12:07:04 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2021-04-19 14:52:08 +00:00
|
|
|
import logging
|
2021-03-18 12:07:04 +00:00
|
|
|
from typing import Any
|
2020-09-26 16:11:51 +00:00
|
|
|
|
|
|
|
from homeassistant.components.cover import SUPPORT_CLOSE, SUPPORT_OPEN, CoverEntity
|
|
|
|
from homeassistant.const import (
|
|
|
|
CONF_COVERS,
|
|
|
|
CONF_NAME,
|
2021-05-16 06:40:19 +00:00
|
|
|
STATE_CLOSED,
|
|
|
|
STATE_CLOSING,
|
|
|
|
STATE_OPEN,
|
|
|
|
STATE_OPENING,
|
|
|
|
STATE_UNAVAILABLE,
|
|
|
|
STATE_UNKNOWN,
|
2020-09-26 16:11:51 +00:00
|
|
|
)
|
2021-04-19 08:13:32 +00:00
|
|
|
from homeassistant.core import HomeAssistant
|
2020-09-26 16:11:51 +00:00
|
|
|
from homeassistant.helpers.restore_state import RestoreEntity
|
2021-04-19 08:13:32 +00:00
|
|
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
2020-09-26 16:11:51 +00:00
|
|
|
|
2021-05-20 14:56:11 +00:00
|
|
|
from .base_platform import BasePlatform
|
2020-09-26 16:11:51 +00:00
|
|
|
from .const import (
|
|
|
|
CALL_TYPE_COIL,
|
2021-05-17 20:12:18 +00:00
|
|
|
CALL_TYPE_WRITE_COIL,
|
|
|
|
CALL_TYPE_WRITE_REGISTER,
|
2020-09-26 16:11:51 +00:00
|
|
|
CONF_STATE_CLOSED,
|
|
|
|
CONF_STATE_CLOSING,
|
|
|
|
CONF_STATE_OPEN,
|
|
|
|
CONF_STATE_OPENING,
|
|
|
|
CONF_STATUS_REGISTER,
|
|
|
|
CONF_STATUS_REGISTER_TYPE,
|
|
|
|
MODBUS_DOMAIN,
|
|
|
|
)
|
2021-02-12 15:33:18 +00:00
|
|
|
from .modbus import ModbusHub
|
2020-09-26 16:11:51 +00:00
|
|
|
|
2021-05-15 17:54:17 +00:00
|
|
|
PARALLEL_UPDATES = 1
|
2021-04-19 14:52:08 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2020-09-26 16:11:51 +00:00
|
|
|
|
|
|
|
async def async_setup_platform(
|
2021-04-19 08:13:32 +00:00
|
|
|
hass: HomeAssistant,
|
2020-09-26 16:11:51 +00:00
|
|
|
config: ConfigType,
|
|
|
|
async_add_entities,
|
2021-03-18 12:07:04 +00:00
|
|
|
discovery_info: DiscoveryInfoType | None = None,
|
2020-09-26 16:11:51 +00:00
|
|
|
):
|
|
|
|
"""Read configuration and create Modbus cover."""
|
2021-05-28 09:29:37 +00:00
|
|
|
if discovery_info is None: # pragma: no cover
|
2020-09-26 16:11:51 +00:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2021-05-20 14:56:11 +00:00
|
|
|
class ModbusCover(BasePlatform, CoverEntity, RestoreEntity):
|
2020-09-26 16:11:51 +00:00
|
|
|
"""Representation of a Modbus cover."""
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
hub: ModbusHub,
|
2021-03-18 12:07:04 +00:00
|
|
|
config: dict[str, Any],
|
2021-05-20 12:06:44 +00:00
|
|
|
) -> None:
|
2020-09-26 16:11:51 +00:00
|
|
|
"""Initialize the modbus cover."""
|
2021-05-20 14:56:11 +00:00
|
|
|
super().__init__(hub, config)
|
2020-09-26 16:11:51 +00:00
|
|
|
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]
|
|
|
|
|
|
|
|
# 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.
|
2021-05-28 11:23:44 +00:00
|
|
|
if self._input_type == CALL_TYPE_COIL:
|
2021-05-17 20:12:18 +00:00
|
|
|
self._write_type = CALL_TYPE_WRITE_COIL
|
2021-05-28 11:23:44 +00:00
|
|
|
self._write_address = self._address
|
2021-05-17 20:12:18 +00:00
|
|
|
if self._status_register is None:
|
|
|
|
self._state_closed = False
|
|
|
|
self._state_open = True
|
|
|
|
self._state_closing = None
|
|
|
|
self._state_opening = None
|
2021-05-28 11:23:44 +00:00
|
|
|
else:
|
|
|
|
# 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".
|
2021-05-17 20:12:18 +00:00
|
|
|
self._write_type = CALL_TYPE_WRITE_REGISTER
|
2021-05-28 11:23:44 +00:00
|
|
|
self._write_address = self._address
|
|
|
|
if self._status_register:
|
|
|
|
self._address = self._status_register
|
|
|
|
self._input_type = self._status_register_type
|
2020-09-26 16:11:51 +00:00
|
|
|
|
|
|
|
async def async_added_to_hass(self):
|
|
|
|
"""Handle entity which will be added."""
|
2021-05-20 14:56:11 +00:00
|
|
|
await self.async_base_added_to_hass()
|
2020-09-26 16:11:51 +00:00
|
|
|
state = await self.async_get_last_state()
|
2021-04-30 20:36:55 +00:00
|
|
|
if state:
|
2021-05-16 06:40:19 +00:00
|
|
|
convert = {
|
|
|
|
STATE_CLOSED: self._state_closed,
|
|
|
|
STATE_CLOSING: self._state_closing,
|
|
|
|
STATE_OPENING: self._state_opening,
|
|
|
|
STATE_OPEN: self._state_open,
|
|
|
|
STATE_UNAVAILABLE: None,
|
|
|
|
STATE_UNKNOWN: None,
|
|
|
|
}
|
|
|
|
self._value = convert[state.state]
|
2020-09-26 16:11:51 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def supported_features(self):
|
|
|
|
"""Flag supported features."""
|
|
|
|
return SUPPORT_OPEN | SUPPORT_CLOSE
|
|
|
|
|
|
|
|
@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
|
|
|
|
|
2021-05-15 17:54:17 +00:00
|
|
|
async def async_open_cover(self, **kwargs: Any) -> None:
|
2020-09-26 16:11:51 +00:00
|
|
|
"""Open cover."""
|
2021-05-19 08:13:48 +00:00
|
|
|
result = await self._hub.async_pymodbus_call(
|
2021-05-28 11:23:44 +00:00
|
|
|
self._slave, self._write_address, self._state_open, self._write_type
|
2021-05-17 20:12:18 +00:00
|
|
|
)
|
2021-05-19 08:13:48 +00:00
|
|
|
self._available = result is not None
|
2021-05-23 15:18:35 +00:00
|
|
|
await self.async_update()
|
2020-09-26 16:11:51 +00:00
|
|
|
|
2021-05-15 17:54:17 +00:00
|
|
|
async def async_close_cover(self, **kwargs: Any) -> None:
|
2020-09-26 16:11:51 +00:00
|
|
|
"""Close cover."""
|
2021-05-19 08:13:48 +00:00
|
|
|
result = await self._hub.async_pymodbus_call(
|
2021-05-28 11:23:44 +00:00
|
|
|
self._slave, self._write_address, self._state_closed, self._write_type
|
2021-05-17 20:12:18 +00:00
|
|
|
)
|
2021-05-19 08:13:48 +00:00
|
|
|
self._available = result is not None
|
2021-05-23 15:18:35 +00:00
|
|
|
await self.async_update()
|
2020-09-26 16:11:51 +00:00
|
|
|
|
2021-05-15 17:54:17 +00:00
|
|
|
async def async_update(self, now=None):
|
2020-09-26 16:11:51 +00:00
|
|
|
"""Update the state of the cover."""
|
2021-05-15 17:54:17 +00:00
|
|
|
# remark "now" is a dummy parameter to avoid problems with
|
|
|
|
# async_track_time_interval
|
2021-07-13 19:45:42 +00:00
|
|
|
# do not allow multiple active calls to the same platform
|
|
|
|
if self._call_active:
|
|
|
|
return
|
|
|
|
self._call_active = True
|
2021-05-17 20:12:18 +00:00
|
|
|
result = await self._hub.async_pymodbus_call(
|
2021-05-28 11:23:44 +00:00
|
|
|
self._slave, self._address, 1, self._input_type
|
2021-05-17 20:12:18 +00:00
|
|
|
)
|
2021-07-13 19:45:42 +00:00
|
|
|
self._call_active = False
|
2021-04-19 15:18:15 +00:00
|
|
|
if result is None:
|
2020-09-26 16:11:51 +00:00
|
|
|
self._available = False
|
2021-05-28 11:23:44 +00:00
|
|
|
self.async_write_ha_state()
|
2021-04-19 15:18:15 +00:00
|
|
|
return None
|
2020-09-26 16:11:51 +00:00
|
|
|
self._available = True
|
2021-05-28 11:23:44 +00:00
|
|
|
if self._input_type == CALL_TYPE_COIL:
|
|
|
|
self._value = bool(result.bits[0] & 1)
|
|
|
|
else:
|
|
|
|
self._value = int(result.registers[0])
|
|
|
|
self.async_write_ha_state()
|