Add configurable delay between connect and first request in modbus (#50124)

* Activate startup delay.

* Add removal of call_later if HA is stopped.

This is unlikely to happen, but just security measure.

* Removing timing interval.

async_fire_time_changed() needs to be called twice, first time the delay is
ended and second time update() is executed.

* Variable naming.
This commit is contained in:
jan iversen
2021-05-08 13:28:35 +02:00
committed by GitHub
parent e0de6752af
commit 29eb31e9da
2 changed files with 155 additions and 7 deletions
+120
View File
@@ -39,6 +39,7 @@ from homeassistant.const import (
CONF_METHOD,
CONF_NAME,
CONF_PORT,
CONF_SCAN_INTERVAL,
CONF_SENSORS,
CONF_TIMEOUT,
CONF_TYPE,
@@ -160,6 +161,12 @@ async def _config_helper(hass, do_config, caplog):
CONF_TIMEOUT: 30,
CONF_DELAY: 10,
},
{
CONF_TYPE: "tcp",
CONF_HOST: "modbusTestHost",
CONF_PORT: 5501,
CONF_DELAY: 5,
},
],
)
async def test_config_modbus(hass, caplog, do_config, mock_pymodbus):
@@ -467,3 +474,116 @@ async def test_pymodbus_connect_fail(hass, caplog, mock_pymodbus):
await hass.async_block_till_done()
assert len(caplog.records) == 1
assert caplog.records[0].levelname == "ERROR"
async def test_delay(hass, mock_pymodbus):
"""Run test for different read."""
# the purpose of this test is to test startup delay
# We "hijiack" binary_sensor and sensor in order
# to make a proper blackbox test.
config = {
DOMAIN: [
{
CONF_TYPE: "tcp",
CONF_HOST: "modbusTestHost",
CONF_PORT: 5501,
CONF_NAME: TEST_MODBUS_NAME,
CONF_DELAY: 15,
CONF_BINARY_SENSORS: [
{
CONF_INPUT_TYPE: CALL_TYPE_COIL,
CONF_NAME: f"{TEST_SENSOR_NAME}_2",
CONF_ADDRESS: 52,
CONF_SCAN_INTERVAL: 5,
},
{
CONF_INPUT_TYPE: CALL_TYPE_DISCRETE,
CONF_NAME: f"{TEST_SENSOR_NAME}_1",
CONF_ADDRESS: 51,
CONF_SCAN_INTERVAL: 5,
},
],
CONF_SENSORS: [
{
CONF_INPUT_TYPE: CALL_TYPE_REGISTER_HOLDING,
CONF_NAME: f"{TEST_SENSOR_NAME}_3",
CONF_ADDRESS: 53,
CONF_SCAN_INTERVAL: 5,
},
{
CONF_INPUT_TYPE: CALL_TYPE_REGISTER_INPUT,
CONF_NAME: f"{TEST_SENSOR_NAME}_4",
CONF_ADDRESS: 54,
CONF_SCAN_INTERVAL: 5,
},
],
}
]
}
mock_pymodbus.read_coils.return_value = ReadResult([0x01])
mock_pymodbus.read_discrete_inputs.return_value = ReadResult([0x01])
mock_pymodbus.read_holding_registers.return_value = ReadResult([7])
mock_pymodbus.read_input_registers.return_value = ReadResult([7])
now = dt_util.utcnow()
with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now):
assert await async_setup_component(hass, DOMAIN, config) is True
await hass.async_block_till_done()
now = now + timedelta(seconds=10)
with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now):
async_fire_time_changed(hass, now)
await hass.async_block_till_done()
# Check states
entity_id = f"{BINARY_SENSOR_DOMAIN}.{TEST_SENSOR_NAME}_1"
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
entity_id = f"{BINARY_SENSOR_DOMAIN}.{TEST_SENSOR_NAME}_2"
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
entity_id = f"{SENSOR_DOMAIN}.{TEST_SENSOR_NAME}_3"
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
entity_id = f"{SENSOR_DOMAIN}.{TEST_SENSOR_NAME}_4"
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
mock_pymodbus.reset_mock()
data = {
ATTR_HUB: TEST_MODBUS_NAME,
ATTR_UNIT: 17,
ATTR_ADDRESS: 16,
ATTR_STATE: False,
}
await hass.services.async_call(DOMAIN, SERVICE_WRITE_COIL, data, blocking=True)
assert not mock_pymodbus.write_coil.called
await hass.services.async_call(DOMAIN, SERVICE_WRITE_COIL, data, blocking=True)
assert not mock_pymodbus.write_coil.called
data[ATTR_STATE] = [True, False, True]
await hass.services.async_call(DOMAIN, SERVICE_WRITE_COIL, data, blocking=True)
assert not mock_pymodbus.write_coils.called
del data[ATTR_STATE]
data[ATTR_VALUE] = 15
await hass.services.async_call(DOMAIN, SERVICE_WRITE_REGISTER, data, blocking=True)
assert not mock_pymodbus.write_register.called
data[ATTR_VALUE] = [1, 2, 3]
await hass.services.async_call(DOMAIN, SERVICE_WRITE_REGISTER, data, blocking=True)
assert not mock_pymodbus.write_registers.called
# 2 times fire_changed is needed to secure "normal" update is called.
now = now + timedelta(seconds=6)
with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now):
async_fire_time_changed(hass, now)
await hass.async_block_till_done()
now = now + timedelta(seconds=10)
with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now):
async_fire_time_changed(hass, now)
await hass.async_block_till_done()
# Check states
entity_id = f"{BINARY_SENSOR_DOMAIN}.{TEST_SENSOR_NAME}_1"
assert not hass.states.get(entity_id).state == STATE_UNAVAILABLE
entity_id = f"{BINARY_SENSOR_DOMAIN}.{TEST_SENSOR_NAME}_2"
assert not hass.states.get(entity_id).state == STATE_UNAVAILABLE
entity_id = f"{SENSOR_DOMAIN}.{TEST_SENSOR_NAME}_3"
assert not hass.states.get(entity_id).state == STATE_UNAVAILABLE
entity_id = f"{SENSOR_DOMAIN}.{TEST_SENSOR_NAME}_4"
assert not hass.states.get(entity_id).state == STATE_UNAVAILABLE