Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ventilation mode select #269

Merged
merged 1 commit into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions custom_components/lennoxs30/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
UNIQUE_ID_SUFFIX_RESET_SMART_HUB: Final = "_RESET_SMART_HUB"
UNIQUE_ID_SUFFIX_BLE: Final = "_BLE"
UNIQUE_ID_SUFFIX_BLE_COMMSTATUS: Final = "_BLE_COMMSTATUS"
UNIQUE_ID_SUFFIX_VENTILATION_SELECT: Final = "_VENT_SELECT"

VENTILATION_EQUIPMENT_ID = -900

Expand Down
2 changes: 1 addition & 1 deletion custom_components/lennoxs30/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ class TimedVentilationNumber(S30BaseEntityMixin, NumberEntity):
def __init__(self, hass: HomeAssistant, manager: Manager, system: lennox_system):
super().__init__(manager, system)
self._hass = hass
self._myname = self._system.name + "_timed_ventilation"
self._myname = self._system.name + "_ventilate_now"
_LOGGER.debug("Create TimedVentilationNumber myname [%s]", self._myname)

async def async_added_to_hass(self) -> None:
Expand Down
99 changes: 98 additions & 1 deletion custom_components/lennoxs30/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
LENNOX_DEHUMIDIFICATION_MODE_HIGH,
LENNOX_DEHUMIDIFICATION_MODE_MEDIUM,
LENNOX_DEHUMIDIFICATION_MODE_AUTO,
LENNOX_VENTILATION_MODES,
LENNOX_VENTILATION_MODE_INSTALLER,
LENNOX_VENTILATION_MODE_ON,
LENNOX_VENTILATION_MODE_OFF,
lennox_system,
lennox_zone,
)
Expand All @@ -39,7 +43,12 @@
)

from .base_entity import S30BaseEntityMixin
from .const import LOG_INFO_SELECT_ASYNC_SELECT_OPTION, MANAGER, UNIQUE_ID_SUFFIX_EQ_PARAM_SELECT
from .const import (
LOG_INFO_SELECT_ASYNC_SELECT_OPTION,
MANAGER,
UNIQUE_ID_SUFFIX_EQ_PARAM_SELECT,
UNIQUE_ID_SUFFIX_VENTILATION_SELECT,
)
from . import DOMAIN, Manager


Expand All @@ -61,6 +70,11 @@ async def async_setup_entry(
_LOGGER.debug("Create DehumidificationModeSelect system [%s]", system.sysId)
sel = DehumidificationModeSelect(hass, manager, system)
select_list.append(sel)

if system.supports_ventilation():
_LOGGER.info("Create S30 ventilation select system [%s]", system.sysId)
select_list.append(VentilationModeSelect(hass, manager, system))

for zone in system.zone_list:
if zone.is_zone_active():
if zone.dehumidificationOption or zone.humidificationOption:
Expand Down Expand Up @@ -362,3 +376,86 @@ def entity_category(self):
@property
def extra_state_attributes(self) -> dict[str, Any]:
return helper_get_parameter_extra_attributes(self.equipment, self.parameter)


class VentilationModeSelect(S30BaseEntityMixin, SelectEntity):
"""Set the ventilation mode"""

def __init__(
self,
hass: HomeAssistant,
manager: Manager,
system: lennox_system,
):
super().__init__(manager, system)
self.hass: HomeAssistant = hass
self._myname = self._system.name + "_ventilation_mode"
_LOGGER.debug("Create VentilationModeSelect myname [%s]", self._myname)

async def async_added_to_hass(self) -> None:
"""Run when entity about to be added to hass."""
_LOGGER.debug("async_added_to_hass VentilationModeSelect myname [%s]", self._myname)
self._system.registerOnUpdateCallback(
self.system_update_callback,
[
"ventilationMode",
"ventilationControlMode",
],
)
await super().async_added_to_hass()

def system_update_callback(self):
"""Callback for system updates"""
if _LOGGER.isEnabledFor(logging.DEBUG):
_LOGGER.debug(
"system_update_callback VentilationModeSelect myname [%s] system ventilation mode [%s]",
self._myname,
self._system.ventilationMode,
)
self.schedule_update_ha_state()

@property
def unique_id(self) -> str:
# HA fails with dashes in IDs
return self._system.unique_id + UNIQUE_ID_SUFFIX_VENTILATION_SELECT

@property
def name(self):
return self._myname

@property
def current_option(self) -> str:
return self._system.ventilationMode

@property
def options(self) -> list:
return LENNOX_VENTILATION_MODES

async def async_select_option(self, option: str) -> None:
_LOGGER.info(LOG_INFO_SELECT_ASYNC_SELECT_OPTION, self.__class__.__name__, self._myname, option)
try:
if option == LENNOX_VENTILATION_MODE_ON:
await self._system.ventilation_on()
elif option == LENNOX_VENTILATION_MODE_OFF:
await self._system.ventilation_off()
elif option == LENNOX_VENTILATION_MODE_INSTALLER:
await self._system.ventilation_installer()
else:
raise HomeAssistantError(f"select_option [{self._myname}] invalid mode [{option}]")
except Exception as ex:
raise HomeAssistantError(f"select_option unexpected exception, [{self._myname}] exception [{ex}]") from ex

@property
def device_info(self) -> DeviceInfo:
"""Return device info."""
result = {
"identifiers": {(DOMAIN, self._system.unique_id)},
}
return result

@property
def extra_state_attributes(self):
"""Return the state attributes."""
attrs: dict[str, Any] = {}
attrs["installer_settings"] = self._system.ventilationControlMode
return attrs
2 changes: 1 addition & 1 deletion tests/test_number_timed_ventilation.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ async def test_timed_ventilation_time_unique_id(hass, manager: Manager):
async def test_timed_ventilation_time_name(hass, manager: Manager):
system: lennox_system = manager.api.system_list[0]
c = TimedVentilationNumber(hass, manager, system)
assert c.name == system.name + "_timed_ventilation"
assert c.name == system.name + "_ventilate_now"


@pytest.mark.asyncio
Expand Down
39 changes: 26 additions & 13 deletions tests/test_select_setup.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
from pickle import FALSE
from lennoxs30api.s30api_async import (
LENNOX_VENTILATION_DAMPER,
lennox_system,
)
from custom_components.lennoxs30 import (
Manager,
)
"""Test setup of select entities"""
# pylint: disable=line-too-long
# pylint: disable=protected-access

from unittest.mock import Mock
import pytest
from custom_components.lennoxs30.const import MANAGER

from lennoxs30api.s30api_async import LENNOX_VENTILATION_2_SPEED_HRV, lennox_system
from custom_components.lennoxs30 import Manager

from custom_components.lennoxs30.const import MANAGER
from custom_components.lennoxs30.select import (
DehumidificationModeSelect,
HumidityModeSelect,
EquipmentParameterSelect,
VentilationModeSelect,
async_setup_entry,
)


from unittest.mock import Mock


@pytest.mark.asyncio
async def test_async_number_setup_entry(hass, manager: Manager, caplog):
async def test_async_number_setup_entry(hass, manager: Manager):
"""Test the select setup"""
system: lennox_system = manager.api.system_list[0]
entry = manager.config_entry
hass.data["lennoxs30"] = {}
Expand Down Expand Up @@ -93,3 +92,17 @@ async def test_async_number_setup_entry(hass, manager: Manager, caplog):
assert len(sensor_list) == 15
for i in range(0, 15):
assert isinstance(sensor_list[i], EquipmentParameterSelect)

# VenitlatioNModeSelect should be created
system.dehumidifierType = None
for zone in system.zone_list:
zone.dehumidificationOption = False
zone.humidificationOption = False
manager.create_equipment_parameters = False
system.ventilationUnitType = LENNOX_VENTILATION_2_SPEED_HRV
async_add_entities = Mock()
await async_setup_entry(hass, entry, async_add_entities)
assert async_add_entities.call_count == 1
sensor_list = async_add_entities.call_args[0][0]
assert len(sensor_list) == 1
assert isinstance(sensor_list[0], VentilationModeSelect)
173 changes: 173 additions & 0 deletions tests/test_select_ventilation_mode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# pylint: disable=too-many-lines
# pylint: disable=missing-module-docstring
# pylint: disable=missing-function-docstring
# pylint: disable=invalid-name
# pylint: disable=protected-access
# pylint: disable=line-too-long

import logging
from unittest.mock import patch
import pytest

from homeassistant.exceptions import HomeAssistantError

from lennoxs30api.s30api_async import (
LENNOX_VENTILATION_MODE_OFF,
LENNOX_VENTILATION_MODE_ON,
LENNOX_VENTILATION_MODE_INSTALLER,
LENNOX_VENTILATION_CONTROL_MODE_ASHRAE,
lennox_system,
)


from custom_components.lennoxs30 import Manager
from custom_components.lennoxs30.select import VentilationModeSelect
from custom_components.lennoxs30.const import LENNOX_DOMAIN

from tests.conftest import (
conf_test_exception_handling,
conftest_base_entity_availability,
conf_test_select_info_async_select_option,
)


@pytest.mark.asyncio
async def test_select_ventilation_mode_unique_id(hass, manager: Manager):
system: lennox_system = manager.api.system_list[0]
c = VentilationModeSelect(hass, manager, system)
assert c.unique_id == system.unique_id + "_VENT_SELECT"


@pytest.mark.asyncio
async def test_select_ventilation_mode_name(hass, manager: Manager):
system: lennox_system = manager.api.system_list[0]
c = VentilationModeSelect(hass, manager, system)
assert c.name == system.name + "_ventilation_mode"


@pytest.mark.asyncio
async def test_select_ventilation_mode_current_option(hass, manager_mz: Manager):
manager = manager_mz
system: lennox_system = manager.api.system_list[0]
c = VentilationModeSelect(hass, manager, system)

system.ventilationMode = LENNOX_VENTILATION_MODE_INSTALLER
assert c.current_option == LENNOX_VENTILATION_MODE_INSTALLER
assert c.available is True
arr = c.extra_state_attributes
assert len(arr) == 1
assert arr["installer_settings"] == system.ventilationControlMode

system.ventilationMode = LENNOX_VENTILATION_MODE_ON
assert c.current_option == LENNOX_VENTILATION_MODE_ON
assert c.available is True

system.ventilationMode = LENNOX_VENTILATION_MODE_OFF
assert c.current_option == LENNOX_VENTILATION_MODE_OFF
assert c.available is True


@pytest.mark.asyncio
async def test_select_ventilation_mode_subscription(hass, manager_mz: Manager):
manager = manager_mz
system: lennox_system = manager.api.system_list[0]
system.dehumidificationMode = None
c = VentilationModeSelect(hass, manager, system)
await c.async_added_to_hass()

with patch.object(c, "schedule_update_ha_state") as update_callback:
update_set = {"ventilationMode": LENNOX_VENTILATION_MODE_OFF}
system.attr_updater(update_set, "ventilationMode")
system.executeOnUpdateCallbacks()
assert update_callback.call_count == 1
assert c.current_option == LENNOX_VENTILATION_MODE_OFF
assert c.available is True

with patch.object(c, "schedule_update_ha_state") as update_callback:
update_set = {"ventilationMode": LENNOX_VENTILATION_MODE_ON}
system.attr_updater(update_set, "ventilationMode")
system.executeOnUpdateCallbacks()
assert update_callback.call_count == 1
assert c.current_option == LENNOX_VENTILATION_MODE_ON
assert c.available is True

with patch.object(c, "schedule_update_ha_state") as update_callback:
update_set = {"ventilationMode": LENNOX_VENTILATION_MODE_INSTALLER}
system.attr_updater(update_set, "ventilationMode")
system.executeOnUpdateCallbacks()
assert update_callback.call_count == 1
assert c.current_option == LENNOX_VENTILATION_MODE_INSTALLER
assert c.available is True

with patch.object(c, "schedule_update_ha_state") as update_callback:
update_set = {"ventilationControlMode": LENNOX_VENTILATION_CONTROL_MODE_ASHRAE}
system.attr_updater(update_set, "ventilationControlMode")
system.executeOnUpdateCallbacks()
assert update_callback.call_count == 1
assert c.extra_state_attributes["installer_settings"] == LENNOX_VENTILATION_CONTROL_MODE_ASHRAE
assert c.available is True

conftest_base_entity_availability(manager, system, c)


@pytest.mark.asyncio
async def test_select_ventilation_mode_options(hass, manager_mz: Manager):
manager = manager_mz
system: lennox_system = manager.api.system_list[0]
c = VentilationModeSelect(hass, manager, system)

opt = c.options
assert len(opt) == 3
assert LENNOX_VENTILATION_MODE_INSTALLER in opt
assert LENNOX_VENTILATION_MODE_ON in opt
assert LENNOX_VENTILATION_MODE_OFF in opt


@pytest.mark.asyncio
async def test_select_ventilation_mode_async_select_options(hass, manager_mz: Manager, caplog):
manager = manager_mz
system: lennox_system = manager.api.system_list[0]
c = VentilationModeSelect(hass, manager, system)

with patch.object(system, "ventilation_on") as set_dehumidificationMode:
await c.async_select_option(LENNOX_VENTILATION_MODE_ON)
assert set_dehumidificationMode.call_count == 1

with patch.object(system, "ventilation_off") as set_dehumidificationMode:
await c.async_select_option(LENNOX_VENTILATION_MODE_OFF)
assert set_dehumidificationMode.call_count == 1

with patch.object(system, "ventilation_installer") as set_dehumidificationMode:
await c.async_select_option(LENNOX_VENTILATION_MODE_INSTALLER)
assert set_dehumidificationMode.call_count == 1

with caplog.at_level(logging.ERROR):
caplog.clear()
with patch.object(system, "set_dehumidificationMode") as set_dehumidificationMode:
ex: HomeAssistantError = None
try:
await c.async_select_option("bad_value")
except HomeAssistantError as err:
ex = err
assert ex is not None
assert set_dehumidificationMode.call_count == 0
msg = str(ex)
assert "bad_value" in msg

await conf_test_exception_handling(
system, "ventilation_on", c, c.async_select_option, option=LENNOX_VENTILATION_MODE_ON
)
await conf_test_select_info_async_select_option(system, "ventilation_on", c, caplog)


@pytest.mark.asyncio
async def test_select_ventilation_mode_device_info(hass, manager_mz: Manager):
manager = manager_mz
await manager.create_devices()
system: lennox_system = manager.api.system_list[0]
c = VentilationModeSelect(hass, manager, system)

identifiers = c.device_info["identifiers"]
for x in identifiers:
assert x[0] == LENNOX_DOMAIN
assert x[1] == system.unique_id
Loading