Allow manual scan and add delay in switch verify. (#50974)

This commit is contained in:
jan iversen 2021-05-24 12:59:55 +02:00 committed by GitHub
parent 2583e4bdc9
commit be13a73db8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 82 additions and 26 deletions

View File

@ -140,6 +140,8 @@ def control_scan_interval(config: dict) -> dict:
for entry in hub[conf_key]: for entry in hub[conf_key]:
scan_interval = entry.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) scan_interval = entry.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
if scan_interval < MINIMUM_SCAN_INTERVAL: if scan_interval < MINIMUM_SCAN_INTERVAL:
if scan_interval == 0:
continue
_LOGGER.warning( _LOGGER.warning(
"%s %s scan_interval(%d) is adjusted to minimum(%d)", "%s %s scan_interval(%d) is adjusted to minimum(%d)",
component, component,
@ -236,6 +238,7 @@ SWITCH_SCHEMA = BASE_COMPONENT_SCHEMA.extend(
), ),
vol.Optional(CONF_STATE_OFF): cv.positive_int, vol.Optional(CONF_STATE_OFF): cv.positive_int,
vol.Optional(CONF_STATE_ON): cv.positive_int, vol.Optional(CONF_STATE_ON): cv.positive_int,
vol.Optional(CONF_DELAY, default=0): cv.positive_int,
} }
), ),
} }

View File

@ -36,7 +36,7 @@ class BasePlatform(Entity):
self._input_type = entry[CONF_INPUT_TYPE] self._input_type = entry[CONF_INPUT_TYPE]
self._value = None self._value = None
self._available = True self._available = True
self._scan_interval = timedelta(seconds=entry[CONF_SCAN_INTERVAL]) self._scan_interval = int(entry[CONF_SCAN_INTERVAL])
@abstractmethod @abstractmethod
async def async_update(self, now=None): async def async_update(self, now=None):
@ -44,7 +44,10 @@ class BasePlatform(Entity):
async def async_base_added_to_hass(self): async def async_base_added_to_hass(self):
"""Handle entity which will be added.""" """Handle entity which will be added."""
async_track_time_interval(self.hass, self.async_update, self._scan_interval) if self._scan_interval > 0:
async_track_time_interval(
self.hass, self.async_update, timedelta(seconds=self._scan_interval)
)
@property @property
def name(self): def name(self):

View File

@ -8,11 +8,13 @@ from homeassistant.const import (
CONF_ADDRESS, CONF_ADDRESS,
CONF_COMMAND_OFF, CONF_COMMAND_OFF,
CONF_COMMAND_ON, CONF_COMMAND_ON,
CONF_DELAY,
CONF_NAME, CONF_NAME,
CONF_SWITCHES, CONF_SWITCHES,
STATE_ON, STATE_ON,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.event import async_call_later
from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
@ -64,6 +66,7 @@ class ModbusSwitch(BasePlatform, SwitchEntity, RestoreEntity):
if config[CONF_VERIFY] is None: if config[CONF_VERIFY] is None:
config[CONF_VERIFY] = {} config[CONF_VERIFY] = {}
self._verify_active = True self._verify_active = True
self._verify_delay = config[CONF_VERIFY].get(CONF_DELAY, 0)
self._verify_address = config[CONF_VERIFY].get( self._verify_address = config[CONF_VERIFY].get(
CONF_ADDRESS, config[CONF_ADDRESS] CONF_ADDRESS, config[CONF_ADDRESS]
) )
@ -87,38 +90,34 @@ class ModbusSwitch(BasePlatform, SwitchEntity, RestoreEntity):
"""Return true if switch is on.""" """Return true if switch is on."""
return self._is_on return self._is_on
async def async_turn_on(self, **kwargs): async def _async_turn(self, command):
"""Set switch on.""" """Evaluate switch result."""
result = await self._hub.async_pymodbus_call( result = await self._hub.async_pymodbus_call(
self._slave, self._address, self._command_on, self._write_type self._slave, self._address, command, self._write_type
) )
if result is None: if result is None:
self._available = False self._available = False
self.async_write_ha_state() self.async_write_ha_state()
return
self._available = True
if not self._verify_active:
self._is_on = command == self._command_on
self.async_write_ha_state()
return
if self._verify_delay:
async_call_later(self.hass, self._verify_delay, self.async_update)
else: else:
self._available = True await self.async_update()
if self._verify_active:
await self.async_update() async def async_turn_on(self, **kwargs):
else: """Set switch on."""
self._is_on = True await self._async_turn(self._command_on)
self.async_write_ha_state()
async def async_turn_off(self, **kwargs): async def async_turn_off(self, **kwargs):
"""Set switch off.""" """Set switch off."""
result = await self._hub.async_pymodbus_call( await self._async_turn(self._command_off)
self._slave, self._address, self._command_off, self._write_type
)
if result is None:
self._available = False
self.async_write_ha_state()
else:
self._available = True
if self._verify_active:
await self.async_update()
else:
self._is_on = False
self.async_write_ha_state()
async def async_update(self, now=None): async def async_update(self, now=None):
"""Update the entity state.""" """Update the entity state."""

View File

@ -1,4 +1,7 @@
"""The tests for the Modbus switch component.""" """The tests for the Modbus switch component."""
from datetime import timedelta
from unittest import mock
from pymodbus.exceptions import ModbusException from pymodbus.exceptions import ModbusException
import pytest import pytest
@ -19,10 +22,12 @@ from homeassistant.const import (
CONF_ADDRESS, CONF_ADDRESS,
CONF_COMMAND_OFF, CONF_COMMAND_OFF,
CONF_COMMAND_ON, CONF_COMMAND_ON,
CONF_DELAY,
CONF_DEVICE_CLASS, CONF_DEVICE_CLASS,
CONF_HOST, CONF_HOST,
CONF_NAME, CONF_NAME,
CONF_PORT, CONF_PORT,
CONF_SCAN_INTERVAL,
CONF_SLAVE, CONF_SLAVE,
CONF_SWITCHES, CONF_SWITCHES,
CONF_TYPE, CONF_TYPE,
@ -32,10 +37,11 @@ from homeassistant.const import (
) )
from homeassistant.core import State from homeassistant.core import State
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
from .conftest import ReadResult, base_config_test, base_test, prepare_service_update from .conftest import ReadResult, base_config_test, base_test, prepare_service_update
from tests.common import mock_restore_cache from tests.common import async_fire_time_changed, mock_restore_cache
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -72,6 +78,7 @@ from tests.common import mock_restore_cache
CONF_ADDRESS: 1235, CONF_ADDRESS: 1235,
CONF_STATE_OFF: 0, CONF_STATE_OFF: 0,
CONF_STATE_ON: 1, CONF_STATE_ON: 1,
CONF_DELAY: 10,
}, },
}, },
{ {
@ -93,6 +100,7 @@ from tests.common import mock_restore_cache
CONF_COMMAND_OFF: 0x00, CONF_COMMAND_OFF: 0x00,
CONF_COMMAND_ON: 0x01, CONF_COMMAND_ON: 0x01,
CONF_DEVICE_CLASS: "switch", CONF_DEVICE_CLASS: "switch",
CONF_SCAN_INTERVAL: 0,
CONF_VERIFY: None, CONF_VERIFY: None,
}, },
], ],
@ -292,3 +300,46 @@ async def test_service_switch_update(hass, mock_pymodbus):
"homeassistant", "update_entity", {"entity_id": entity_id}, blocking=True "homeassistant", "update_entity", {"entity_id": entity_id}, blocking=True
) )
assert hass.states.get(entity_id).state == STATE_OFF assert hass.states.get(entity_id).state == STATE_OFF
async def test_delay_switch(hass, mock_pymodbus):
"""Run test for switch verify delay."""
switch_name = "test_switch"
entity_id = f"{SWITCH_DOMAIN}.{switch_name}"
config = {
MODBUS_DOMAIN: [
{
CONF_TYPE: "tcp",
CONF_HOST: "modbusTestHost",
CONF_PORT: 5501,
CONF_SWITCHES: [
{
CONF_NAME: switch_name,
CONF_ADDRESS: 51,
CONF_SCAN_INTERVAL: 0,
CONF_VERIFY: {
CONF_DELAY: 1,
CONF_INPUT_TYPE: CALL_TYPE_REGISTER_HOLDING,
},
}
],
}
]
}
mock_pymodbus.read_holding_registers.return_value = ReadResult([0x01])
now = dt_util.utcnow()
with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now):
assert await async_setup_component(hass, MODBUS_DOMAIN, config) is True
await hass.async_block_till_done()
await hass.services.async_call(
"switch", "turn_on", service_data={"entity_id": entity_id}
)
await hass.async_block_till_done()
assert hass.states.get(entity_id).state == STATE_OFF
now = now + timedelta(seconds=2)
with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now):
async_fire_time_changed(hass, now)
await hass.async_block_till_done()
assert hass.states.get(entity_id).state == STATE_ON