diff --git a/.coveragerc b/.coveragerc index eaeee0f992d..24ceeae65be 100644 --- a/.coveragerc +++ b/.coveragerc @@ -563,7 +563,6 @@ omit = homeassistant/components/map/* homeassistant/components/mastodon/notify.py homeassistant/components/matrix/* - homeassistant/components/maxcube/* homeassistant/components/mcp23017/* homeassistant/components/media_extractor/* homeassistant/components/mediaroom/media_player.py diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6437f30ddaf..a8ceff28333 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -472,6 +472,9 @@ logi_circle==0.2.2 # homeassistant.components.luftdaten luftdaten==0.6.4 +# homeassistant.components.maxcube +maxcube-api==0.4.1 + # homeassistant.components.mythicbeastsdns mbddns==0.1.2 diff --git a/tests/components/maxcube/conftest.py b/tests/components/maxcube/conftest.py new file mode 100644 index 00000000000..7f45634b986 --- /dev/null +++ b/tests/components/maxcube/conftest.py @@ -0,0 +1,110 @@ +"""Tests for EQ3 Max! component.""" +from unittest.mock import create_autospec, patch + +from maxcube.device import MAX_DEVICE_MODE_AUTOMATIC, MAX_DEVICE_MODE_MANUAL +from maxcube.room import MaxRoom +from maxcube.thermostat import MaxThermostat +from maxcube.wallthermostat import MaxWallThermostat +from maxcube.windowshutter import MaxWindowShutter +import pytest + +from homeassistant.components.maxcube import DOMAIN +from homeassistant.setup import async_setup_component + + +@pytest.fixture +def room(): + """Create a test MAX! room.""" + r = MaxRoom() + r.id = 1 + r.name = "TestRoom" + return r + + +@pytest.fixture +def thermostat(): + """Create test MAX! thermostat.""" + t = create_autospec(MaxThermostat) + t.name = "TestThermostat" + t.serial = "AABBCCDD01" + t.rf_address = "abc1" + t.room_id = 1 + t.is_thermostat.return_value = True + t.is_wallthermostat.return_value = False + t.is_windowshutter.return_value = False + t.mode = MAX_DEVICE_MODE_AUTOMATIC + t.comfort_temperature = 19.0 + t.eco_temperature = 14.0 + t.target_temperature = 20.5 + t.actual_temperature = 19.0 + t.max_temperature = None + t.min_temperature = None + t.valve_position = 25 # 25% + return t + + +@pytest.fixture +def wallthermostat(): + """Create test MAX! wall thermostat.""" + t = create_autospec(MaxWallThermostat) + t.name = "TestWallThermostat" + t.serial = "AABBCCDD02" + t.rf_address = "abc2" + t.room_id = 1 + t.is_thermostat.return_value = False + t.is_wallthermostat.return_value = True + t.is_windowshutter.return_value = False + t.mode = MAX_DEVICE_MODE_MANUAL + t.comfort_temperature = 19.0 + t.eco_temperature = 14.0 + t.target_temperature = 4.5 + t.actual_temperature = 19.0 + t.max_temperature = 29.0 + t.min_temperature = 4.5 + return t + + +@pytest.fixture +def windowshutter(): + """Create test MAX! window shutter.""" + shutter = create_autospec(MaxWindowShutter) + shutter.name = "TestShutter" + shutter.serial = "AABBCCDD03" + shutter.rf_address = "abc3" + shutter.room_id = 1 + shutter.is_open = True + shutter.is_thermostat.return_value = False + shutter.is_wallthermostat.return_value = False + shutter.is_windowshutter.return_value = True + return shutter + + +@pytest.fixture +def hass_config(): + """Return test HASS configuration.""" + return { + DOMAIN: { + "gateways": [ + { + "host": "1.2.3.4", + } + ] + } + } + + +@pytest.fixture +async def cube(hass, hass_config, room, thermostat, wallthermostat, windowshutter): + """Build and setup a cube mock with a single room and some devices.""" + with patch("homeassistant.components.maxcube.MaxCube") as mock: + cube = mock.return_value + cube.rooms = [room] + cube.devices = [thermostat, wallthermostat, windowshutter] + cube.room_by_id.return_value = room + cube.devices_by_room.return_value = [thermostat, wallthermostat, windowshutter] + cube.device_by_rf.side_effect = {d.rf_address: d for d in cube.devices}.get + assert await async_setup_component(hass, DOMAIN, hass_config) + await hass.async_block_till_done() + gateway = hass_config[DOMAIN]["gateways"][0] + mock.assert_called_with(gateway["host"], gateway.get("port", 62910)) + return cube diff --git a/tests/components/maxcube/test_maxcube_binary_sensor.py b/tests/components/maxcube/test_maxcube_binary_sensor.py new file mode 100644 index 00000000000..df448284e80 --- /dev/null +++ b/tests/components/maxcube/test_maxcube_binary_sensor.py @@ -0,0 +1,37 @@ +"""Test EQ3 Max! Window Shutters.""" +from datetime import timedelta + +from maxcube.cube import MaxCube +from maxcube.windowshutter import MaxWindowShutter + +from homeassistant.components.binary_sensor import DEVICE_CLASS_WINDOW +from homeassistant.const import ( + ATTR_DEVICE_CLASS, + ATTR_FRIENDLY_NAME, + STATE_OFF, + STATE_ON, +) +from homeassistant.util import utcnow + +from tests.common import async_fire_time_changed + +ENTITY_ID = "binary_sensor.testroom_testshutter" + + +async def test_window_shuttler(hass, cube: MaxCube, windowshutter: MaxWindowShutter): + """Test a successful setup with a shuttler device.""" + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state is not None + assert state.state == STATE_ON + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "TestRoom TestShutter" + assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_WINDOW + + windowshutter.is_open = False + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.state == STATE_OFF diff --git a/tests/components/maxcube/test_maxcube_climate.py b/tests/components/maxcube/test_maxcube_climate.py new file mode 100644 index 00000000000..e700763769c --- /dev/null +++ b/tests/components/maxcube/test_maxcube_climate.py @@ -0,0 +1,364 @@ +"""Test EQ3 Max! Thermostats.""" +from datetime import timedelta + +from maxcube.cube import MaxCube +from maxcube.device import ( + MAX_DEVICE_MODE_AUTOMATIC, + MAX_DEVICE_MODE_BOOST, + MAX_DEVICE_MODE_MANUAL, + MAX_DEVICE_MODE_VACATION, +) +from maxcube.thermostat import MaxThermostat +from maxcube.wallthermostat import MaxWallThermostat + +from homeassistant.components.climate.const import ( + ATTR_CURRENT_TEMPERATURE, + ATTR_HVAC_ACTION, + ATTR_HVAC_MODE, + ATTR_HVAC_MODES, + ATTR_MAX_TEMP, + ATTR_MIN_TEMP, + ATTR_PRESET_MODE, + ATTR_PRESET_MODES, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + CURRENT_HVAC_OFF, + DOMAIN as CLIMATE_DOMAIN, + HVAC_MODE_AUTO, + HVAC_MODE_HEAT, + HVAC_MODE_OFF, + PRESET_AWAY, + PRESET_BOOST, + PRESET_COMFORT, + PRESET_ECO, + PRESET_NONE, + SERVICE_SET_HVAC_MODE, + SERVICE_SET_PRESET_MODE, + SERVICE_SET_TEMPERATURE, +) +from homeassistant.components.maxcube.climate import ( + MAX_TEMPERATURE, + MIN_TEMPERATURE, + OFF_TEMPERATURE, + ON_TEMPERATURE, + PRESET_ON, + SUPPORT_FLAGS, +) +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_FRIENDLY_NAME, + ATTR_SUPPORTED_FEATURES, + ATTR_TEMPERATURE, +) +from homeassistant.util import utcnow + +from tests.common import async_fire_time_changed + +ENTITY_ID = "climate.testroom_testthermostat" +WALL_ENTITY_ID = "climate.testroom_testwallthermostat" +VALVE_POSITION = "valve_position" + + +async def test_setup_thermostat(hass, cube: MaxCube): + """Test a successful setup of a thermostat device.""" + + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_AUTO + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "TestRoom TestThermostat" + assert state.attributes.get(ATTR_HVAC_ACTION) == CURRENT_HVAC_HEAT + assert state.attributes.get(ATTR_HVAC_MODES) == [ + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + HVAC_MODE_HEAT, + ] + assert state.attributes.get(ATTR_PRESET_MODES) == [ + PRESET_NONE, + PRESET_BOOST, + PRESET_COMFORT, + PRESET_ECO, + PRESET_AWAY, + PRESET_ON, + ] + assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_NONE + assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == SUPPORT_FLAGS + assert state.attributes.get(ATTR_MAX_TEMP) == MAX_TEMPERATURE + assert state.attributes.get(ATTR_MIN_TEMP) == 5.0 + assert state.attributes.get(ATTR_CURRENT_TEMPERATURE) == 19.0 + assert state.attributes.get(ATTR_TEMPERATURE) == 20.5 + assert state.attributes.get(VALVE_POSITION) == 25 + + +async def test_setup_wallthermostat(hass, cube: MaxCube): + """Test a successful setup of a wall thermostat device.""" + state = hass.states.get(WALL_ENTITY_ID) + assert state.state == HVAC_MODE_OFF + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "TestRoom TestWallThermostat" + assert state.attributes.get(ATTR_HVAC_ACTION) == CURRENT_HVAC_HEAT + assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_NONE + assert state.attributes.get(ATTR_MAX_TEMP) == 29.0 + assert state.attributes.get(ATTR_MIN_TEMP) == 5.0 + assert state.attributes.get(ATTR_CURRENT_TEMPERATURE) == 19.0 + assert state.attributes.get(ATTR_TEMPERATURE) is None + + +async def test_thermostat_set_hvac_mode_off( + hass, cube: MaxCube, thermostat: MaxThermostat +): + """Turn off thermostat.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_HVAC_MODE: HVAC_MODE_OFF}, + blocking=True, + ) + cube.set_temperature_mode.assert_called_once_with( + thermostat, OFF_TEMPERATURE, MAX_DEVICE_MODE_MANUAL + ) + + thermostat.mode = MAX_DEVICE_MODE_MANUAL + thermostat.target_temperature = OFF_TEMPERATURE + thermostat.valve_position = 0 + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_OFF + assert state.attributes.get(ATTR_TEMPERATURE) is None + assert state.attributes.get(ATTR_HVAC_ACTION) == CURRENT_HVAC_OFF + assert state.attributes.get(VALVE_POSITION) == 0 + + wall_state = hass.states.get(WALL_ENTITY_ID) + assert wall_state.attributes.get(ATTR_HVAC_ACTION) == CURRENT_HVAC_OFF + + +async def test_thermostat_set_hvac_mode_heat( + hass, cube: MaxCube, thermostat: MaxThermostat +): + """Set hvac mode to heat.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_HVAC_MODE: HVAC_MODE_HEAT}, + blocking=True, + ) + cube.set_temperature_mode.assert_called_once_with( + thermostat, 20.5, MAX_DEVICE_MODE_MANUAL + ) + thermostat.mode = MAX_DEVICE_MODE_MANUAL + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_HEAT + + +async def test_thermostat_set_temperature( + hass, cube: MaxCube, thermostat: MaxThermostat +): + """Set hvac mode to heat.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_TEMPERATURE: 10.0}, + blocking=True, + ) + cube.set_target_temperature.assert_called_once_with(thermostat, 10.0) + thermostat.target_temperature = 10.0 + thermostat.valve_position = 0 + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_AUTO + assert state.attributes.get(ATTR_TEMPERATURE) == 10.0 + assert state.attributes.get(ATTR_HVAC_ACTION) == CURRENT_HVAC_IDLE + + +async def test_thermostat_set_preset_on(hass, cube: MaxCube, thermostat: MaxThermostat): + """Set preset mode to on.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_ON}, + blocking=True, + ) + + cube.set_temperature_mode.assert_called_once_with( + thermostat, ON_TEMPERATURE, MAX_DEVICE_MODE_MANUAL + ) + thermostat.mode = MAX_DEVICE_MODE_MANUAL + thermostat.target_temperature = ON_TEMPERATURE + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_HEAT + assert state.attributes.get(ATTR_TEMPERATURE) is None + assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_ON + + +async def test_thermostat_set_preset_comfort( + hass, cube: MaxCube, thermostat: MaxThermostat +): + """Set preset mode to comfort.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_COMFORT}, + blocking=True, + ) + cube.set_temperature_mode.assert_called_once_with( + thermostat, thermostat.comfort_temperature, MAX_DEVICE_MODE_MANUAL + ) + thermostat.mode = MAX_DEVICE_MODE_MANUAL + thermostat.target_temperature = thermostat.comfort_temperature + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_HEAT + assert state.attributes.get(ATTR_TEMPERATURE) == thermostat.comfort_temperature + assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_COMFORT + + +async def test_thermostat_set_preset_eco( + hass, cube: MaxCube, thermostat: MaxThermostat +): + """Set preset mode to eco.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_ECO}, + blocking=True, + ) + cube.set_temperature_mode.assert_called_once_with( + thermostat, thermostat.eco_temperature, MAX_DEVICE_MODE_MANUAL + ) + thermostat.mode = MAX_DEVICE_MODE_MANUAL + thermostat.target_temperature = thermostat.eco_temperature + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_HEAT + assert state.attributes.get(ATTR_TEMPERATURE) == thermostat.eco_temperature + assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_ECO + + +async def test_thermostat_set_preset_away( + hass, cube: MaxCube, thermostat: MaxThermostat +): + """Set preset mode to away.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_AWAY}, + blocking=True, + ) + cube.set_temperature_mode.assert_called_once_with( + thermostat, None, MAX_DEVICE_MODE_VACATION + ) + thermostat.mode = MAX_DEVICE_MODE_VACATION + thermostat.target_temperature = thermostat.eco_temperature + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_HEAT + assert state.attributes.get(ATTR_TEMPERATURE) == thermostat.eco_temperature + assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_AWAY + + +async def test_thermostat_set_preset_boost( + hass, cube: MaxCube, thermostat: MaxThermostat +): + """Set preset mode to boost.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_BOOST}, + blocking=True, + ) + cube.set_temperature_mode.assert_called_once_with( + thermostat, None, MAX_DEVICE_MODE_BOOST + ) + thermostat.mode = MAX_DEVICE_MODE_BOOST + thermostat.target_temperature = thermostat.eco_temperature + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_AUTO + assert state.attributes.get(ATTR_TEMPERATURE) == thermostat.eco_temperature + assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_BOOST + + +async def test_thermostat_set_preset_none( + hass, cube: MaxCube, thermostat: MaxThermostat +): + """Set preset mode to boost.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_NONE}, + blocking=True, + ) + cube.set_temperature_mode.assert_called_once_with( + thermostat, None, MAX_DEVICE_MODE_AUTOMATIC + ) + + +async def test_wallthermostat_set_hvac_mode_heat( + hass, cube: MaxCube, wallthermostat: MaxWallThermostat +): + """Set wall thermostat hvac mode to heat.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: WALL_ENTITY_ID, ATTR_HVAC_MODE: HVAC_MODE_HEAT}, + blocking=True, + ) + cube.set_temperature_mode.assert_called_once_with( + wallthermostat, MIN_TEMPERATURE, MAX_DEVICE_MODE_MANUAL + ) + wallthermostat.target_temperature = MIN_TEMPERATURE + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(WALL_ENTITY_ID) + assert state.state == HVAC_MODE_HEAT + assert state.attributes.get(ATTR_TEMPERATURE) == MIN_TEMPERATURE + + +async def test_wallthermostat_set_hvac_mode_auto( + hass, cube: MaxCube, wallthermostat: MaxWallThermostat +): + """Set wall thermostat hvac mode to auto.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: WALL_ENTITY_ID, ATTR_HVAC_MODE: HVAC_MODE_AUTO}, + blocking=True, + ) + cube.set_temperature_mode.assert_called_once_with( + wallthermostat, None, MAX_DEVICE_MODE_AUTOMATIC + ) + wallthermostat.mode = MAX_DEVICE_MODE_AUTOMATIC + wallthermostat.target_temperature = 23.0 + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(WALL_ENTITY_ID) + assert state.state == HVAC_MODE_AUTO + assert state.attributes.get(ATTR_TEMPERATURE) == 23.0