From be13a73db8b56f64b6f200ab53817dab83d277a3 Mon Sep 17 00:00:00 2001 From: jan iversen Date: Mon, 24 May 2021 12:59:55 +0200 Subject: [PATCH] Allow manual scan and add delay in switch verify. (#50974) --- homeassistant/components/modbus/__init__.py | 3 ++ .../components/modbus/base_platform.py | 7 ++- homeassistant/components/modbus/switch.py | 45 ++++++++-------- .../{test_modbus_switch.py => test_switch.py} | 53 ++++++++++++++++++- 4 files changed, 82 insertions(+), 26 deletions(-) rename tests/components/modbus/{test_modbus_switch.py => test_switch.py} (81%) diff --git a/homeassistant/components/modbus/__init__.py b/homeassistant/components/modbus/__init__.py index c6336739bf2..d1c3c1a0c8a 100644 --- a/homeassistant/components/modbus/__init__.py +++ b/homeassistant/components/modbus/__init__.py @@ -140,6 +140,8 @@ def control_scan_interval(config: dict) -> dict: for entry in hub[conf_key]: scan_interval = entry.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) if scan_interval < MINIMUM_SCAN_INTERVAL: + if scan_interval == 0: + continue _LOGGER.warning( "%s %s scan_interval(%d) is adjusted to minimum(%d)", component, @@ -236,6 +238,7 @@ SWITCH_SCHEMA = BASE_COMPONENT_SCHEMA.extend( ), vol.Optional(CONF_STATE_OFF): cv.positive_int, vol.Optional(CONF_STATE_ON): cv.positive_int, + vol.Optional(CONF_DELAY, default=0): cv.positive_int, } ), } diff --git a/homeassistant/components/modbus/base_platform.py b/homeassistant/components/modbus/base_platform.py index ddfe11717de..d6811bca1ff 100644 --- a/homeassistant/components/modbus/base_platform.py +++ b/homeassistant/components/modbus/base_platform.py @@ -36,7 +36,7 @@ class BasePlatform(Entity): self._input_type = entry[CONF_INPUT_TYPE] self._value = None self._available = True - self._scan_interval = timedelta(seconds=entry[CONF_SCAN_INTERVAL]) + self._scan_interval = int(entry[CONF_SCAN_INTERVAL]) @abstractmethod async def async_update(self, now=None): @@ -44,7 +44,10 @@ class BasePlatform(Entity): async def async_base_added_to_hass(self): """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 def name(self): diff --git a/homeassistant/components/modbus/switch.py b/homeassistant/components/modbus/switch.py index ef068d7bd18..502a6fc73b9 100644 --- a/homeassistant/components/modbus/switch.py +++ b/homeassistant/components/modbus/switch.py @@ -8,11 +8,13 @@ from homeassistant.const import ( CONF_ADDRESS, CONF_COMMAND_OFF, CONF_COMMAND_ON, + CONF_DELAY, CONF_NAME, CONF_SWITCHES, STATE_ON, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers.event import async_call_later from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import ConfigType @@ -64,6 +66,7 @@ class ModbusSwitch(BasePlatform, SwitchEntity, RestoreEntity): if config[CONF_VERIFY] is None: config[CONF_VERIFY] = {} self._verify_active = True + self._verify_delay = config[CONF_VERIFY].get(CONF_DELAY, 0) self._verify_address = config[CONF_VERIFY].get( CONF_ADDRESS, config[CONF_ADDRESS] ) @@ -87,38 +90,34 @@ class ModbusSwitch(BasePlatform, SwitchEntity, RestoreEntity): """Return true if switch is on.""" return self._is_on - async def async_turn_on(self, **kwargs): - """Set switch on.""" - + async def _async_turn(self, command): + """Evaluate switch result.""" 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: self._available = False 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: - self._available = True - if self._verify_active: - await self.async_update() - else: - self._is_on = True - self.async_write_ha_state() + await self.async_update() + + async def async_turn_on(self, **kwargs): + """Set switch on.""" + await self._async_turn(self._command_on) async def async_turn_off(self, **kwargs): """Set switch off.""" - result = await self._hub.async_pymodbus_call( - 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() + await self._async_turn(self._command_off) async def async_update(self, now=None): """Update the entity state.""" diff --git a/tests/components/modbus/test_modbus_switch.py b/tests/components/modbus/test_switch.py similarity index 81% rename from tests/components/modbus/test_modbus_switch.py rename to tests/components/modbus/test_switch.py index 8cb1cf69dc9..37ddfec2b4d 100644 --- a/tests/components/modbus/test_modbus_switch.py +++ b/tests/components/modbus/test_switch.py @@ -1,4 +1,7 @@ """The tests for the Modbus switch component.""" +from datetime import timedelta +from unittest import mock + from pymodbus.exceptions import ModbusException import pytest @@ -19,10 +22,12 @@ from homeassistant.const import ( CONF_ADDRESS, CONF_COMMAND_OFF, CONF_COMMAND_ON, + CONF_DELAY, CONF_DEVICE_CLASS, CONF_HOST, CONF_NAME, CONF_PORT, + CONF_SCAN_INTERVAL, CONF_SLAVE, CONF_SWITCHES, CONF_TYPE, @@ -32,10 +37,11 @@ from homeassistant.const import ( ) from homeassistant.core import State 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 tests.common import mock_restore_cache +from tests.common import async_fire_time_changed, mock_restore_cache @pytest.mark.parametrize( @@ -72,6 +78,7 @@ from tests.common import mock_restore_cache CONF_ADDRESS: 1235, CONF_STATE_OFF: 0, CONF_STATE_ON: 1, + CONF_DELAY: 10, }, }, { @@ -93,6 +100,7 @@ from tests.common import mock_restore_cache CONF_COMMAND_OFF: 0x00, CONF_COMMAND_ON: 0x01, CONF_DEVICE_CLASS: "switch", + CONF_SCAN_INTERVAL: 0, 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 ) 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