Skip to content

Commit 5726df2

Browse files
authored
Merge pull request #322 from PeteRager/2024.4.0
2024.4.0
2 parents 900785e + 84664e1 commit 5726df2

File tree

14 files changed

+442
-98
lines changed

14 files changed

+442
-98
lines changed

.vscode/settings.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
"python.testing.pytestArgs": [
33
"tests","--asyncio-mode=auto"
44
],
5+
"python.experiments.optOutFrom": ["pythonTestAdapter"],
56
"python.testing.unittestEnabled": false,
67
"python.testing.pytestEnabled": true,
7-
"python.experiments.enabled": false,
88
"cSpell.words": [
99
"ASHRAE",
1010
"automations",
@@ -24,4 +24,8 @@
2424
"sysuptime"
2525
],
2626
"cSpell.enabled": false,
27+
"black-formatter.args": ["--line-length", "120"],
28+
"[python]": {
29+
"editor.defaultFormatter": "ms-python.black-formatter"
30+
}
2731
}

README.MD

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,9 @@ Away Mode. The **Away** preset will put the S30 system into Manual Away Mode. Yo
178178

179179
Cancel Manual Away Mode or Smart Away Mode. The **cancel away mode** preset will cancel the active away mode (manual or smart) and return the S30 system to whatever state it was in prior to putting it into away mode. This works the same as if you pressed the cancel away icon on the S30 panel.
180180

181-
**Emergency Heat** - Lennox systems that have a heat pump and an auxiliary furnace, have an additional HVAC_MODE to run just the auxiliary furnace. In the S30 App this is shown as Emergency Heat. Home Assistant **does not** allow this mode directly - instead Home Assistant provides support for turning aux_heat on and off - independent of the HVAC_MODE. The integration has the following behavior:
181+
**Emergency Heat** - Lennox systems that have a heat pump and an auxiliary furnace, have an additional HVAC_MODE to run just the auxiliary furnace. In the S30 App this is shown as Emergency Heat. Home Assistant **does not** allow this mode directly - instead a **select.system_zone_hvac_mode** entity provides support for setting the hvac mode to emergency heat. The integration has the following behavior:
182182

183-
- If Emergency Heat Mode is set in the S30, the HA climate will show Heat and the Aux Heat switch will be on.
184-
- If the HA Aux Switch is turned on, the S30 Heat Mode will be set to **Emergency Heat**
185-
- If the HA Aux Switch is turned off, the S30 Heat Mode will be set to **Heat**
183+
- If Emergency Heat Mode is set in the S30, the HA climate will show Heat and the select entity will show **Emergency Heat**.
186184
- If the Lennox Auxiliary Heat is running, the **aux** attribute in the HA Climate entity will be set to True and the HA Climate Entity will show **Heating**
187185

188186
**Humidification and Dehumidification**
@@ -474,11 +472,18 @@ Local Connections Only. Reports the WIFI signal strength, this sensor can be us
474472

475473
## Select Entities
476474

475+
### HVAC Mode
476+
477+
If your Lennox System has Emergency Heat a select entity will be created to allow you to set the hvac_mode.
478+
479+
select.<system_name><zone_name>\_hvac_mode
480+
481+
477482
### Humidity Mode
478483

479484
If your Lennox System has Humidification or Dehumidification capability, one of these select entities is created per zone to allow you to set the active humidity mode. The available options - depending on your equipment - are **Dehumidify**, **Humidify** and **Off**
480485

481-
select.<system_name><zone_name>\_humidity_mode
486+
select.<system_name>\_<zone_name>\_humidity_mode
482487

483488
### Dehumidification Modes
484489

custom_components/lennoxs30/climate.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# pylint: disable=unused-argument
55
# pylint: disable=line-too-long
66
# pylint: disable=invalid-name
7+
# pylint: disable=abstract-method
78

89
from __future__ import annotations
910

@@ -99,7 +100,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_e
99100

100101
class S30Climate(S30BaseEntityMixin, ClimateEntity):
101102
"""Class for Lennox S30 thermostat."""
102-
103103
def __init__(self, hass, manager: Manager, system: lennox_system, zone: lennox_zone):
104104
"""Initialize the climate device."""
105105
super().__init__(manager, system)
@@ -603,9 +603,16 @@ def is_aux_heat(self) -> bool | None:
603603
res = self._zone.systemMode == LENNOX_HVAC_EMERGENCY_HEAT
604604
return res
605605

606+
def _create_aux_heat_issue(self, service: str):
607+
_LOGGER.warning(
608+
"climate.%s is deprecated and will be removed in version 2024.10 learn more https://github.com/PeteRager/lennoxs30/blob/master/docs/aux_heat.md", service
609+
)
610+
611+
606612
async def async_turn_aux_heat_on(self):
607613
"""Turn auxiliary heater on."""
608614
_LOGGER.info("climate:async_turn_aux_heat_on zone [%s]", self._myname)
615+
self._create_aux_heat_issue("turn_aux_heat_on")
609616
if self.is_zone_disabled:
610617
raise HomeAssistantError(f"Unable to turn_aux_heat_on mode as zone [{self._myname}] is disabled")
611618
try:
@@ -620,6 +627,7 @@ async def async_turn_aux_heat_on(self):
620627

621628
async def async_turn_aux_heat_off(self):
622629
_LOGGER.info("climate:async_turn_aux_heat_off zone [%s]", self._myname)
630+
self._create_aux_heat_issue("turn_aux_heat_off")
623631
# When Aux is turned off, we will revert the zone to Heat Mode.
624632
if self.is_zone_disabled:
625633
raise HomeAssistantError(f"Unable to turn_aux_heat_on mode as zone [{self._myname}] is disabled")

custom_components/lennoxs30/const.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
UNIQUE_ID_SUFFIX_BLE_COMMSTATUS: Final = "_BLE_COMMSTATUS"
4848
UNIQUE_ID_SUFFIX_VENTILATION_SELECT: Final = "_VENT_SELECT"
4949
UNIQUE_ID_SUFFIX_WIFI_RSSI: Final = "_WIFI_RSSI"
50+
UNIQUE_ID_SUFFIX_EMERGENCY_HEAT: Final = "_EHEAT"
51+
UNIQUE_ID_SUFFIX_ZONEMODE_SELECT: Final = "_ZONE_MODE_SELECT"
5052

5153
VENTILATION_EQUIPMENT_ID = -900
5254

custom_components/lennoxs30/manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88
"iot_class": "local_push",
99
"issue_tracker" : "https://github.com/PeteRager/lennoxs30/issues",
1010
"quality_scale": "platinum",
11-
"requirements": ["lennoxs30api==0.2.14"],
12-
"version": "2024.3.0"
11+
"requirements": ["lennoxs30api==0.2.15"],
12+
"version": "2024.6.0"
1313
}

custom_components/lennoxs30/select.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
# pylint: disable=unused-argument
55
# pylint: disable=line-too-long
66
# pylint: disable=invalid-name
7+
# pylint: disable=abstract-method
78
from typing import Any
89
import logging
910

1011

12+
from homeassistant.components.climate.const import HVACMode
1113
from homeassistant.components.select import SelectEntity
1214
from homeassistant.core import HomeAssistant
1315
from homeassistant.config_entries import ConfigEntry
@@ -20,6 +22,8 @@
2022
LENNOX_HUMIDITY_MODE_OFF,
2123
LENNOX_HUMIDITY_MODE_HUMIDIFY,
2224
LENNOX_HUMIDITY_MODE_DEHUMIDIFY,
25+
LENNOX_HVAC_EMERGENCY_HEAT,
26+
LENNOX_HVAC_HEAT_COOL,
2327
LENNOX_DEHUMIDIFICATION_MODE_HIGH,
2428
LENNOX_DEHUMIDIFICATION_MODE_MEDIUM,
2529
LENNOX_DEHUMIDIFICATION_MODE_AUTO,
@@ -48,6 +52,7 @@
4852
MANAGER,
4953
UNIQUE_ID_SUFFIX_EQ_PARAM_SELECT,
5054
UNIQUE_ID_SUFFIX_VENTILATION_SELECT,
55+
UNIQUE_ID_SUFFIX_ZONEMODE_SELECT,
5156
)
5257
from . import DOMAIN, Manager
5358

@@ -89,6 +94,13 @@ async def async_setup_entry(
8994
select = EquipmentParameterSelect(hass, manager, system, equipment, parameter)
9095
select_list.append(select)
9196

97+
for zone in system.zone_list:
98+
if zone.is_zone_active():
99+
if zone.emergencyHeatingOption or system.has_emergency_heat():
100+
zone_emergency_heat = ZoneModeSelect(hass, manager, system, zone)
101+
select_list.append(zone_emergency_heat)
102+
103+
92104
if len(select_list) != 0:
93105
async_add_entities(select_list, True)
94106

@@ -459,3 +471,105 @@ def extra_state_attributes(self):
459471
attrs: dict[str, Any] = {}
460472
attrs["installer_settings"] = self._system.ventilationControlMode
461473
return attrs
474+
475+
class ZoneModeSelect(S30BaseEntityMixin, SelectEntity):
476+
"""Set the zone hvac mode"""
477+
478+
def __init__(
479+
self,
480+
hass: HomeAssistant,
481+
manager: Manager,
482+
system: lennox_system,
483+
zone: lennox_zone,
484+
):
485+
super().__init__(manager, system)
486+
self.hass: HomeAssistant = hass
487+
self._zone = zone
488+
self._myname = f"{self._system.name}_{self._zone.name}_hvac_mode"
489+
_LOGGER.debug("Create ZoneModeSelect myname [%s]", self._myname)
490+
491+
async def async_added_to_hass(self) -> None:
492+
"""Run when entity about to be added to hass."""
493+
_LOGGER.debug("async_added_to_hass VentilationModeSelect myname [%s]", self._myname)
494+
self._zone.registerOnUpdateCallback(self.zone_update_callback, ["systemMode"])
495+
self._system.registerOnUpdateCallback(self.system_update_callback, ["zoningMode"])
496+
await super().async_added_to_hass()
497+
498+
def zone_update_callback(self):
499+
"""Callback for system updates"""
500+
if _LOGGER.isEnabledFor(logging.DEBUG):
501+
_LOGGER.debug(
502+
"zone_update_callback ZoneModeSelect myname [%s]",
503+
self._myname,
504+
)
505+
self.schedule_update_ha_state()
506+
507+
def system_update_callback(self):
508+
"""Callback for system updates"""
509+
if _LOGGER.isEnabledFor(logging.DEBUG):
510+
_LOGGER.debug(
511+
"system_update_callback ZoneModeSelect myname [%s]",
512+
self._myname,
513+
)
514+
self.schedule_update_ha_state()
515+
516+
517+
@property
518+
def unique_id(self) -> str:
519+
# HA fails with dashes in IDs
520+
return self._zone.unique_id + UNIQUE_ID_SUFFIX_ZONEMODE_SELECT
521+
522+
@property
523+
def name(self):
524+
return self._myname
525+
526+
@property
527+
def current_option(self) -> str:
528+
r = self._zone.getSystemMode()
529+
if r == LENNOX_HVAC_HEAT_COOL:
530+
r = HVACMode.HEAT_COOL
531+
return r
532+
533+
@property
534+
def options(self) -> list:
535+
modes = []
536+
if self._zone.is_zone_disabled:
537+
return modes
538+
modes.append(HVACMode.OFF)
539+
if self._zone.coolingOption:
540+
modes.append(HVACMode.COOL)
541+
if self._zone.heatingOption:
542+
modes.append(HVACMode.HEAT)
543+
if self._zone.coolingOption and self._zone.heatingOption:
544+
modes.append(HVACMode.HEAT_COOL)
545+
if self._zone.emergencyHeatingOption or self._system.has_emergency_heat():
546+
modes.append(LENNOX_HVAC_EMERGENCY_HEAT)
547+
return modes
548+
549+
async def async_select_option(self, option: str) -> None:
550+
_LOGGER.info(LOG_INFO_SELECT_ASYNC_SELECT_OPTION, self.__class__.__name__, self._myname, option)
551+
if self._zone.is_zone_disabled:
552+
raise HomeAssistantError(f"Unable to set hvac_mode as zone [{self._myname}] is disabled")
553+
try:
554+
hvac_mode = option if option != HVACMode.HEAT_COOL else LENNOX_HVAC_HEAT_COOL
555+
_LOGGER.info(
556+
"select:async_set_option [%s] ha_mode [%s] lennox_mode [%s]",
557+
self._myname,
558+
option,
559+
hvac_mode,
560+
)
561+
await self._zone.setHVACMode(hvac_mode)
562+
except S30Exception as ex:
563+
raise HomeAssistantError(f"async_select_option [{self._myname}] [{ex.as_string()}]") from ex
564+
except Exception as ex:
565+
raise HomeAssistantError(
566+
f"async_select_option unexpected exception, please log issue, [{self._myname}] exception [{ex}]"
567+
) from ex
568+
569+
@property
570+
def device_info(self) -> DeviceInfo:
571+
"""Return device info."""
572+
result = {
573+
"identifiers": {(DOMAIN, self._zone.unique_id)},
574+
}
575+
return result

custom_components/lennoxs30/translations/en.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,5 +86,5 @@
8686
"title": "Options"
8787
}
8888
}
89-
}
89+
}
9090
}

docs/aux_heat.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Aux Heat
2+
3+
## Overview
4+
5+
Home Assistant has deprecated aux_heat and will remove it from the product in 2024.10.0
6+
7+
This integration will continue to support aux heat until 2024.10.0
8+
9+
If you are using aux heat you will need to migrate to use the new functionality before that date.
10+
11+
## Changes
12+
13+
A new select entity is created for each zone for systems that have aux heat. The name of the entity is **select.[system_name]_[zone_name]_hvac_mode()**
14+
15+
This select entity will have all the valid hvac_modes for your system including **emergency heat**. You can select emergency heat directly from the drop down or use the service call:
16+
17+
```yaml
18+
service: select.select_option
19+
target:
20+
entity_id: select.ragehouse_zone_1_hvac_mode
21+
data:
22+
option: "emergency heat"
23+
```
24+
25+
The climate entity hvac_mode will be heat when the lennox hvac_mode is heat or emergency heat. Changing the hvac_mode in the climate entity will cause the select to update.

tests/conftest.py

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,6 @@
7575

7676
pytest_plugins = "pytest_homeassistant_custom_component"
7777

78-
79-
@pytest.fixture(autouse=True)
80-
def socket_enabled():
81-
pass
82-
8378
@pytest.fixture(autouse=True)
8479
def auto_enable_custom_integrations(enable_custom_integrations):
8580
yield
@@ -131,8 +126,7 @@ def loadfile(name: str, sysId: str = None) -> json:
131126

132127
@pytest.fixture
133128
def config_entry_local() -> config_entries.ConfigEntry:
134-
config = config_entries.ConfigEntry(version=1, domain=DOMAIN, title="10.0.0.1", data={}, source="User")
135-
config.unique_id = "12345"
129+
config = config_entries.ConfigEntry(version=1, minor_version=0, domain=DOMAIN, title="10.0.0.1", data={}, source="User", unique_id="12345")
136130
config.data = {}
137131
config.data[CONF_CLOUD_CONNECTION] = False
138132
config.data[CONF_HOST] = "10.0.0.1"
@@ -158,8 +152,7 @@ def config_entry_local() -> config_entries.ConfigEntry:
158152

159153
@pytest.fixture
160154
def config_entry_cloud() -> config_entries.ConfigEntry:
161-
config = config_entries.ConfigEntry(version=1, domain=DOMAIN, title="10.0.0.1", data={}, source="User")
162-
config.unique_id = "12345"
155+
config = config_entries.ConfigEntry(version=1, minor_version = 0, domain=DOMAIN, title="10.0.0.1", data={}, source="User", unique_id="12345")
163156
config.data = {}
164157
config.data[CONF_CLOUD_CONNECTION] = True
165158
config.data[CONF_EMAIL] = "pete.rage@rage.com"
@@ -270,8 +263,7 @@ def manager_us_customary_units(hass: HomeAssistant, config_entry_local) -> Manag
270263

271264
@pytest.fixture
272265
def manager_2_systems(hass) -> Manager:
273-
config = config_entries.ConfigEntry(version=1, domain=DOMAIN, title="10.0.0.1", data={}, source="User")
274-
config.unique_id = "12345"
266+
config = config_entries.ConfigEntry(version=1, minor_version = 0, domain=DOMAIN, title="10.0.0.1", data={}, source="User", unique_id="12345")
275267

276268
manager_to_return = Manager(
277269
hass=hass,
@@ -326,9 +318,7 @@ def manager_2_systems(hass) -> Manager:
326318

327319
@pytest.fixture
328320
def manager_mz(hass) -> Manager:
329-
config = config_entries.ConfigEntry(version=1, domain=DOMAIN, title="10.0.0.1", data={}, source="User")
330-
config.unique_id = "12345"
331-
321+
config = config_entries.ConfigEntry(version=1, minor_version = 0, domain=DOMAIN, title="10.0.0.1", data={}, source="User", unique_id="12345")
332322
manager_to_return = Manager(
333323
hass=hass,
334324
config=config,
@@ -371,9 +361,7 @@ def manager_mz(hass) -> Manager:
371361

372362
@pytest.fixture
373363
def manager_system_04_furn_ac_zoning(hass) -> Manager:
374-
config = config_entries.ConfigEntry(version=1, domain=DOMAIN, title="10.0.0.1", data={}, source="User")
375-
config.unique_id = "12345"
376-
364+
config = config_entries.ConfigEntry(version=1, minor_version = 0, domain=DOMAIN, title="10.0.0.1", data={}, source="User", unique_id="12345")
377365
manager_to_return = Manager(
378366
hass=hass,
379367
config=config,

0 commit comments

Comments
 (0)