core/tests/components/modbus/test_init.py

804 lines
23 KiB
Python
Raw Normal View History

"""The tests for the Modbus init.
This file is responsible for testing:
- pymodbus API
- Functionality of class ModbusHub
- Coverage 100%:
__init__.py
const.py
modbus.py
validators.py
baseplatform.py (only BasePlatform)
It uses binary_sensors/sensors to do black box testing of the read calls.
"""
from datetime import timedelta
2021-04-20 18:25:37 +00:00
import logging
from unittest import mock
from pymodbus.exceptions import ModbusException
from pymodbus.pdu import ExceptionResponse, IllegalFunctionRequest
Allow discovery configuration of modbus platforms (#46591) * Change modbus configuration to new style. The old (frozen) configuration is still supported, but when detected a big warning is issued that it will soon be removed. This allows users to change their configuration at their pace. Clean configuration SCHEMAs and move common modbus parts to MODBUS_SCHEMA (renamed from BASE_SCHEMA). Add BASE_COMPONENT_SCHEMA to ensure common configuration of components. All component define e.g. NAME, move these to a common schema. change components (binary_sensor, sensor, switch) to new config Add test set for modbus itself (old config and discovery_info). Add test of devices discovery_info configuration * Update discovery_info configuration for binary_sensor. * Update discovery_info configuration for sensor. * Update discovery_info configuration for switch. * Review comments. * update due to change in core * flake8 problem. * Correct log message. * add should_poll property. * Fix polling for Modbus binary sensor * Fix polling for Modbus sensor * Fix polling for Modbus switch * Fix switch. * Fix pytest errors. * Update homeassistant/components/modbus/binary_sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/binary_sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/modbus.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/switch.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/switch.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/switch.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * ToogleEntity -> SwitchEntity and add abastract * Update homeassistant/components/modbus/switch.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update tests/components/modbus/test_init.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * removed if/else in test. * Remove other if. Co-authored-by: Vladimir Zahradnik <vladimir@zahradnik.io> Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2021-03-27 21:48:06 +00:00
import pytest
import voluptuous as vol
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
2021-04-20 18:25:37 +00:00
from homeassistant.components.modbus.const import (
ATTR_ADDRESS,
ATTR_HUB,
ATTR_STATE,
ATTR_UNIT,
ATTR_VALUE,
CALL_TYPE_COIL,
CALL_TYPE_DISCRETE,
CALL_TYPE_REGISTER_HOLDING,
CALL_TYPE_REGISTER_INPUT,
CALL_TYPE_WRITE_COIL,
CALL_TYPE_WRITE_COILS,
CALL_TYPE_WRITE_REGISTER,
CALL_TYPE_WRITE_REGISTERS,
2021-04-20 18:25:37 +00:00
CONF_BAUDRATE,
CONF_BYTESIZE,
CONF_DATA_TYPE,
CONF_INPUT_TYPE,
CONF_MSG_WAIT,
2021-04-20 18:25:37 +00:00
CONF_PARITY,
CONF_STOPBITS,
CONF_SWAP,
CONF_SWAP_BYTE,
CONF_SWAP_WORD,
DEFAULT_SCAN_INTERVAL,
2021-04-20 18:25:37 +00:00
MODBUS_DOMAIN as DOMAIN,
RTUOVERTCP,
SERIAL,
SERVICE_RESTART,
SERVICE_STOP,
SERVICE_WRITE_COIL,
SERVICE_WRITE_REGISTER,
TCP,
UDP,
2021-10-15 05:09:59 +00:00
DataType,
2021-04-20 18:25:37 +00:00
)
from homeassistant.components.modbus.validators import (
duplicate_entity_validator,
duplicate_modbus_validator,
number_validator,
struct_validator,
)
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
2021-04-20 18:25:37 +00:00
from homeassistant.const import (
CONF_ADDRESS,
CONF_BINARY_SENSORS,
CONF_COUNT,
2021-04-20 18:25:37 +00:00
CONF_DELAY,
CONF_HOST,
CONF_METHOD,
CONF_NAME,
CONF_PORT,
CONF_SCAN_INTERVAL,
CONF_SENSORS,
CONF_STRUCTURE,
2021-04-20 18:25:37 +00:00
CONF_TIMEOUT,
CONF_TYPE,
2021-08-28 06:11:58 +00:00
EVENT_HOMEASSISTANT_STOP,
STATE_ON,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
2021-04-20 18:25:37 +00:00
)
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
2021-08-17 18:43:27 +00:00
from .conftest import (
TEST_ENTITY_NAME,
TEST_MODBUS_HOST,
TEST_MODBUS_NAME,
TEST_PORT_SERIAL,
TEST_PORT_TCP,
ReadResult,
)
from tests.common import async_fire_time_changed
2021-10-20 11:38:55 +00:00
@pytest.fixture(name="mock_modbus_with_pymodbus")
async def mock_modbus_with_pymodbus_fixture(hass, caplog, do_config, mock_pymodbus):
"""Load integration modbus using mocked pymodbus."""
caplog.clear()
caplog.set_level(logging.ERROR)
config = {DOMAIN: do_config}
assert await async_setup_component(hass, DOMAIN, config) is True
await hass.async_block_till_done()
assert DOMAIN in hass.config.components
assert caplog.text == ""
yield mock_pymodbus
Allow discovery configuration of modbus platforms (#46591) * Change modbus configuration to new style. The old (frozen) configuration is still supported, but when detected a big warning is issued that it will soon be removed. This allows users to change their configuration at their pace. Clean configuration SCHEMAs and move common modbus parts to MODBUS_SCHEMA (renamed from BASE_SCHEMA). Add BASE_COMPONENT_SCHEMA to ensure common configuration of components. All component define e.g. NAME, move these to a common schema. change components (binary_sensor, sensor, switch) to new config Add test set for modbus itself (old config and discovery_info). Add test of devices discovery_info configuration * Update discovery_info configuration for binary_sensor. * Update discovery_info configuration for sensor. * Update discovery_info configuration for switch. * Review comments. * update due to change in core * flake8 problem. * Correct log message. * add should_poll property. * Fix polling for Modbus binary sensor * Fix polling for Modbus sensor * Fix polling for Modbus switch * Fix switch. * Fix pytest errors. * Update homeassistant/components/modbus/binary_sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/binary_sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/modbus.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/switch.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/switch.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/switch.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * ToogleEntity -> SwitchEntity and add abastract * Update homeassistant/components/modbus/switch.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update tests/components/modbus/test_init.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * removed if/else in test. * Remove other if. Co-authored-by: Vladimir Zahradnik <vladimir@zahradnik.io> Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2021-03-27 21:48:06 +00:00
async def test_number_validator():
"""Test number validator."""
2021-10-20 11:38:55 +00:00
for value, value_type in (
(15, int),
(15.1, float),
("15", int),
("15.1", float),
(-15, int),
(-15.1, float),
("-15", int),
("-15.1", float),
2021-10-20 11:38:55 +00:00
):
assert isinstance(number_validator(value), value_type)
Allow discovery configuration of modbus platforms (#46591) * Change modbus configuration to new style. The old (frozen) configuration is still supported, but when detected a big warning is issued that it will soon be removed. This allows users to change their configuration at their pace. Clean configuration SCHEMAs and move common modbus parts to MODBUS_SCHEMA (renamed from BASE_SCHEMA). Add BASE_COMPONENT_SCHEMA to ensure common configuration of components. All component define e.g. NAME, move these to a common schema. change components (binary_sensor, sensor, switch) to new config Add test set for modbus itself (old config and discovery_info). Add test of devices discovery_info configuration * Update discovery_info configuration for binary_sensor. * Update discovery_info configuration for sensor. * Update discovery_info configuration for switch. * Review comments. * update due to change in core * flake8 problem. * Correct log message. * add should_poll property. * Fix polling for Modbus binary sensor * Fix polling for Modbus sensor * Fix polling for Modbus switch * Fix switch. * Fix pytest errors. * Update homeassistant/components/modbus/binary_sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/binary_sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/modbus.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/switch.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/switch.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/switch.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * ToogleEntity -> SwitchEntity and add abastract * Update homeassistant/components/modbus/switch.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update tests/components/modbus/test_init.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * removed if/else in test. * Remove other if. Co-authored-by: Vladimir Zahradnik <vladimir@zahradnik.io> Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2021-03-27 21:48:06 +00:00
try:
number_validator("x15.1")
Allow discovery configuration of modbus platforms (#46591) * Change modbus configuration to new style. The old (frozen) configuration is still supported, but when detected a big warning is issued that it will soon be removed. This allows users to change their configuration at their pace. Clean configuration SCHEMAs and move common modbus parts to MODBUS_SCHEMA (renamed from BASE_SCHEMA). Add BASE_COMPONENT_SCHEMA to ensure common configuration of components. All component define e.g. NAME, move these to a common schema. change components (binary_sensor, sensor, switch) to new config Add test set for modbus itself (old config and discovery_info). Add test of devices discovery_info configuration * Update discovery_info configuration for binary_sensor. * Update discovery_info configuration for sensor. * Update discovery_info configuration for switch. * Review comments. * update due to change in core * flake8 problem. * Correct log message. * add should_poll property. * Fix polling for Modbus binary sensor * Fix polling for Modbus sensor * Fix polling for Modbus switch * Fix switch. * Fix pytest errors. * Update homeassistant/components/modbus/binary_sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/binary_sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/modbus.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/switch.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/switch.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/modbus/switch.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * ToogleEntity -> SwitchEntity and add abastract * Update homeassistant/components/modbus/switch.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update tests/components/modbus/test_init.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * removed if/else in test. * Remove other if. Co-authored-by: Vladimir Zahradnik <vladimir@zahradnik.io> Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2021-03-27 21:48:06 +00:00
except (vol.Invalid):
return
pytest.fail("Number_validator not throwing exception")
2021-04-20 18:25:37 +00:00
@pytest.mark.parametrize(
"do_config",
[
{
2021-08-17 18:43:27 +00:00
CONF_NAME: TEST_ENTITY_NAME,
CONF_COUNT: 2,
2021-10-15 05:09:59 +00:00
CONF_DATA_TYPE: DataType.STRING,
},
{
2021-08-17 18:43:27 +00:00
CONF_NAME: TEST_ENTITY_NAME,
CONF_COUNT: 2,
2021-10-15 05:09:59 +00:00
CONF_DATA_TYPE: DataType.INT,
},
{
2021-08-17 18:43:27 +00:00
CONF_NAME: TEST_ENTITY_NAME,
CONF_COUNT: 2,
2021-10-15 05:09:59 +00:00
CONF_DATA_TYPE: DataType.INT,
CONF_SWAP: CONF_SWAP_BYTE,
},
{
CONF_NAME: TEST_ENTITY_NAME,
CONF_COUNT: 2,
2021-10-15 05:09:59 +00:00
CONF_DATA_TYPE: DataType.CUSTOM,
CONF_STRUCTURE: ">i",
CONF_SWAP: CONF_SWAP_BYTE,
},
],
)
async def test_ok_struct_validator(do_config):
"""Test struct validator."""
try:
struct_validator(do_config)
except vol.Invalid:
pytest.fail("struct_validator unexpected exception")
@pytest.mark.parametrize(
"do_config",
[
{
2021-08-17 18:43:27 +00:00
CONF_NAME: TEST_ENTITY_NAME,
CONF_COUNT: 8,
2021-10-15 05:09:59 +00:00
CONF_DATA_TYPE: DataType.INT,
},
{
2021-08-17 18:43:27 +00:00
CONF_NAME: TEST_ENTITY_NAME,
CONF_COUNT: 8,
2021-10-15 05:09:59 +00:00
CONF_DATA_TYPE: DataType.CUSTOM,
},
{
2021-08-17 18:43:27 +00:00
CONF_NAME: TEST_ENTITY_NAME,
CONF_COUNT: 8,
2021-10-15 05:09:59 +00:00
CONF_DATA_TYPE: DataType.CUSTOM,
CONF_STRUCTURE: "no good",
},
{
2021-08-17 18:43:27 +00:00
CONF_NAME: TEST_ENTITY_NAME,
CONF_COUNT: 20,
2021-10-15 05:09:59 +00:00
CONF_DATA_TYPE: DataType.CUSTOM,
CONF_STRUCTURE: ">f",
},
{
2021-08-17 18:43:27 +00:00
CONF_NAME: TEST_ENTITY_NAME,
CONF_COUNT: 1,
2021-10-15 05:09:59 +00:00
CONF_DATA_TYPE: DataType.CUSTOM,
CONF_STRUCTURE: ">f",
CONF_SWAP: CONF_SWAP_WORD,
},
{
CONF_NAME: TEST_ENTITY_NAME,
CONF_COUNT: 1,
2021-10-15 05:09:59 +00:00
CONF_DATA_TYPE: DataType.STRING,
CONF_STRUCTURE: ">f",
CONF_SWAP: CONF_SWAP_WORD,
},
],
)
async def test_exception_struct_validator(do_config):
"""Test struct validator."""
try:
struct_validator(do_config)
except vol.Invalid:
return
pytest.fail("struct_validator missing exception")
@pytest.mark.parametrize(
"do_config",
[
[
{
CONF_NAME: TEST_MODBUS_NAME,
CONF_TYPE: TCP,
CONF_HOST: TEST_MODBUS_HOST,
CONF_PORT: TEST_PORT_TCP,
},
{
CONF_NAME: TEST_MODBUS_NAME,
CONF_TYPE: TCP,
CONF_HOST: TEST_MODBUS_HOST + "2",
CONF_PORT: TEST_PORT_TCP,
},
],
[
{
CONF_NAME: TEST_MODBUS_NAME,
CONF_TYPE: TCP,
CONF_HOST: TEST_MODBUS_HOST,
CONF_PORT: TEST_PORT_TCP,
},
{
CONF_NAME: TEST_MODBUS_NAME + "2",
CONF_TYPE: TCP,
CONF_HOST: TEST_MODBUS_HOST,
CONF_PORT: TEST_PORT_TCP,
},
],
],
)
async def test_duplicate_modbus_validator(do_config):
"""Test duplicate modbus validator."""
duplicate_modbus_validator(do_config)
assert len(do_config) == 1
@pytest.mark.parametrize(
"do_config",
[
[
{
CONF_NAME: TEST_MODBUS_NAME,
CONF_TYPE: TCP,
CONF_HOST: TEST_MODBUS_HOST,
CONF_PORT: TEST_PORT_TCP,
CONF_SENSORS: [
{
CONF_NAME: TEST_ENTITY_NAME,
CONF_ADDRESS: 117,
},
{
CONF_NAME: TEST_ENTITY_NAME,
CONF_ADDRESS: 119,
},
],
}
],
[
{
CONF_NAME: TEST_MODBUS_NAME,
CONF_TYPE: TCP,
CONF_HOST: TEST_MODBUS_HOST,
CONF_PORT: TEST_PORT_TCP,
CONF_SENSORS: [
{
CONF_NAME: TEST_ENTITY_NAME,
CONF_ADDRESS: 117,
},
{
CONF_NAME: TEST_ENTITY_NAME + "2",
CONF_ADDRESS: 117,
},
],
}
],
],
)
async def test_duplicate_entity_validator(do_config):
"""Test duplicate entity validator."""
duplicate_entity_validator(do_config)
assert len(do_config[0][CONF_SENSORS]) == 1
2021-04-20 18:25:37 +00:00
@pytest.mark.parametrize(
"do_config",
[
{
CONF_TYPE: TCP,
2021-08-17 18:43:27 +00:00
CONF_HOST: TEST_MODBUS_HOST,
CONF_PORT: TEST_PORT_TCP,
2021-04-20 18:25:37 +00:00
},
{
CONF_TYPE: TCP,
2021-08-17 18:43:27 +00:00
CONF_HOST: TEST_MODBUS_HOST,
CONF_PORT: TEST_PORT_TCP,
CONF_NAME: TEST_MODBUS_NAME,
2021-04-20 18:25:37 +00:00
CONF_TIMEOUT: 30,
CONF_DELAY: 10,
},
{
CONF_TYPE: UDP,
2021-08-17 18:43:27 +00:00
CONF_HOST: TEST_MODBUS_HOST,
CONF_PORT: TEST_PORT_TCP,
2021-04-20 18:25:37 +00:00
},
{
CONF_TYPE: UDP,
2021-08-17 18:43:27 +00:00
CONF_HOST: TEST_MODBUS_HOST,
CONF_PORT: TEST_PORT_TCP,
CONF_NAME: TEST_MODBUS_NAME,
2021-04-20 18:25:37 +00:00
CONF_TIMEOUT: 30,
CONF_DELAY: 10,
},
{
CONF_TYPE: RTUOVERTCP,
2021-08-17 18:43:27 +00:00
CONF_HOST: TEST_MODBUS_HOST,
CONF_PORT: TEST_PORT_TCP,
2021-04-20 18:25:37 +00:00
},
{
CONF_TYPE: RTUOVERTCP,
2021-08-17 18:43:27 +00:00
CONF_HOST: TEST_MODBUS_HOST,
CONF_PORT: TEST_PORT_TCP,
CONF_NAME: TEST_MODBUS_NAME,
2021-04-20 18:25:37 +00:00
CONF_TIMEOUT: 30,
CONF_DELAY: 10,
},
{
CONF_TYPE: SERIAL,
2021-04-20 18:25:37 +00:00
CONF_BAUDRATE: 9600,
CONF_BYTESIZE: 8,
CONF_METHOD: "rtu",
2021-08-17 18:43:27 +00:00
CONF_PORT: TEST_PORT_SERIAL,
2021-04-20 18:25:37 +00:00
CONF_PARITY: "E",
CONF_STOPBITS: 1,
CONF_MSG_WAIT: 100,
2021-04-20 18:25:37 +00:00
},
{
CONF_TYPE: SERIAL,
2021-04-20 18:25:37 +00:00
CONF_BAUDRATE: 9600,
CONF_BYTESIZE: 8,
CONF_METHOD: "rtu",
2021-08-17 18:43:27 +00:00
CONF_PORT: TEST_PORT_SERIAL,
2021-04-20 18:25:37 +00:00
CONF_PARITY: "E",
CONF_STOPBITS: 1,
CONF_NAME: TEST_MODBUS_NAME,
2021-04-20 18:25:37 +00:00
CONF_TIMEOUT: 30,
CONF_DELAY: 10,
},
{
CONF_TYPE: TCP,
2021-08-17 18:43:27 +00:00
CONF_HOST: TEST_MODBUS_HOST,
CONF_PORT: TEST_PORT_TCP,
CONF_DELAY: 5,
},
[
{
CONF_TYPE: TCP,
2021-08-17 18:43:27 +00:00
CONF_HOST: TEST_MODBUS_HOST,
CONF_PORT: TEST_PORT_TCP,
CONF_NAME: TEST_MODBUS_NAME,
},
{
CONF_TYPE: TCP,
2021-08-17 18:43:27 +00:00
CONF_HOST: TEST_MODBUS_HOST,
CONF_PORT: TEST_PORT_TCP,
CONF_NAME: f"{TEST_MODBUS_NAME}2",
},
{
CONF_TYPE: SERIAL,
CONF_BAUDRATE: 9600,
CONF_BYTESIZE: 8,
CONF_METHOD: "rtu",
2021-08-17 18:43:27 +00:00
CONF_PORT: TEST_PORT_SERIAL,
CONF_PARITY: "E",
CONF_STOPBITS: 1,
2021-08-17 18:43:27 +00:00
CONF_NAME: f"{TEST_MODBUS_NAME}3",
},
],
{
# Special test for scan_interval validator with scan_interval: 0
CONF_TYPE: TCP,
2021-08-17 18:43:27 +00:00
CONF_HOST: TEST_MODBUS_HOST,
CONF_PORT: TEST_PORT_TCP,
CONF_SENSORS: [
{
2021-08-17 18:43:27 +00:00
CONF_NAME: TEST_ENTITY_NAME,
CONF_ADDRESS: 117,
CONF_SCAN_INTERVAL: 0,
}
],
},
2021-04-20 18:25:37 +00:00
],
)
async def test_config_modbus(hass, caplog, mock_modbus_with_pymodbus):
"""Run configuration test for modbus."""
2021-04-20 18:25:37 +00:00
VALUE = "value"
FUNC = "func"
DATA = "data"
SERVICE = "service"
@pytest.mark.parametrize(
"do_config",
[
{
CONF_NAME: TEST_MODBUS_NAME,
CONF_TYPE: SERIAL,
CONF_BAUDRATE: 9600,
CONF_BYTESIZE: 8,
CONF_METHOD: "rtu",
2021-08-17 18:43:27 +00:00
CONF_PORT: TEST_PORT_SERIAL,
CONF_PARITY: "E",
CONF_STOPBITS: 1,
},
],
)
@pytest.mark.parametrize(
"do_write",
[
2021-04-20 18:25:37 +00:00
{
DATA: ATTR_VALUE,
VALUE: 15,
SERVICE: SERVICE_WRITE_REGISTER,
FUNC: CALL_TYPE_WRITE_REGISTER,
2021-04-20 18:25:37 +00:00
},
{
DATA: ATTR_VALUE,
VALUE: [1, 2, 3],
SERVICE: SERVICE_WRITE_REGISTER,
FUNC: CALL_TYPE_WRITE_REGISTERS,
2021-04-20 18:25:37 +00:00
},
{
DATA: ATTR_STATE,
VALUE: False,
SERVICE: SERVICE_WRITE_COIL,
FUNC: CALL_TYPE_WRITE_COIL,
2021-04-20 18:25:37 +00:00
},
{
DATA: ATTR_STATE,
VALUE: [True, False, True],
SERVICE: SERVICE_WRITE_COIL,
FUNC: CALL_TYPE_WRITE_COILS,
},
],
)
@pytest.mark.parametrize(
"do_return",
[
{VALUE: ReadResult([0x0001]), DATA: ""},
{VALUE: ExceptionResponse(0x06), DATA: "Pymodbus:"},
{VALUE: IllegalFunctionRequest(0x06), DATA: "Pymodbus:"},
{VALUE: ModbusException("fail write_"), DATA: "Pymodbus:"},
],
)
async def test_pb_service_write(
hass, do_write, do_return, caplog, mock_modbus_with_pymodbus
):
"""Run test for service write_register."""
func_name = {
CALL_TYPE_WRITE_COIL: mock_modbus_with_pymodbus.write_coil,
CALL_TYPE_WRITE_COILS: mock_modbus_with_pymodbus.write_coils,
CALL_TYPE_WRITE_REGISTER: mock_modbus_with_pymodbus.write_register,
CALL_TYPE_WRITE_REGISTERS: mock_modbus_with_pymodbus.write_registers,
}
data = {
ATTR_HUB: TEST_MODBUS_NAME,
ATTR_UNIT: 17,
ATTR_ADDRESS: 16,
do_write[DATA]: do_write[VALUE],
}
mock_modbus_with_pymodbus.reset_mock()
caplog.clear()
caplog.set_level(logging.DEBUG)
func_name[do_write[FUNC]].return_value = do_return[VALUE]
await hass.services.async_call(DOMAIN, do_write[SERVICE], data, blocking=True)
assert func_name[do_write[FUNC]].called
assert func_name[do_write[FUNC]].call_args[0] == (
data[ATTR_ADDRESS],
data[do_write[DATA]],
)
if do_return[DATA]:
assert caplog.messages[-1].startswith("Pymodbus:")
2021-10-20 11:38:55 +00:00
@pytest.fixture(name="mock_modbus_read_pymodbus")
async def mock_modbus_read_pymodbus_fixture(
hass,
do_group,
do_type,
do_scan_interval,
do_return,
do_exception,
caplog,
mock_pymodbus,
):
"""Load integration modbus using mocked pymodbus."""
caplog.clear()
caplog.set_level(logging.ERROR)
mock_pymodbus.read_coils.side_effect = do_exception
mock_pymodbus.read_discrete_inputs.side_effect = do_exception
mock_pymodbus.read_input_registers.side_effect = do_exception
mock_pymodbus.read_holding_registers.side_effect = do_exception
mock_pymodbus.read_coils.return_value = do_return
mock_pymodbus.read_discrete_inputs.return_value = do_return
mock_pymodbus.read_input_registers.return_value = do_return
mock_pymodbus.read_holding_registers.return_value = do_return
config = {
DOMAIN: [
{
CONF_TYPE: TCP,
2021-08-17 18:43:27 +00:00
CONF_HOST: TEST_MODBUS_HOST,
CONF_PORT: TEST_PORT_TCP,
CONF_NAME: TEST_MODBUS_NAME,
do_group: [
{
CONF_INPUT_TYPE: do_type,
2021-08-17 18:43:27 +00:00
CONF_NAME: TEST_ENTITY_NAME,
CONF_ADDRESS: 51,
CONF_SCAN_INTERVAL: do_scan_interval,
}
],
}
],
}
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()
assert DOMAIN in hass.config.components
assert caplog.text == ""
now = now + timedelta(seconds=DEFAULT_SCAN_INTERVAL + 60)
with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now):
async_fire_time_changed(hass, now)
await hass.async_block_till_done()
yield mock_pymodbus
@pytest.mark.parametrize(
"do_domain, do_group,do_type,do_scan_interval",
[
[SENSOR_DOMAIN, CONF_SENSORS, CALL_TYPE_REGISTER_HOLDING, 10],
[SENSOR_DOMAIN, CONF_SENSORS, CALL_TYPE_REGISTER_INPUT, 10],
[BINARY_SENSOR_DOMAIN, CONF_BINARY_SENSORS, CALL_TYPE_DISCRETE, 10],
[BINARY_SENSOR_DOMAIN, CONF_BINARY_SENSORS, CALL_TYPE_COIL, 1],
],
)
@pytest.mark.parametrize(
"do_return,do_exception,do_expect_state,do_expect_value",
[
[ReadResult([1]), None, STATE_ON, "1"],
[IllegalFunctionRequest(0x99), None, STATE_UNAVAILABLE, STATE_UNAVAILABLE],
[ExceptionResponse(0x99), None, STATE_UNAVAILABLE, STATE_UNAVAILABLE],
[
ReadResult([1]),
ModbusException("fail read_"),
STATE_UNAVAILABLE,
STATE_UNAVAILABLE,
],
],
)
async def test_pb_read(
hass, do_domain, do_expect_state, do_expect_value, caplog, mock_modbus_read_pymodbus
):
"""Run test for different read."""
# Check state
2021-08-17 18:43:27 +00:00
entity_id = f"{do_domain}.{TEST_ENTITY_NAME}"
state = hass.states.get(entity_id).state
assert hass.states.get(entity_id).state
# this if is needed to avoid explode the
if do_domain == SENSOR_DOMAIN:
do_expect = do_expect_value
else:
do_expect = do_expect_state
assert state == do_expect
async def test_pymodbus_constructor_fail(hass, caplog):
"""Run test for failing pymodbus constructor."""
config = {
DOMAIN: [
{
2021-08-27 16:26:57 +00:00
CONF_NAME: TEST_MODBUS_NAME,
CONF_TYPE: TCP,
2021-08-17 18:43:27 +00:00
CONF_HOST: TEST_MODBUS_HOST,
CONF_PORT: TEST_PORT_TCP,
}
]
}
with mock.patch(
"homeassistant.components.modbus.modbus.ModbusTcpClient", autospec=True
) as mock_pb:
caplog.set_level(logging.ERROR)
mock_pb.side_effect = ModbusException("test no class")
assert await async_setup_component(hass, DOMAIN, config) is False
await hass.async_block_till_done()
2021-08-27 16:26:57 +00:00
message = f"Pymodbus: {TEST_MODBUS_NAME}: Modbus Error: test"
assert caplog.messages[0].startswith(message)
assert caplog.records[0].levelname == "ERROR"
assert mock_pb.called
async def test_pymodbus_close_fail(hass, caplog, mock_pymodbus):
"""Run test for failing pymodbus close."""
config = {
DOMAIN: [
{
CONF_TYPE: TCP,
2021-08-17 18:43:27 +00:00
CONF_HOST: TEST_MODBUS_HOST,
CONF_PORT: TEST_PORT_TCP,
}
]
}
caplog.set_level(logging.ERROR)
mock_pymodbus.connect.return_value = True
mock_pymodbus.close.side_effect = ModbusException("close fail")
assert await async_setup_component(hass, DOMAIN, config) is True
await hass.async_block_till_done()
# Close() is called as part of teardown
async def test_pymodbus_connect_fail(hass, caplog, mock_pymodbus):
"""Run test for failing pymodbus constructor."""
config = {
DOMAIN: [
{
CONF_NAME: TEST_MODBUS_NAME,
CONF_TYPE: TCP,
CONF_HOST: TEST_MODBUS_HOST,
CONF_PORT: TEST_PORT_TCP,
}
]
}
caplog.set_level(logging.WARNING)
ExceptionMessage = "test connect exception"
mock_pymodbus.connect.side_effect = ModbusException(ExceptionMessage)
assert await async_setup_component(hass, DOMAIN, config) is False
assert ExceptionMessage in caplog.text
async def test_delay(hass, mock_pymodbus):
"""Run test for startup delay."""
# the purpose of this test is to test startup delay
# We "hijiack" a binary_sensor to make a proper blackbox test.
2021-10-20 11:38:55 +00:00
set_delay = 15
set_scan_interval = 5
2021-08-17 18:43:27 +00:00
entity_id = f"{BINARY_SENSOR_DOMAIN}.{TEST_ENTITY_NAME}"
config = {
DOMAIN: [
{
CONF_TYPE: TCP,
2021-08-17 18:43:27 +00:00
CONF_HOST: TEST_MODBUS_HOST,
CONF_PORT: TEST_PORT_TCP,
CONF_NAME: TEST_MODBUS_NAME,
2021-10-20 11:38:55 +00:00
CONF_DELAY: set_delay,
CONF_BINARY_SENSORS: [
{
CONF_INPUT_TYPE: CALL_TYPE_COIL,
2021-08-17 18:43:27 +00:00
CONF_NAME: TEST_ENTITY_NAME,
CONF_ADDRESS: 52,
2021-10-20 11:38:55 +00:00
CONF_SCAN_INTERVAL: set_scan_interval,
},
],
}
]
}
mock_pymodbus.read_coils.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, DOMAIN, config) is True
await hass.async_block_till_done()
# pass first scan_interval
start_time = now
2021-10-20 11:38:55 +00:00
now = now + timedelta(seconds=(set_scan_interval + 1))
with mock.patch(
"homeassistant.helpers.event.dt_util.utcnow", return_value=now, autospec=True
):
async_fire_time_changed(hass, now)
await hass.async_block_till_done()
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
2021-10-20 11:38:55 +00:00
stop_time = start_time + timedelta(seconds=(set_delay + 1))
step_timedelta = timedelta(seconds=1)
while now < stop_time:
now = now + step_timedelta
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_UNAVAILABLE
now = now + step_timedelta + 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
2021-08-28 06:11:58 +00:00
@pytest.mark.parametrize(
"do_config",
[
{
CONF_TYPE: TCP,
CONF_HOST: TEST_MODBUS_HOST,
CONF_PORT: TEST_PORT_TCP,
CONF_SENSORS: [
{
CONF_NAME: TEST_ENTITY_NAME,
CONF_ADDRESS: 117,
CONF_SCAN_INTERVAL: 0,
}
],
},
],
)
async def test_shutdown(hass, caplog, mock_pymodbus, mock_modbus_with_pymodbus):
"""Run test for shutdown."""
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
await hass.async_block_till_done()
await hass.async_block_till_done()
assert mock_pymodbus.close.called
assert caplog.text == ""
@pytest.mark.parametrize(
"do_config",
[
{
CONF_SENSORS: [
{
CONF_NAME: TEST_ENTITY_NAME,
CONF_ADDRESS: 51,
}
]
},
],
)
async def test_stop_restart(hass, caplog, mock_modbus):
"""Run test for service stop."""
caplog.set_level(logging.INFO)
entity_id = f"{SENSOR_DOMAIN}.{TEST_ENTITY_NAME}"
assert hass.states.get(entity_id).state == STATE_UNKNOWN
hass.states.async_set(entity_id, 17)
await hass.async_block_till_done()
assert hass.states.get(entity_id).state == "17"
mock_modbus.reset_mock()
caplog.clear()
data = {
ATTR_HUB: TEST_MODBUS_NAME,
}
await hass.services.async_call(DOMAIN, SERVICE_STOP, data, blocking=True)
await hass.async_block_till_done()
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
assert mock_modbus.close.called
assert f"modbus {TEST_MODBUS_NAME} communication closed" in caplog.text
mock_modbus.reset_mock()
caplog.clear()
await hass.services.async_call(DOMAIN, SERVICE_RESTART, data, blocking=True)
await hass.async_block_till_done()
assert not mock_modbus.close.called
assert mock_modbus.connect.called
assert f"modbus {TEST_MODBUS_NAME} communication open" in caplog.text
mock_modbus.reset_mock()
caplog.clear()
await hass.services.async_call(DOMAIN, SERVICE_RESTART, data, blocking=True)
await hass.async_block_till_done()
assert mock_modbus.close.called
assert mock_modbus.connect.called
assert f"modbus {TEST_MODBUS_NAME} communication closed" in caplog.text
assert f"modbus {TEST_MODBUS_NAME} communication open" in caplog.text