diff --git a/tests/components/modbus/conftest.py b/tests/components/modbus/conftest.py index a3e6078ea09..403607b110f 100644 --- a/tests/components/modbus/conftest.py +++ b/tests/components/modbus/conftest.py @@ -1,31 +1,25 @@ """The tests for the Modbus sensor component.""" +from datetime import timedelta +import logging from unittest import mock -from unittest.mock import patch import pytest -from homeassistant.components.modbus.const import ( - CALL_TYPE_COIL, - CALL_TYPE_DISCRETE, - CALL_TYPE_REGISTER_INPUT, - DEFAULT_HUB, - MODBUS_DOMAIN as DOMAIN, +from homeassistant.components.modbus.const import DEFAULT_HUB, MODBUS_DOMAIN as DOMAIN +from homeassistant.const import ( + CONF_HOST, + CONF_NAME, + CONF_PLATFORM, + CONF_PORT, + CONF_SCAN_INTERVAL, + CONF_TYPE, ) -from homeassistant.const import CONF_PLATFORM, CONF_SCAN_INTERVAL from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from tests.common import async_fire_time_changed - -@pytest.fixture() -def mock_hub(hass): - """Mock hub.""" - with patch("homeassistant.components.modbus.setup", return_value=True): - hub = mock.MagicMock() - hub.name = "hub" - hass.data[DOMAIN] = {DEFAULT_HUB: hub} - yield hub +_LOGGER = logging.getLogger(__name__) class ReadResult: @@ -37,63 +31,119 @@ class ReadResult: self.bits = register_words -async def setup_base_test( - sensor_name, +async def base_test( hass, - use_mock_hub, - data_array, + config_device, + device_name, entity_domain, - scan_interval, -): - """Run setup device for given config.""" - # Full sensor configuration - config = { - entity_domain: { - CONF_PLATFORM: "modbus", - CONF_SCAN_INTERVAL: scan_interval, - **data_array, - } - } - - # Initialize sensor - now = dt_util.utcnow() - with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now): - assert await async_setup_component(hass, entity_domain, config) - await hass.async_block_till_done() - - entity_id = f"{entity_domain}.{sensor_name}" - device = hass.states.get(entity_id) - if device is None: - pytest.fail("CONFIG failed, see output") - return entity_id, now, device - - -async def run_base_read_test( - entity_id, - hass, - use_mock_hub, - register_type, + array_name_discovery, + array_name_old_config, register_words, expected, - now, + method_discovery=False, + check_config_only=False, + config_modbus=None, + scan_interval=None, ): - """Run test for given config.""" - # Setup inputs for the sensor - read_result = ReadResult(register_words) - if register_type == CALL_TYPE_COIL: - use_mock_hub.read_coils.return_value = read_result - elif register_type == CALL_TYPE_DISCRETE: - use_mock_hub.read_discrete_inputs.return_value = read_result - elif register_type == CALL_TYPE_REGISTER_INPUT: - use_mock_hub.read_input_registers.return_value = read_result - else: # CALL_TYPE_REGISTER_HOLDING - use_mock_hub.read_holding_registers.return_value = read_result + """Run test on device for given config.""" - # Trigger update call with time_changed event - with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now): - async_fire_time_changed(hass, now) - await hass.async_block_till_done() + if config_modbus is None: + config_modbus = { + DOMAIN: { + CONF_NAME: DEFAULT_HUB, + CONF_TYPE: "tcp", + CONF_HOST: "modbusTest", + CONF_PORT: 5001, + }, + } - # Check state - state = hass.states.get(entity_id).state - assert state == expected + mock_sync = mock.MagicMock() + with mock.patch( + "homeassistant.components.modbus.modbus.ModbusTcpClient", return_value=mock_sync + ), mock.patch( + "homeassistant.components.modbus.modbus.ModbusSerialClient", + return_value=mock_sync, + ), mock.patch( + "homeassistant.components.modbus.modbus.ModbusUdpClient", return_value=mock_sync + ): + + # Setup inputs for the sensor + read_result = ReadResult(register_words) + mock_sync.read_coils.return_value = read_result + mock_sync.read_discrete_inputs.return_value = read_result + mock_sync.read_input_registers.return_value = read_result + mock_sync.read_holding_registers.return_value = read_result + + # mock timer and add old/new config + now = dt_util.utcnow() + with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now): + if method_discovery and config_device is not None: + # setup modbus which in turn does setup for the devices + config_modbus[DOMAIN].update( + {array_name_discovery: [{**config_device}]} + ) + config_device = None + assert await async_setup_component(hass, DOMAIN, config_modbus) + await hass.async_block_till_done() + + # setup platform old style + if config_device is not None: + config_device = { + entity_domain: { + CONF_PLATFORM: DOMAIN, + array_name_old_config: [ + { + **config_device, + } + ], + } + } + if scan_interval is not None: + config_device[entity_domain][CONF_SCAN_INTERVAL] = scan_interval + assert await async_setup_component(hass, entity_domain, config_device) + await hass.async_block_till_done() + + assert DOMAIN in hass.data + if config_device is not None: + entity_id = f"{entity_domain}.{device_name}" + device = hass.states.get(entity_id) + if device is None: + pytest.fail("CONFIG failed, see output") + if check_config_only: + return + + # Trigger update call with time_changed event + now = now + timedelta(seconds=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() + + # Check state + entity_id = f"{entity_domain}.{device_name}" + return hass.states.get(entity_id).state + + +async def base_config_test( + hass, + config_device, + device_name, + entity_domain, + array_name_discovery, + array_name_old_config, + method_discovery=False, + config_modbus=None, +): + """Check config of device for given config.""" + + await base_test( + hass, + config_device, + device_name, + entity_domain, + array_name_discovery, + array_name_old_config, + None, + None, + method_discovery=method_discovery, + check_config_only=True, + ) diff --git a/tests/components/modbus/test_modbus_binary_sensor.py b/tests/components/modbus/test_modbus_binary_sensor.py index 3bc7223c865..4cd586f390f 100644 --- a/tests/components/modbus/test_modbus_binary_sensor.py +++ b/tests/components/modbus/test_modbus_binary_sensor.py @@ -1,6 +1,4 @@ """The tests for the Modbus sensor component.""" -from datetime import timedelta - import pytest from homeassistant.components.binary_sensor import DOMAIN as SENSOR_DOMAIN @@ -10,83 +8,76 @@ from homeassistant.components.modbus.const import ( CONF_INPUT_TYPE, CONF_INPUTS, ) -from homeassistant.const import CONF_ADDRESS, CONF_NAME, STATE_OFF, STATE_ON +from homeassistant.const import CONF_ADDRESS, CONF_NAME, CONF_SLAVE, STATE_OFF, STATE_ON -from .conftest import run_base_read_test, setup_base_test +from .conftest import base_config_test, base_test +@pytest.mark.parametrize("do_options", [False, True]) +async def test_config_binary_sensor(hass, do_options): + """Run test for binary sensor.""" + sensor_name = "test_sensor" + config_sensor = { + CONF_NAME: sensor_name, + CONF_ADDRESS: 51, + } + if do_options: + config_sensor.update( + { + CONF_SLAVE: 10, + CONF_INPUT_TYPE: CALL_TYPE_DISCRETE, + } + ) + await base_config_test( + hass, + config_sensor, + sensor_name, + SENSOR_DOMAIN, + None, + CONF_INPUTS, + method_discovery=False, + ) + + +@pytest.mark.parametrize("do_type", [CALL_TYPE_COIL, CALL_TYPE_DISCRETE]) @pytest.mark.parametrize( - "cfg,regs,expected", + "regs,expected", [ ( - { - CONF_INPUT_TYPE: CALL_TYPE_COIL, - }, [0xFF], STATE_ON, ), ( - { - CONF_INPUT_TYPE: CALL_TYPE_COIL, - }, [0x01], STATE_ON, ), ( - { - CONF_INPUT_TYPE: CALL_TYPE_COIL, - }, [0x00], STATE_OFF, ), ( - { - CONF_INPUT_TYPE: CALL_TYPE_COIL, - }, [0x80], STATE_OFF, ), ( - { - CONF_INPUT_TYPE: CALL_TYPE_COIL, - }, [0xFE], STATE_OFF, ), - ( - { - CONF_INPUT_TYPE: CALL_TYPE_DISCRETE, - }, - [0xFF], - STATE_ON, - ), - ( - { - CONF_INPUT_TYPE: CALL_TYPE_DISCRETE, - }, - [0x00], - STATE_OFF, - ), ], ) -async def test_coil_true(hass, mock_hub, cfg, regs, expected): +async def test_all_binary_sensor(hass, do_type, regs, expected): """Run test for given config.""" sensor_name = "modbus_test_binary_sensor" - scan_interval = 5 - entity_id, now, device = await setup_base_test( + state = await base_test( + hass, + {CONF_NAME: sensor_name, CONF_ADDRESS: 1234, CONF_INPUT_TYPE: do_type}, sensor_name, - hass, - mock_hub, - {CONF_INPUTS: [dict(**{CONF_NAME: sensor_name, CONF_ADDRESS: 1234}, **cfg)]}, SENSOR_DOMAIN, - scan_interval, - ) - await run_base_read_test( - entity_id, - hass, - mock_hub, - cfg.get(CONF_INPUT_TYPE), + None, + CONF_INPUTS, regs, expected, - now + timedelta(seconds=scan_interval + 1), + method_discovery=False, + scan_interval=5, ) + assert state == expected diff --git a/tests/components/modbus/test_modbus_climate.py b/tests/components/modbus/test_modbus_climate.py new file mode 100644 index 00000000000..bbdaed63995 --- /dev/null +++ b/tests/components/modbus/test_modbus_climate.py @@ -0,0 +1,75 @@ +"""The tests for the Modbus climate component.""" +import pytest + +from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN +from homeassistant.components.modbus.const import ( + CONF_CLIMATES, + CONF_CURRENT_TEMP, + CONF_DATA_COUNT, + CONF_TARGET_TEMP, +) +from homeassistant.const import CONF_NAME, CONF_SCAN_INTERVAL, CONF_SLAVE + +from .conftest import base_config_test, base_test + + +@pytest.mark.parametrize("do_options", [False, True]) +async def test_config_climate(hass, do_options): + """Run test for climate.""" + device_name = "test_climate" + device_config = { + CONF_NAME: device_name, + CONF_TARGET_TEMP: 117, + CONF_CURRENT_TEMP: 117, + CONF_SLAVE: 10, + } + if do_options: + device_config.update( + { + CONF_SCAN_INTERVAL: 20, + CONF_DATA_COUNT: 2, + } + ) + await base_config_test( + hass, + device_config, + device_name, + CLIMATE_DOMAIN, + CONF_CLIMATES, + None, + method_discovery=True, + ) + + +@pytest.mark.parametrize( + "regs,expected", + [ + ( + [0x00], + "auto", + ), + ], +) +async def test_temperature_climate(hass, regs, expected): + """Run test for given config.""" + climate_name = "modbus_test_climate" + return + state = await base_test( + hass, + { + CONF_NAME: climate_name, + CONF_SLAVE: 1, + CONF_TARGET_TEMP: 117, + CONF_CURRENT_TEMP: 117, + CONF_DATA_COUNT: 2, + }, + climate_name, + CLIMATE_DOMAIN, + CONF_CLIMATES, + None, + regs, + expected, + method_discovery=True, + scan_interval=5, + ) + assert state == expected diff --git a/tests/components/modbus/test_modbus_cover.py b/tests/components/modbus/test_modbus_cover.py new file mode 100644 index 00000000000..ff765314745 --- /dev/null +++ b/tests/components/modbus/test_modbus_cover.py @@ -0,0 +1,136 @@ +"""The tests for the Modbus cover component.""" +import pytest + +from homeassistant.components.cover import DOMAIN as COVER_DOMAIN +from homeassistant.components.modbus.const import CALL_TYPE_COIL, CONF_REGISTER +from homeassistant.const import ( + CONF_COVERS, + CONF_NAME, + CONF_SCAN_INTERVAL, + CONF_SLAVE, + STATE_OPEN, + STATE_OPENING, +) + +from .conftest import base_config_test, base_test + + +@pytest.mark.parametrize("do_options", [False, True]) +@pytest.mark.parametrize("read_type", [CALL_TYPE_COIL, CONF_REGISTER]) +async def test_config_cover(hass, do_options, read_type): + """Run test for cover.""" + device_name = "test_cover" + device_config = { + CONF_NAME: device_name, + read_type: 1234, + } + if do_options: + device_config.update( + { + CONF_SLAVE: 10, + CONF_SCAN_INTERVAL: 20, + } + ) + await base_config_test( + hass, + device_config, + device_name, + COVER_DOMAIN, + CONF_COVERS, + None, + method_discovery=True, + ) + + +@pytest.mark.parametrize( + "regs,expected", + [ + ( + [0x00], + STATE_OPENING, + ), + ( + [0x80], + STATE_OPENING, + ), + ( + [0xFE], + STATE_OPENING, + ), + ( + [0xFF], + STATE_OPENING, + ), + ( + [0x01], + STATE_OPENING, + ), + ], +) +async def test_coil_cover(hass, regs, expected): + """Run test for given config.""" + cover_name = "modbus_test_cover" + state = await base_test( + hass, + { + CONF_NAME: cover_name, + CALL_TYPE_COIL: 1234, + CONF_SLAVE: 1, + }, + cover_name, + COVER_DOMAIN, + CONF_COVERS, + None, + regs, + expected, + method_discovery=True, + scan_interval=5, + ) + assert state == expected + + +@pytest.mark.parametrize( + "regs,expected", + [ + ( + [0x00], + STATE_OPEN, + ), + ( + [0x80], + STATE_OPEN, + ), + ( + [0xFE], + STATE_OPEN, + ), + ( + [0xFF], + STATE_OPEN, + ), + ( + [0x01], + STATE_OPEN, + ), + ], +) +async def test_register_COVER(hass, regs, expected): + """Run test for given config.""" + cover_name = "modbus_test_cover" + state = await base_test( + hass, + { + CONF_NAME: cover_name, + CONF_REGISTER: 1234, + CONF_SLAVE: 1, + }, + cover_name, + COVER_DOMAIN, + CONF_COVERS, + None, + regs, + expected, + method_discovery=True, + scan_interval=5, + ) + assert state == expected diff --git a/tests/components/modbus/test_modbus_sensor.py b/tests/components/modbus/test_modbus_sensor.py index 3f7c0fc60df..71a5213db9e 100644 --- a/tests/components/modbus/test_modbus_sensor.py +++ b/tests/components/modbus/test_modbus_sensor.py @@ -1,6 +1,4 @@ """The tests for the Modbus sensor component.""" -from datetime import timedelta - import pytest from homeassistant.components.modbus.const import ( @@ -20,9 +18,41 @@ from homeassistant.components.modbus.const import ( DATA_TYPE_UINT, ) from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN -from homeassistant.const import CONF_NAME, CONF_OFFSET +from homeassistant.const import CONF_NAME, CONF_OFFSET, CONF_SLAVE -from .conftest import run_base_read_test, setup_base_test +from .conftest import base_config_test, base_test + + +@pytest.mark.parametrize("do_options", [False, True]) +async def test_config_sensor(hass, do_options): + """Run test for sensor.""" + sensor_name = "test_sensor" + config_sensor = { + CONF_NAME: sensor_name, + CONF_REGISTER: 51, + } + if do_options: + config_sensor.update( + { + CONF_SLAVE: 10, + CONF_COUNT: 1, + CONF_DATA_TYPE: "int", + CONF_PRECISION: 0, + CONF_SCALE: 1, + CONF_REVERSE_ORDER: False, + CONF_OFFSET: 0, + CONF_REGISTER_TYPE: CALL_TYPE_REGISTER_HOLDING, + } + ) + await base_config_test( + hass, + config_sensor, + sensor_name, + SENSOR_DOMAIN, + None, + CONF_REGISTERS, + method_discovery=False, + ) @pytest.mark.parametrize( @@ -235,28 +265,19 @@ from .conftest import run_base_read_test, setup_base_test ), ], ) -async def test_all_sensor(hass, mock_hub, cfg, regs, expected): +async def test_all_sensor(hass, cfg, regs, expected): """Run test for sensor.""" sensor_name = "modbus_test_sensor" - scan_interval = 5 - entity_id, now, device = await setup_base_test( + state = await base_test( + hass, + {CONF_NAME: sensor_name, CONF_REGISTER: 1234, **cfg}, sensor_name, - hass, - mock_hub, - { - CONF_REGISTERS: [ - dict(**{CONF_NAME: sensor_name, CONF_REGISTER: 1234}, **cfg) - ] - }, SENSOR_DOMAIN, - scan_interval, - ) - await run_base_read_test( - entity_id, - hass, - mock_hub, - cfg.get(CONF_REGISTER_TYPE), + None, + CONF_REGISTERS, regs, expected, - now + timedelta(seconds=scan_interval + 1), + method_discovery=False, + scan_interval=5, ) + assert state == expected diff --git a/tests/components/modbus/test_modbus_switch.py b/tests/components/modbus/test_modbus_switch.py index da2ff953660..5c4717c9cf8 100644 --- a/tests/components/modbus/test_modbus_switch.py +++ b/tests/components/modbus/test_modbus_switch.py @@ -1,11 +1,8 @@ """The tests for the Modbus switch component.""" -from datetime import timedelta - import pytest from homeassistant.components.modbus.const import ( CALL_TYPE_COIL, - CALL_TYPE_REGISTER_HOLDING, CONF_COILS, CONF_REGISTER, CONF_REGISTERS, @@ -20,7 +17,43 @@ from homeassistant.const import ( STATE_ON, ) -from .conftest import run_base_read_test, setup_base_test +from .conftest import base_config_test, base_test + + +@pytest.mark.parametrize("do_options", [False, True]) +@pytest.mark.parametrize("read_type", [CALL_TYPE_COIL, CONF_REGISTER]) +async def test_config_switch(hass, do_options, read_type): + """Run test for switch.""" + device_name = "test_switch" + + if read_type == CONF_REGISTER: + device_config = { + CONF_NAME: device_name, + CONF_REGISTER: 1234, + CONF_SLAVE: 1, + CONF_COMMAND_OFF: 0x00, + CONF_COMMAND_ON: 0x01, + } + array_type = CONF_REGISTERS + else: + device_config = { + CONF_NAME: device_name, + read_type: 1234, + CONF_SLAVE: 10, + } + array_type = CONF_COILS + if do_options: + device_config.update({}) + + await base_config_test( + hass, + device_config, + device_name, + SWITCH_DOMAIN, + None, + array_type, + method_discovery=False, + ) @pytest.mark.parametrize( @@ -48,32 +81,26 @@ from .conftest import run_base_read_test, setup_base_test ), ], ) -async def test_coil_switch(hass, mock_hub, regs, expected): +async def test_coil_switch(hass, regs, expected): """Run test for given config.""" switch_name = "modbus_test_switch" - scan_interval = 5 - entity_id, now, device = await setup_base_test( - switch_name, + state = await base_test( hass, - mock_hub, { - CONF_COILS: [ - {CONF_NAME: switch_name, CALL_TYPE_COIL: 1234, CONF_SLAVE: 1}, - ] + CONF_NAME: switch_name, + CALL_TYPE_COIL: 1234, + CONF_SLAVE: 1, }, + switch_name, SWITCH_DOMAIN, - scan_interval, - ) - - await run_base_read_test( - entity_id, - hass, - mock_hub, - CALL_TYPE_COIL, + None, + CONF_COILS, regs, expected, - now + timedelta(seconds=scan_interval + 1), + method_discovery=False, + scan_interval=5, ) + assert state == expected @pytest.mark.parametrize( @@ -101,38 +128,28 @@ async def test_coil_switch(hass, mock_hub, regs, expected): ), ], ) -async def test_register_switch(hass, mock_hub, regs, expected): +async def test_register_switch(hass, regs, expected): """Run test for given config.""" switch_name = "modbus_test_switch" - scan_interval = 5 - entity_id, now, device = await setup_base_test( - switch_name, + state = await base_test( hass, - mock_hub, { - CONF_REGISTERS: [ - { - CONF_NAME: switch_name, - CONF_REGISTER: 1234, - CONF_SLAVE: 1, - CONF_COMMAND_OFF: 0x00, - CONF_COMMAND_ON: 0x01, - }, - ] + CONF_NAME: switch_name, + CONF_REGISTER: 1234, + CONF_SLAVE: 1, + CONF_COMMAND_OFF: 0x00, + CONF_COMMAND_ON: 0x01, }, + switch_name, SWITCH_DOMAIN, - scan_interval, - ) - - await run_base_read_test( - entity_id, - hass, - mock_hub, - CALL_TYPE_REGISTER_HOLDING, + None, + CONF_REGISTERS, regs, expected, - now + timedelta(seconds=scan_interval + 1), + method_discovery=False, + scan_interval=5, ) + assert state == expected @pytest.mark.parametrize( @@ -152,35 +169,25 @@ async def test_register_switch(hass, mock_hub, regs, expected): ), ], ) -async def test_register_state_switch(hass, mock_hub, regs, expected): +async def test_register_state_switch(hass, regs, expected): """Run test for given config.""" switch_name = "modbus_test_switch" - scan_interval = 5 - entity_id, now, device = await setup_base_test( - switch_name, + state = await base_test( hass, - mock_hub, { - CONF_REGISTERS: [ - { - CONF_NAME: switch_name, - CONF_REGISTER: 1234, - CONF_SLAVE: 1, - CONF_COMMAND_OFF: 0x04, - CONF_COMMAND_ON: 0x40, - }, - ] + CONF_NAME: switch_name, + CONF_REGISTER: 1234, + CONF_SLAVE: 1, + CONF_COMMAND_OFF: 0x04, + CONF_COMMAND_ON: 0x40, }, + switch_name, SWITCH_DOMAIN, - scan_interval, - ) - - await run_base_read_test( - entity_id, - hass, - mock_hub, - CALL_TYPE_REGISTER_HOLDING, + None, + CONF_REGISTERS, regs, expected, - now + timedelta(seconds=scan_interval + 1), + method_discovery=False, + scan_interval=5, ) + assert state == expected