From 187b4fad841c54a562b941975b13d31ef813a2ef Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 16 Jan 2022 07:51:16 -1000 Subject: [PATCH] Update nexia climate platform to use newer standards (#64186) --- .coveragerc | 1 + homeassistant/components/nexia/climate.py | 143 +++++++--------------- homeassistant/components/nexia/entity.py | 32 ++++- 3 files changed, 74 insertions(+), 102 deletions(-) diff --git a/.coveragerc b/.coveragerc index ab9771e5d10..1a9a2b47803 100644 --- a/.coveragerc +++ b/.coveragerc @@ -731,6 +731,7 @@ omit = homeassistant/components/netgear_lte/* homeassistant/components/netio/switch.py homeassistant/components/neurio_energy/sensor.py + homeassistant/components/nexia/entity.py homeassistant/components/nexia/climate.py homeassistant/components/nextcloud/* homeassistant/components/nfandroidtv/__init__.py diff --git a/homeassistant/components/nexia/climate.py b/homeassistant/components/nexia/climate.py index 5f61cae4975..b99e94d3abc 100644 --- a/homeassistant/components/nexia/climate.py +++ b/homeassistant/components/nexia/climate.py @@ -1,4 +1,6 @@ """Support for Nexia / Trane XL thermostats.""" +from __future__ import annotations + from nexia.const import ( HOLD_PERMANENT, HOLD_RESUME_SCHEDULE, @@ -9,17 +11,17 @@ from nexia.const import ( SYSTEM_STATUS_COOL, SYSTEM_STATUS_HEAT, SYSTEM_STATUS_IDLE, - UNIT_FAHRENHEIT, ) +from nexia.home import NexiaHome +from nexia.thermostat import NexiaThermostat from nexia.util import find_humidity_setpoint +from nexia.zone import NexiaThermostatZone import voluptuous as vol from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( ATTR_HUMIDITY, ATTR_HVAC_MODE, - ATTR_MAX_HUMIDITY, - ATTR_MIN_HUMIDITY, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, CURRENT_HVAC_COOL, @@ -43,7 +45,6 @@ from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_platform import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.dispatcher import dispatcher_send from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ( @@ -55,8 +56,6 @@ from .const import ( ATTR_RUN_MODE, ATTR_ZONE_STATUS, DOMAIN, - SIGNAL_THERMOSTAT_UPDATE, - SIGNAL_ZONE_UPDATE, ) from .coordinator import NexiaDataUpdateCoordinator from .entity import NexiaThermostatZoneEntity @@ -108,6 +107,21 @@ NEXIA_TO_HA_HVAC_MODE_MAP = { value: key for key, value in HA_TO_NEXIA_HVAC_MODE_MAP.items() } +HVAC_MODES = [ + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_HEAT, + HVAC_MODE_COOL, +] + +NEXIA_SUPPORTED = ( + SUPPORT_TARGET_TEMPERATURE_RANGE + | SUPPORT_TARGET_TEMPERATURE + | SUPPORT_FAN_MODE + | SUPPORT_PRESET_MODE +) + async def async_setup_entry( hass: HomeAssistant, @@ -116,7 +130,7 @@ async def async_setup_entry( ) -> None: """Set up climate for a Nexia device.""" coordinator: NexiaDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] - nexia_home = coordinator.nexia_home + nexia_home: NexiaHome = coordinator.nexia_home platform = entity_platform.async_get_current_platform() @@ -132,61 +146,57 @@ async def async_setup_entry( SERVICE_SET_HVAC_RUN_MODE, SET_HVAC_RUN_MODE_SCHEMA, SERVICE_SET_HVAC_RUN_MODE ) - entities = [] + entities: list[NexiaZone] = [] for thermostat_id in nexia_home.get_thermostat_ids(): - thermostat = nexia_home.get_thermostat_by_id(thermostat_id) + thermostat: NexiaThermostat = nexia_home.get_thermostat_by_id(thermostat_id) for zone_id in thermostat.get_zone_ids(): - zone = thermostat.get_zone_by_id(zone_id) + zone: NexiaThermostatZone = thermostat.get_zone_by_id(zone_id) entities.append(NexiaZone(coordinator, zone)) - async_add_entities(entities, True) + async_add_entities(entities) class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity): """Provides Nexia Climate support.""" - def __init__(self, coordinator, zone): + def __init__( + self, coordinator: NexiaDataUpdateCoordinator, zone: NexiaThermostatZone + ) -> None: """Initialize the thermostat.""" super().__init__( coordinator, zone, name=zone.get_name(), unique_id=zone.zone_id ) - self._undo_humidfy_dispatcher = None - self._undo_aircleaner_dispatcher = None + unit = self._thermostat.get_unit() + min_humidity, max_humidity = self._thermostat.get_humidity_setpoint_limits() + min_setpoint, max_setpoint = self._thermostat.get_setpoint_limits() # The has_* calls are stable for the life of the device # and do not do I/O self._has_relative_humidity = self._thermostat.has_relative_humidity() self._has_emergency_heat = self._thermostat.has_emergency_heat() self._has_humidify_support = self._thermostat.has_humidify_support() self._has_dehumidify_support = self._thermostat.has_dehumidify_support() - - @property - def supported_features(self): - """Return the list of supported features.""" - supported = ( - SUPPORT_TARGET_TEMPERATURE_RANGE - | SUPPORT_TARGET_TEMPERATURE - | SUPPORT_FAN_MODE - | SUPPORT_PRESET_MODE - ) - + supported = NEXIA_SUPPORTED if self._has_humidify_support or self._has_dehumidify_support: supported |= SUPPORT_TARGET_HUMIDITY - if self._has_emergency_heat: supported |= SUPPORT_AUX_HEAT - - return supported + self._attr_supported_features = supported + self._attr_preset_modes = self._zone.get_presets() + self._attr_fan_modes = self._thermostat.get_fan_modes() + self._attr_hvac_modes = HVAC_MODES + self._attr_min_humidity = percent_conv(min_humidity) + self._attr_max_humidity = percent_conv(max_humidity) + self._attr_min_temp = min_setpoint + self._attr_max_temp = max_setpoint + self._attr_temperature_unit = TEMP_CELSIUS if unit == "C" else TEMP_FAHRENHEIT + self._attr_target_temperature_step = 0.5 if unit == "C" else 1.0 + self._attr_preset_modes = self._zone.get_presets() @property def is_fan_on(self): """Blower is on.""" return self._thermostat.is_blower_active() - @property - def temperature_unit(self): - """Return the unit of measurement.""" - return TEMP_CELSIUS if self._thermostat.get_unit() == "C" else TEMP_FAHRENHEIT - @property def current_temperature(self): """Return the current temperature.""" @@ -197,21 +207,6 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity): """Return the fan setting.""" return self._thermostat.get_fan_mode() - @property - def fan_modes(self): - """Return the list of available fan modes.""" - return self._thermostat.get_fan_modes() - - @property - def min_temp(self): - """Minimum temp for the current setting.""" - return (self._thermostat.get_setpoint_limits())[0] - - @property - def max_temp(self): - """Maximum temp for the current setting.""" - return (self._thermostat.get_setpoint_limits())[1] - def set_fan_mode(self, fan_mode): """Set new target fan mode.""" self._thermostat.set_fan_mode(fan_mode) @@ -233,11 +228,6 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity): """Preset that is active.""" return self._zone.get_preset() - @property - def preset_modes(self): - """All presets.""" - return self._zone.get_presets() - def set_humidity(self, humidity): """Dehumidify target.""" if self._thermostat.has_dehumidify_support(): @@ -273,13 +263,6 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity): return self._zone.get_heating_setpoint() return None - @property - def target_temperature_step(self): - """Step size of temperature units.""" - if self._thermostat.get_unit() == UNIT_FAHRENHEIT: - return 1.0 - return 0.5 - @property def target_temperature_high(self): """Highest temperature we are trying to reach.""" @@ -332,17 +315,6 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity): return NEXIA_TO_HA_HVAC_MODE_MAP[mode] - @property - def hvac_modes(self): - """List of HVAC available modes.""" - return [ - HVAC_MODE_OFF, - HVAC_MODE_AUTO, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_COOL, - ] - def set_temperature(self, **kwargs): """Set target temperature.""" new_heat_temp = kwargs.get(ATTR_TARGET_TEMP_LOW) @@ -397,12 +369,8 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity): if not self._has_relative_humidity: return data - min_humidity = percent_conv(self._thermostat.get_humidity_setpoint_limits()[0]) - max_humidity = percent_conv(self._thermostat.get_humidity_setpoint_limits()[1]) data.update( { - ATTR_MIN_HUMIDITY: min_humidity, - ATTR_MAX_HUMIDITY: max_humidity, ATTR_DEHUMIDIFY_SUPPORTED: self._has_dehumidify_support, ATTR_HUMIDIFY_SUPPORTED: self._has_humidify_support, } @@ -454,7 +422,7 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity): self._zone.call_permanent_hold() self._zone.set_mode(mode=HA_TO_NEXIA_HVAC_MODE_MAP[hvac_mode]) - self.schedule_update_ha_state() + self._signal_zone_update() def set_aircleaner_mode(self, aircleaner_mode): """Set the aircleaner mode.""" @@ -480,26 +448,3 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity): return self._thermostat.set_dehumidify_setpoint(target_humidity) self._signal_thermostat_update() - - def _signal_thermostat_update(self): - """Signal a thermostat update. - - Whenever the underlying library does an action against - a thermostat, the data for the thermostat and all - connected zone is updated. - - Update all the zones on the thermostat. - """ - dispatcher_send( - self.hass, f"{SIGNAL_THERMOSTAT_UPDATE}-{self._thermostat.thermostat_id}" - ) - - def _signal_zone_update(self): - """Signal a zone update. - - Whenever the underlying library does an action against - a zone, the data for the zone is updated. - - Update a single zone. - """ - dispatcher_send(self.hass, f"{SIGNAL_ZONE_UPDATE}-{self._zone.zone_id}") diff --git a/homeassistant/components/nexia/entity.py b/homeassistant/components/nexia/entity.py index 06d3b90e048..0be5c05396d 100644 --- a/homeassistant/components/nexia/entity.py +++ b/homeassistant/components/nexia/entity.py @@ -1,6 +1,9 @@ """The nexia integration base entity.""" +from nexia.thermostat import NexiaThermostat +from nexia.zone import NexiaThermostatZone + from homeassistant.const import ATTR_ATTRIBUTION -from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -47,7 +50,7 @@ class NexiaThermostatEntity(NexiaEntity): def __init__(self, coordinator, thermostat, name, unique_id): """Initialize the entity.""" super().__init__(coordinator, name, unique_id) - self._thermostat = thermostat + self._thermostat: NexiaThermostat = thermostat @property def device_info(self) -> DeviceInfo: @@ -73,6 +76,19 @@ class NexiaThermostatEntity(NexiaEntity): ) ) + def _signal_thermostat_update(self): + """Signal a thermostat update. + + Whenever the underlying library does an action against + a thermostat, the data for the thermostat and all + connected zone is updated. + + Update all the zones on the thermostat. + """ + dispatcher_send( + self.hass, f"{SIGNAL_THERMOSTAT_UPDATE}-{self._thermostat.thermostat_id}" + ) + class NexiaThermostatZoneEntity(NexiaThermostatEntity): """Base class for nexia devices attached to a thermostat.""" @@ -80,7 +96,7 @@ class NexiaThermostatZoneEntity(NexiaThermostatEntity): def __init__(self, coordinator, zone, name, unique_id): """Initialize the entity.""" super().__init__(coordinator, zone.thermostat, name, unique_id) - self._zone = zone + self._zone: NexiaThermostatZone = zone @property def device_info(self): @@ -107,3 +123,13 @@ class NexiaThermostatZoneEntity(NexiaThermostatEntity): self.async_write_ha_state, ) ) + + def _signal_zone_update(self): + """Signal a zone update. + + Whenever the underlying library does an action against + a zone, the data for the zone is updated. + + Update a single zone. + """ + dispatcher_send(self.hass, f"{SIGNAL_ZONE_UPDATE}-{self._zone.zone_id}")