mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 00:37:53 +00:00
Add aircleaner and humidify service to nexia climate (#33078)
* Add aircleaner and humidify service to nexia climate * These were removed from the original merge to reduce review scope * Additional tests for binary_sensor, sensor, and climate states * Switch to signals for services Get rid of everywhere we call device and change to zone or thermostat as it was too confusing Renames to make it clear that zone and thermostat are tightly coupled * Make scene activation responsive * no need to use update for only one key/value * stray comma * use async_call_later * its async, need ()s * cleaner * merge entity platform services testing branch
This commit is contained in:
parent
0e3dc7976c
commit
b8fdebd05c
@ -15,7 +15,7 @@ from homeassistant.exceptions import ConfigEntryNotReady
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from .const import DATA_NEXIA, DOMAIN, NEXIA_DEVICE, PLATFORMS, UPDATE_COORDINATOR
|
||||
from .const import DOMAIN, NEXIA_DEVICE, PLATFORMS, UPDATE_COORDINATOR
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -94,8 +94,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
update_interval=timedelta(seconds=DEFAULT_UPDATE_RATE),
|
||||
)
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id] = {}
|
||||
hass.data[DOMAIN][entry.entry_id][DATA_NEXIA] = {
|
||||
hass.data[DOMAIN][entry.entry_id] = {
|
||||
NEXIA_DEVICE: nexia_home,
|
||||
UPDATE_COORDINATOR: coordinator,
|
||||
}
|
||||
|
@ -1,23 +1,15 @@
|
||||
"""Support for Nexia / Trane XL Thermostats."""
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.const import ATTR_ATTRIBUTION
|
||||
|
||||
from .const import (
|
||||
ATTRIBUTION,
|
||||
DATA_NEXIA,
|
||||
DOMAIN,
|
||||
MANUFACTURER,
|
||||
NEXIA_DEVICE,
|
||||
UPDATE_COORDINATOR,
|
||||
)
|
||||
from .entity import NexiaEntity
|
||||
from .const import DOMAIN, NEXIA_DEVICE, UPDATE_COORDINATOR
|
||||
from .entity import NexiaThermostatEntity
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up sensors for a Nexia device."""
|
||||
|
||||
nexia_data = hass.data[DOMAIN][config_entry.entry_id][DATA_NEXIA]
|
||||
nexia_data = hass.data[DOMAIN][config_entry.entry_id]
|
||||
nexia_home = nexia_data[NEXIA_DEVICE]
|
||||
coordinator = nexia_data[UPDATE_COORDINATOR]
|
||||
|
||||
@ -42,48 +34,21 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
async_add_entities(entities, True)
|
||||
|
||||
|
||||
class NexiaBinarySensor(NexiaEntity, BinarySensorDevice):
|
||||
class NexiaBinarySensor(NexiaThermostatEntity, BinarySensorDevice):
|
||||
"""Provices Nexia BinarySensor support."""
|
||||
|
||||
def __init__(self, coordinator, device, sensor_call, sensor_name):
|
||||
def __init__(self, coordinator, thermostat, sensor_call, sensor_name):
|
||||
"""Initialize the nexia sensor."""
|
||||
super().__init__(coordinator)
|
||||
self._coordinator = coordinator
|
||||
self._device = device
|
||||
self._name = f"{self._device.get_name()} {sensor_name}"
|
||||
super().__init__(
|
||||
coordinator,
|
||||
thermostat,
|
||||
name=f"{thermostat.get_name()} {sensor_name}",
|
||||
unique_id=f"{thermostat.thermostat_id}_{sensor_call}",
|
||||
)
|
||||
self._call = sensor_call
|
||||
self._unique_id = f"{self._device.thermostat_id}_{sensor_call}"
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the unique id of the binary sensor."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return the device_info of the device."""
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self._device.thermostat_id)},
|
||||
"name": self._device.get_name(),
|
||||
"model": self._device.get_model(),
|
||||
"sw_version": self._device.get_firmware(),
|
||||
"manufacturer": MANUFACTURER,
|
||||
}
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the device specific state attributes."""
|
||||
return {
|
||||
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||
}
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return the status of the sensor."""
|
||||
return getattr(self._device, self._call)()
|
||||
return getattr(self._thermostat, self._call)()
|
||||
|
@ -12,9 +12,11 @@ from nexia.const import (
|
||||
SYSTEM_STATUS_IDLE,
|
||||
UNIT_FAHRENHEIT,
|
||||
)
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_HUMIDITY,
|
||||
ATTR_MAX_HUMIDITY,
|
||||
ATTR_MIN_HUMIDITY,
|
||||
ATTR_TARGET_TEMP_HIGH,
|
||||
@ -36,26 +38,50 @@ from homeassistant.components.climate.const import (
|
||||
SUPPORT_TARGET_TEMPERATURE_RANGE,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION,
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_TEMPERATURE,
|
||||
TEMP_CELSIUS,
|
||||
TEMP_FAHRENHEIT,
|
||||
)
|
||||
from homeassistant.helpers import entity_platform
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||
|
||||
from .const import (
|
||||
ATTR_AIRCLEANER_MODE,
|
||||
ATTR_DEHUMIDIFY_SETPOINT,
|
||||
ATTR_DEHUMIDIFY_SUPPORTED,
|
||||
ATTR_HUMIDIFY_SETPOINT,
|
||||
ATTR_HUMIDIFY_SUPPORTED,
|
||||
ATTR_ZONE_STATUS,
|
||||
ATTRIBUTION,
|
||||
DATA_NEXIA,
|
||||
DOMAIN,
|
||||
MANUFACTURER,
|
||||
NEXIA_DEVICE,
|
||||
SIGNAL_THERMOSTAT_UPDATE,
|
||||
SIGNAL_ZONE_UPDATE,
|
||||
UPDATE_COORDINATOR,
|
||||
)
|
||||
from .entity import NexiaEntity
|
||||
from .entity import NexiaThermostatZoneEntity
|
||||
from .util import percent_conv
|
||||
|
||||
SERVICE_SET_AIRCLEANER_MODE = "set_aircleaner_mode"
|
||||
SERVICE_SET_HUMIDIFY_SETPOINT = "set_humidify_setpoint"
|
||||
|
||||
SET_AIRCLEANER_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Required(ATTR_AIRCLEANER_MODE): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
SET_HUMIDITY_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Required(ATTR_HUMIDITY): vol.All(
|
||||
vol.Coerce(int), vol.Range(min=35, max=65)
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -83,10 +109,21 @@ NEXIA_TO_HA_HVAC_MODE_MAP = {
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up climate for a Nexia device."""
|
||||
|
||||
nexia_data = hass.data[DOMAIN][config_entry.entry_id][DATA_NEXIA]
|
||||
nexia_data = hass.data[DOMAIN][config_entry.entry_id]
|
||||
nexia_home = nexia_data[NEXIA_DEVICE]
|
||||
coordinator = nexia_data[UPDATE_COORDINATOR]
|
||||
|
||||
platform = entity_platform.current_platform.get()
|
||||
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_SET_HUMIDIFY_SETPOINT,
|
||||
SET_HUMIDITY_SCHEMA,
|
||||
SERVICE_SET_HUMIDIFY_SETPOINT,
|
||||
)
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_SET_AIRCLEANER_MODE, SET_AIRCLEANER_SCHEMA, SERVICE_SET_AIRCLEANER_MODE,
|
||||
)
|
||||
|
||||
entities = []
|
||||
for thermostat_id in nexia_home.get_thermostat_ids():
|
||||
thermostat = nexia_home.get_thermostat_by_id(thermostat_id)
|
||||
@ -97,26 +134,22 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
async_add_entities(entities, True)
|
||||
|
||||
|
||||
class NexiaZone(NexiaEntity, ClimateDevice):
|
||||
class NexiaZone(NexiaThermostatZoneEntity, ClimateDevice):
|
||||
"""Provides Nexia Climate support."""
|
||||
|
||||
def __init__(self, coordinator, device):
|
||||
def __init__(self, coordinator, zone):
|
||||
"""Initialize the thermostat."""
|
||||
super().__init__(coordinator)
|
||||
self.thermostat = device.thermostat
|
||||
self._device = device
|
||||
self._coordinator = coordinator
|
||||
super().__init__(
|
||||
coordinator, zone, name=zone.get_name(), unique_id=zone.zone_id
|
||||
)
|
||||
self._undo_humidfy_dispatcher = None
|
||||
self._undo_aircleaner_dispatcher = None
|
||||
# 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 unique_id(self):
|
||||
"""Device Uniqueid."""
|
||||
return self._device.zone_id
|
||||
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):
|
||||
@ -139,27 +172,22 @@ class NexiaZone(NexiaEntity, ClimateDevice):
|
||||
@property
|
||||
def is_fan_on(self):
|
||||
"""Blower is on."""
|
||||
return self.thermostat.is_blower_active()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Name of the zone."""
|
||||
return self._device.get_name()
|
||||
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
|
||||
return TEMP_CELSIUS if self._thermostat.get_unit() == "C" else TEMP_FAHRENHEIT
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
return self._device.get_temperature()
|
||||
return self._zone.get_temperature()
|
||||
|
||||
@property
|
||||
def fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return self.thermostat.get_fan_mode()
|
||||
return self._thermostat.get_fan_mode()
|
||||
|
||||
@property
|
||||
def fan_modes(self):
|
||||
@ -169,92 +197,92 @@ class NexiaZone(NexiaEntity, ClimateDevice):
|
||||
@property
|
||||
def min_temp(self):
|
||||
"""Minimum temp for the current setting."""
|
||||
return (self._device.thermostat.get_setpoint_limits())[0]
|
||||
return (self._thermostat.get_setpoint_limits())[0]
|
||||
|
||||
@property
|
||||
def max_temp(self):
|
||||
"""Maximum temp for the current setting."""
|
||||
return (self._device.thermostat.get_setpoint_limits())[1]
|
||||
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)
|
||||
self.schedule_update_ha_state()
|
||||
self._thermostat.set_fan_mode(fan_mode)
|
||||
self._signal_thermostat_update()
|
||||
|
||||
@property
|
||||
def preset_mode(self):
|
||||
"""Preset that is active."""
|
||||
return self._device.get_preset()
|
||||
return self._zone.get_preset()
|
||||
|
||||
@property
|
||||
def preset_modes(self):
|
||||
"""All presets."""
|
||||
return self._device.get_presets()
|
||||
return self._zone.get_presets()
|
||||
|
||||
def set_humidity(self, humidity):
|
||||
"""Dehumidify target."""
|
||||
self.thermostat.set_dehumidify_setpoint(humidity / 100.0)
|
||||
self.schedule_update_ha_state()
|
||||
self._thermostat.set_dehumidify_setpoint(humidity / 100.0)
|
||||
self._signal_thermostat_update()
|
||||
|
||||
@property
|
||||
def target_humidity(self):
|
||||
"""Humidity indoors setpoint."""
|
||||
if self._has_dehumidify_support:
|
||||
return round(self.thermostat.get_dehumidify_setpoint() * 100.0, 1)
|
||||
return percent_conv(self._thermostat.get_dehumidify_setpoint())
|
||||
if self._has_humidify_support:
|
||||
return round(self.thermostat.get_humidify_setpoint() * 100.0, 1)
|
||||
return percent_conv(self._thermostat.get_humidify_setpoint())
|
||||
return None
|
||||
|
||||
@property
|
||||
def current_humidity(self):
|
||||
"""Humidity indoors."""
|
||||
if self._has_relative_humidity:
|
||||
return round(self.thermostat.get_relative_humidity() * 100.0, 1)
|
||||
return percent_conv(self._thermostat.get_relative_humidity())
|
||||
return None
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Temperature we try to reach."""
|
||||
current_mode = self._device.get_current_mode()
|
||||
current_mode = self._zone.get_current_mode()
|
||||
|
||||
if current_mode == OPERATION_MODE_COOL:
|
||||
return self._device.get_cooling_setpoint()
|
||||
return self._zone.get_cooling_setpoint()
|
||||
if current_mode == OPERATION_MODE_HEAT:
|
||||
return self._device.get_heating_setpoint()
|
||||
return self._zone.get_heating_setpoint()
|
||||
return None
|
||||
|
||||
@property
|
||||
def target_temperature_step(self):
|
||||
"""Step size of temperature units."""
|
||||
if self._device.thermostat.get_unit() == UNIT_FAHRENHEIT:
|
||||
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."""
|
||||
current_mode = self._device.get_current_mode()
|
||||
current_mode = self._zone.get_current_mode()
|
||||
|
||||
if current_mode in (OPERATION_MODE_COOL, OPERATION_MODE_HEAT):
|
||||
return None
|
||||
return self._device.get_cooling_setpoint()
|
||||
return self._zone.get_cooling_setpoint()
|
||||
|
||||
@property
|
||||
def target_temperature_low(self):
|
||||
"""Lowest temperature we are trying to reach."""
|
||||
current_mode = self._device.get_current_mode()
|
||||
current_mode = self._zone.get_current_mode()
|
||||
|
||||
if current_mode in (OPERATION_MODE_COOL, OPERATION_MODE_HEAT):
|
||||
return None
|
||||
return self._device.get_heating_setpoint()
|
||||
return self._zone.get_heating_setpoint()
|
||||
|
||||
@property
|
||||
def hvac_action(self) -> str:
|
||||
"""Operation ie. heat, cool, idle."""
|
||||
system_status = self.thermostat.get_system_status()
|
||||
zone_called = self._device.is_calling()
|
||||
system_status = self._thermostat.get_system_status()
|
||||
zone_called = self._zone.is_calling()
|
||||
|
||||
if self._device.get_requested_mode() == OPERATION_MODE_OFF:
|
||||
if self._zone.get_requested_mode() == OPERATION_MODE_OFF:
|
||||
return CURRENT_HVAC_OFF
|
||||
if not zone_called:
|
||||
return CURRENT_HVAC_IDLE
|
||||
@ -269,8 +297,8 @@ class NexiaZone(NexiaEntity, ClimateDevice):
|
||||
@property
|
||||
def hvac_mode(self):
|
||||
"""Return current mode, as the user-visible name."""
|
||||
mode = self._device.get_requested_mode()
|
||||
hold = self._device.is_in_permanent_hold()
|
||||
mode = self._zone.get_requested_mode()
|
||||
hold = self._zone.is_in_permanent_hold()
|
||||
|
||||
# If the device is in hold mode with
|
||||
# OPERATION_MODE_AUTO
|
||||
@ -299,10 +327,10 @@ class NexiaZone(NexiaEntity, ClimateDevice):
|
||||
new_cool_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH, None)
|
||||
set_temp = kwargs.get(ATTR_TEMPERATURE, None)
|
||||
|
||||
deadband = self.thermostat.get_deadband()
|
||||
cur_cool_temp = self._device.get_cooling_setpoint()
|
||||
cur_heat_temp = self._device.get_heating_setpoint()
|
||||
(min_temp, max_temp) = self.thermostat.get_setpoint_limits()
|
||||
deadband = self._thermostat.get_deadband()
|
||||
cur_cool_temp = self._zone.get_cooling_setpoint()
|
||||
cur_heat_temp = self._zone.get_heating_setpoint()
|
||||
(min_temp, max_temp) = self._thermostat.get_setpoint_limits()
|
||||
|
||||
# Check that we're not going to hit any minimum or maximum values
|
||||
if new_heat_temp and new_heat_temp + deadband > max_temp:
|
||||
@ -318,114 +346,119 @@ class NexiaZone(NexiaEntity, ClimateDevice):
|
||||
if new_cool_temp - new_heat_temp < deadband:
|
||||
new_heat_temp = new_cool_temp - deadband
|
||||
|
||||
self._device.set_heat_cool_temp(
|
||||
self._zone.set_heat_cool_temp(
|
||||
heat_temperature=new_heat_temp,
|
||||
cool_temperature=new_cool_temp,
|
||||
set_temperature=set_temp,
|
||||
)
|
||||
self.schedule_update_ha_state()
|
||||
self._signal_zone_update()
|
||||
|
||||
@property
|
||||
def is_aux_heat(self):
|
||||
"""Emergency heat state."""
|
||||
return self.thermostat.is_emergency_heat_active()
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return the device_info of the device."""
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self._device.zone_id)},
|
||||
"name": self._device.get_name(),
|
||||
"model": self.thermostat.get_model(),
|
||||
"sw_version": self.thermostat.get_firmware(),
|
||||
"manufacturer": MANUFACTURER,
|
||||
"via_device": (DOMAIN, self.thermostat.thermostat_id),
|
||||
}
|
||||
return self._thermostat.is_emergency_heat_active()
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the device specific state attributes."""
|
||||
data = {
|
||||
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||
ATTR_ZONE_STATUS: self._device.get_status(),
|
||||
}
|
||||
data = super().device_state_attributes
|
||||
|
||||
if self._has_relative_humidity:
|
||||
data.update(
|
||||
{
|
||||
ATTR_HUMIDIFY_SUPPORTED: self._has_humidify_support,
|
||||
ATTR_DEHUMIDIFY_SUPPORTED: self._has_dehumidify_support,
|
||||
ATTR_MIN_HUMIDITY: round(
|
||||
self.thermostat.get_humidity_setpoint_limits()[0] * 100.0, 1,
|
||||
),
|
||||
ATTR_MAX_HUMIDITY: round(
|
||||
self.thermostat.get_humidity_setpoint_limits()[1] * 100.0, 1,
|
||||
),
|
||||
}
|
||||
data[ATTR_ZONE_STATUS] = self._zone.get_status()
|
||||
|
||||
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,
|
||||
}
|
||||
)
|
||||
|
||||
if self._has_dehumidify_support:
|
||||
dehumdify_setpoint = percent_conv(
|
||||
self._thermostat.get_dehumidify_setpoint()
|
||||
)
|
||||
if self._has_dehumidify_support:
|
||||
data.update(
|
||||
{
|
||||
ATTR_DEHUMIDIFY_SETPOINT: round(
|
||||
self.thermostat.get_dehumidify_setpoint() * 100.0, 1
|
||||
),
|
||||
}
|
||||
)
|
||||
if self._has_humidify_support:
|
||||
data.update(
|
||||
{
|
||||
ATTR_HUMIDIFY_SETPOINT: round(
|
||||
self.thermostat.get_humidify_setpoint() * 100.0, 1
|
||||
)
|
||||
}
|
||||
)
|
||||
data[ATTR_DEHUMIDIFY_SETPOINT] = dehumdify_setpoint
|
||||
|
||||
if self._has_humidify_support:
|
||||
humdify_setpoint = percent_conv(self._thermostat.get_humidify_setpoint())
|
||||
data[ATTR_HUMIDIFY_SETPOINT] = humdify_setpoint
|
||||
|
||||
return data
|
||||
|
||||
def set_preset_mode(self, preset_mode: str):
|
||||
"""Set the preset mode."""
|
||||
self._device.set_preset(preset_mode)
|
||||
self.schedule_update_ha_state()
|
||||
self._zone.set_preset(preset_mode)
|
||||
self._signal_zone_update()
|
||||
|
||||
def turn_aux_heat_off(self):
|
||||
"""Turn. Aux Heat off."""
|
||||
self.thermostat.set_emergency_heat(False)
|
||||
self.schedule_update_ha_state()
|
||||
self._thermostat.set_emergency_heat(False)
|
||||
self._signal_thermostat_update()
|
||||
|
||||
def turn_aux_heat_on(self):
|
||||
"""Turn. Aux Heat on."""
|
||||
self.thermostat.set_emergency_heat(True)
|
||||
self.schedule_update_ha_state()
|
||||
self._thermostat.set_emergency_heat(True)
|
||||
self._signal_thermostat_update()
|
||||
|
||||
def turn_off(self):
|
||||
"""Turn. off the zone."""
|
||||
self.set_hvac_mode(OPERATION_MODE_OFF)
|
||||
self.schedule_update_ha_state()
|
||||
self._signal_zone_update()
|
||||
|
||||
def turn_on(self):
|
||||
"""Turn. on the zone."""
|
||||
self.set_hvac_mode(OPERATION_MODE_AUTO)
|
||||
self.schedule_update_ha_state()
|
||||
self._signal_zone_update()
|
||||
|
||||
def set_hvac_mode(self, hvac_mode: str) -> None:
|
||||
"""Set the system mode (Auto, Heat_Cool, Cool, Heat, etc)."""
|
||||
if hvac_mode == HVAC_MODE_AUTO:
|
||||
self._device.call_return_to_schedule()
|
||||
self._device.set_mode(mode=OPERATION_MODE_AUTO)
|
||||
self._zone.call_return_to_schedule()
|
||||
self._zone.set_mode(mode=OPERATION_MODE_AUTO)
|
||||
else:
|
||||
self._device.call_permanent_hold()
|
||||
self._device.set_mode(mode=HA_TO_NEXIA_HVAC_MODE_MAP[hvac_mode])
|
||||
self._zone.call_permanent_hold()
|
||||
self._zone.set_mode(mode=HA_TO_NEXIA_HVAC_MODE_MAP[hvac_mode])
|
||||
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def set_aircleaner_mode(self, aircleaner_mode):
|
||||
"""Set the aircleaner mode."""
|
||||
self.thermostat.set_air_cleaner(aircleaner_mode)
|
||||
self.schedule_update_ha_state()
|
||||
self._thermostat.set_air_cleaner(aircleaner_mode)
|
||||
self._signal_thermostat_update()
|
||||
|
||||
def set_humidify_setpoint(self, humidify_setpoint):
|
||||
def set_humidify_setpoint(self, humidity):
|
||||
"""Set the humidify setpoint."""
|
||||
self.thermostat.set_humidify_setpoint(humidify_setpoint / 100.0)
|
||||
self.schedule_update_ha_state()
|
||||
self._thermostat.set_humidify_setpoint(humidity / 100.0)
|
||||
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}")
|
||||
|
||||
async def async_update(self):
|
||||
"""Update the entity.
|
||||
|
@ -7,7 +7,6 @@ ATTRIBUTION = "Data provided by mynexia.com"
|
||||
NOTIFICATION_ID = "nexia_notification"
|
||||
NOTIFICATION_TITLE = "Nexia Setup"
|
||||
|
||||
DATA_NEXIA = "nexia"
|
||||
NEXIA_DEVICE = "device"
|
||||
NEXIA_SCAN_INTERVAL = "scan_interval"
|
||||
|
||||
@ -16,6 +15,8 @@ DEFAULT_ENTITY_NAMESPACE = "nexia"
|
||||
|
||||
ATTR_DESCRIPTION = "description"
|
||||
|
||||
ATTR_AIRCLEANER_MODE = "aircleaner_mode"
|
||||
|
||||
ATTR_ZONE_STATUS = "zone_status"
|
||||
ATTR_HUMIDIFY_SUPPORTED = "humidify_supported"
|
||||
ATTR_DEHUMIDIFY_SUPPORTED = "dehumidify_supported"
|
||||
@ -24,5 +25,7 @@ ATTR_DEHUMIDIFY_SETPOINT = "dehumidify_setpoint"
|
||||
|
||||
UPDATE_COORDINATOR = "update_coordinator"
|
||||
|
||||
|
||||
MANUFACTURER = "Trane"
|
||||
|
||||
SIGNAL_ZONE_UPDATE = "NEXIA_CLIMATE_ZONE_UPDATE"
|
||||
SIGNAL_THERMOSTAT_UPDATE = "NEXIA_CLIMATE_THERMOSTAT_UPDATE"
|
||||
|
@ -1,14 +1,26 @@
|
||||
"""The nexia integration base entity."""
|
||||
|
||||
from homeassistant.const import ATTR_ATTRIBUTION
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from .const import (
|
||||
ATTRIBUTION,
|
||||
DOMAIN,
|
||||
MANUFACTURER,
|
||||
SIGNAL_THERMOSTAT_UPDATE,
|
||||
SIGNAL_ZONE_UPDATE,
|
||||
)
|
||||
|
||||
|
||||
class NexiaEntity(Entity):
|
||||
"""Base class for nexia entities."""
|
||||
|
||||
def __init__(self, coordinator):
|
||||
def __init__(self, coordinator, name, unique_id):
|
||||
"""Initialize the entity."""
|
||||
super().__init__()
|
||||
self._unique_id = unique_id
|
||||
self._name = name
|
||||
self._coordinator = coordinator
|
||||
|
||||
@property
|
||||
@ -16,6 +28,23 @@ class NexiaEntity(Entity):
|
||||
"""Return True if entity is available."""
|
||||
return self._coordinator.last_update_success
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the unique id."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the device specific state attributes."""
|
||||
return {
|
||||
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||
}
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Return False, updates are controlled via coordinator."""
|
||||
@ -28,3 +57,77 @@ class NexiaEntity(Entity):
|
||||
async def async_will_remove_from_hass(self):
|
||||
"""Undo subscription."""
|
||||
self._coordinator.async_remove_listener(self.async_write_ha_state)
|
||||
|
||||
|
||||
class NexiaThermostatEntity(NexiaEntity):
|
||||
"""Base class for nexia devices attached to a thermostat."""
|
||||
|
||||
def __init__(self, coordinator, thermostat, name, unique_id):
|
||||
"""Initialize the entity."""
|
||||
super().__init__(coordinator, name, unique_id)
|
||||
self._thermostat = thermostat
|
||||
self._thermostat_update_subscription = None
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return the device_info of the device."""
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self._thermostat.thermostat_id)},
|
||||
"name": self._thermostat.get_name(),
|
||||
"model": self._thermostat.get_model(),
|
||||
"sw_version": self._thermostat.get_firmware(),
|
||||
"manufacturer": MANUFACTURER,
|
||||
}
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Listen for signals for services."""
|
||||
await super().async_added_to_hass()
|
||||
self._thermostat_update_subscription = async_dispatcher_connect(
|
||||
self.hass,
|
||||
f"{SIGNAL_THERMOSTAT_UPDATE}-{self._thermostat.thermostat_id}",
|
||||
self.async_write_ha_state,
|
||||
)
|
||||
|
||||
async def async_will_remove_from_hass(self):
|
||||
"""Unsub from signals for services."""
|
||||
await super().async_will_remove_from_hass()
|
||||
if self._thermostat_update_subscription:
|
||||
self._thermostat_update_subscription()
|
||||
|
||||
|
||||
class NexiaThermostatZoneEntity(NexiaThermostatEntity):
|
||||
"""Base class for nexia devices attached to a thermostat."""
|
||||
|
||||
def __init__(self, coordinator, zone, name, unique_id):
|
||||
"""Initialize the entity."""
|
||||
super().__init__(coordinator, zone.thermostat, name, unique_id)
|
||||
self._zone = zone
|
||||
self._zone_update_subscription = None
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return the device_info of the device."""
|
||||
data = super().device_info
|
||||
data.update(
|
||||
{
|
||||
"identifiers": {(DOMAIN, self._zone.zone_id)},
|
||||
"name": self._zone.get_name(),
|
||||
"via_device": (DOMAIN, self._zone.thermostat.thermostat_id),
|
||||
}
|
||||
)
|
||||
return data
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Listen for signals for services."""
|
||||
await super().async_added_to_hass()
|
||||
self._zone_update_subscription = async_dispatcher_connect(
|
||||
self.hass,
|
||||
f"{SIGNAL_ZONE_UPDATE}-{self._zone.zone_id}",
|
||||
self.async_write_ha_state,
|
||||
)
|
||||
|
||||
async def async_will_remove_from_hass(self):
|
||||
"""Unsub from signals for services."""
|
||||
await super().async_will_remove_from_hass()
|
||||
if self._zone_update_subscription:
|
||||
self._zone_update_subscription()
|
||||
|
@ -1,23 +1,18 @@
|
||||
"""Support for Nexia Automations."""
|
||||
|
||||
from homeassistant.components.scene import Scene
|
||||
from homeassistant.const import ATTR_ATTRIBUTION
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
|
||||
from .const import (
|
||||
ATTR_DESCRIPTION,
|
||||
ATTRIBUTION,
|
||||
DATA_NEXIA,
|
||||
DOMAIN,
|
||||
NEXIA_DEVICE,
|
||||
UPDATE_COORDINATOR,
|
||||
)
|
||||
from .const import ATTR_DESCRIPTION, DOMAIN, NEXIA_DEVICE, UPDATE_COORDINATOR
|
||||
from .entity import NexiaEntity
|
||||
|
||||
SCENE_ACTIVATION_TIME = 5
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up automations for a Nexia device."""
|
||||
|
||||
nexia_data = hass.data[DOMAIN][config_entry.entry_id][DATA_NEXIA]
|
||||
nexia_data = hass.data[DOMAIN][config_entry.entry_id]
|
||||
nexia_home = nexia_data[NEXIA_DEVICE]
|
||||
coordinator = nexia_data[UPDATE_COORDINATOR]
|
||||
entities = []
|
||||
@ -36,33 +31,28 @@ class NexiaAutomationScene(NexiaEntity, Scene):
|
||||
|
||||
def __init__(self, coordinator, automation):
|
||||
"""Initialize the automation scene."""
|
||||
super().__init__(coordinator)
|
||||
super().__init__(
|
||||
coordinator, name=automation.name, unique_id=automation.automation_id,
|
||||
)
|
||||
self._automation = automation
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the unique id of the automation scene."""
|
||||
# This is the automation unique_id
|
||||
return self._automation.automation_id
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the automation scene."""
|
||||
return self._automation.name
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the scene specific state attributes."""
|
||||
return {
|
||||
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||
ATTR_DESCRIPTION: self._automation.description,
|
||||
}
|
||||
data = super().device_state_attributes
|
||||
data[ATTR_DESCRIPTION] = self._automation.description
|
||||
return data
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon of the automation scene."""
|
||||
return "mdi:script-text-outline"
|
||||
|
||||
def activate(self):
|
||||
async def async_activate(self):
|
||||
"""Activate an automation scene."""
|
||||
self._automation.activate()
|
||||
await self.hass.async_add_executor_job(self._automation.activate)
|
||||
|
||||
async def refresh_callback(_):
|
||||
await self._coordinator.async_refresh()
|
||||
|
||||
async_call_later(self.hass, SCENE_ACTIVATION_TIME, refresh_callback)
|
||||
|
@ -3,7 +3,6 @@
|
||||
from nexia.const import UNIT_CELSIUS
|
||||
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
TEMP_CELSIUS,
|
||||
@ -11,21 +10,15 @@ from homeassistant.const import (
|
||||
UNIT_PERCENTAGE,
|
||||
)
|
||||
|
||||
from .const import (
|
||||
ATTRIBUTION,
|
||||
DATA_NEXIA,
|
||||
DOMAIN,
|
||||
MANUFACTURER,
|
||||
NEXIA_DEVICE,
|
||||
UPDATE_COORDINATOR,
|
||||
)
|
||||
from .entity import NexiaEntity
|
||||
from .const import DOMAIN, NEXIA_DEVICE, UPDATE_COORDINATOR
|
||||
from .entity import NexiaThermostatEntity, NexiaThermostatZoneEntity
|
||||
from .util import percent_conv
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up sensors for a Nexia device."""
|
||||
|
||||
nexia_data = hass.data[DOMAIN][config_entry.entry_id][DATA_NEXIA]
|
||||
nexia_data = hass.data[DOMAIN][config_entry.entry_id]
|
||||
nexia_home = nexia_data[NEXIA_DEVICE]
|
||||
coordinator = nexia_data[UPDATE_COORDINATOR]
|
||||
entities = []
|
||||
@ -35,7 +28,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
thermostat = nexia_home.get_thermostat_by_id(thermostat_id)
|
||||
|
||||
entities.append(
|
||||
NexiaSensor(
|
||||
NexiaThermostatSensor(
|
||||
coordinator,
|
||||
thermostat,
|
||||
"get_system_status",
|
||||
@ -46,7 +39,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
)
|
||||
# Air cleaner
|
||||
entities.append(
|
||||
NexiaSensor(
|
||||
NexiaThermostatSensor(
|
||||
coordinator,
|
||||
thermostat,
|
||||
"get_air_cleaner_mode",
|
||||
@ -58,7 +51,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
# Compressor Speed
|
||||
if thermostat.has_variable_speed_compressor():
|
||||
entities.append(
|
||||
NexiaSensor(
|
||||
NexiaThermostatSensor(
|
||||
coordinator,
|
||||
thermostat,
|
||||
"get_current_compressor_speed",
|
||||
@ -69,7 +62,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
)
|
||||
)
|
||||
entities.append(
|
||||
NexiaSensor(
|
||||
NexiaThermostatSensor(
|
||||
coordinator,
|
||||
thermostat,
|
||||
"get_requested_compressor_speed",
|
||||
@ -87,7 +80,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
else TEMP_FAHRENHEIT
|
||||
)
|
||||
entities.append(
|
||||
NexiaSensor(
|
||||
NexiaThermostatSensor(
|
||||
coordinator,
|
||||
thermostat,
|
||||
"get_outdoor_temperature",
|
||||
@ -99,7 +92,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
# Relative Humidity
|
||||
if thermostat.has_relative_humidity():
|
||||
entities.append(
|
||||
NexiaSensor(
|
||||
NexiaThermostatSensor(
|
||||
coordinator,
|
||||
thermostat,
|
||||
"get_relative_humidity",
|
||||
@ -120,7 +113,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
)
|
||||
# Temperature
|
||||
entities.append(
|
||||
NexiaZoneSensor(
|
||||
NexiaThermostatZoneSensor(
|
||||
coordinator,
|
||||
zone,
|
||||
"get_temperature",
|
||||
@ -132,13 +125,13 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
)
|
||||
# Zone Status
|
||||
entities.append(
|
||||
NexiaZoneSensor(
|
||||
NexiaThermostatZoneSensor(
|
||||
coordinator, zone, "get_status", "Zone Status", None, None,
|
||||
)
|
||||
)
|
||||
# Setpoint Status
|
||||
entities.append(
|
||||
NexiaZoneSensor(
|
||||
NexiaThermostatZoneSensor(
|
||||
coordinator,
|
||||
zone,
|
||||
"get_setpoint_status",
|
||||
@ -151,18 +144,13 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
async_add_entities(entities, True)
|
||||
|
||||
|
||||
def percent_conv(val):
|
||||
"""Convert an actual percentage (0.0-1.0) to 0-100 scale."""
|
||||
return val * 100.0
|
||||
|
||||
|
||||
class NexiaSensor(NexiaEntity):
|
||||
class NexiaThermostatSensor(NexiaThermostatEntity):
|
||||
"""Provides Nexia thermostat sensor support."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator,
|
||||
device,
|
||||
thermostat,
|
||||
sensor_call,
|
||||
sensor_name,
|
||||
sensor_class,
|
||||
@ -170,35 +158,18 @@ class NexiaSensor(NexiaEntity):
|
||||
modifier=None,
|
||||
):
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(coordinator)
|
||||
self._coordinator = coordinator
|
||||
self._device = device
|
||||
super().__init__(
|
||||
coordinator,
|
||||
thermostat,
|
||||
name=f"{thermostat.get_name()} {sensor_name}",
|
||||
unique_id=f"{thermostat.thermostat_id}_{sensor_call}",
|
||||
)
|
||||
self._call = sensor_call
|
||||
self._sensor_name = sensor_name
|
||||
self._class = sensor_class
|
||||
self._state = None
|
||||
self._name = f"{self._device.get_name()} {self._sensor_name}"
|
||||
self._unit_of_measurement = sensor_unit
|
||||
self._modifier = modifier
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the unique id of the sensor."""
|
||||
# This is the thermostat unique_id
|
||||
return f"{self._device.thermostat_id}_{self._call}"
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the device specific state attributes."""
|
||||
return {
|
||||
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||
}
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the device class of the sensor."""
|
||||
@ -207,7 +178,7 @@ class NexiaSensor(NexiaEntity):
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the sensor."""
|
||||
val = getattr(self._device, self._call)()
|
||||
val = getattr(self._thermostat, self._call)()
|
||||
if self._modifier:
|
||||
val = self._modifier(val)
|
||||
if isinstance(val, float):
|
||||
@ -219,25 +190,14 @@ class NexiaSensor(NexiaEntity):
|
||||
"""Return the unit of measurement this sensor expresses itself in."""
|
||||
return self._unit_of_measurement
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return the device_info of the device."""
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self._device.thermostat_id)},
|
||||
"name": self._device.get_name(),
|
||||
"model": self._device.get_model(),
|
||||
"sw_version": self._device.get_firmware(),
|
||||
"manufacturer": MANUFACTURER,
|
||||
}
|
||||
|
||||
|
||||
class NexiaZoneSensor(NexiaSensor):
|
||||
class NexiaThermostatZoneSensor(NexiaThermostatZoneEntity):
|
||||
"""Nexia Zone Sensor Support."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator,
|
||||
device,
|
||||
zone,
|
||||
sensor_call,
|
||||
sensor_name,
|
||||
sensor_class,
|
||||
@ -248,29 +208,32 @@ class NexiaZoneSensor(NexiaSensor):
|
||||
|
||||
super().__init__(
|
||||
coordinator,
|
||||
device,
|
||||
sensor_call,
|
||||
sensor_name,
|
||||
sensor_class,
|
||||
sensor_unit,
|
||||
modifier,
|
||||
zone,
|
||||
name=f"{zone.get_name()} {sensor_name}",
|
||||
unique_id=f"{zone.zone_id}_{sensor_call}",
|
||||
)
|
||||
self._device = device
|
||||
self._call = sensor_call
|
||||
self._class = sensor_class
|
||||
self._state = None
|
||||
self._unit_of_measurement = sensor_unit
|
||||
self._modifier = modifier
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the unique id of the sensor."""
|
||||
# This is the zone unique_id
|
||||
return f"{self._device.zone_id}_{self._call}"
|
||||
def device_class(self):
|
||||
"""Return the device class of the sensor."""
|
||||
return self._class
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return the device_info of the device."""
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self._device.zone_id)},
|
||||
"name": self._device.get_name(),
|
||||
"model": self._device.thermostat.get_model(),
|
||||
"sw_version": self._device.thermostat.get_firmware(),
|
||||
"manufacturer": MANUFACTURER,
|
||||
"via_device": (DOMAIN, self._device.thermostat.thermostat_id),
|
||||
}
|
||||
def state(self):
|
||||
"""Return the state of the sensor."""
|
||||
val = getattr(self._zone, self._call)()
|
||||
if self._modifier:
|
||||
val = self._modifier(val)
|
||||
if isinstance(val, float):
|
||||
val = round(val, 1)
|
||||
return val
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement this sensor expresses itself in."""
|
||||
return self._unit_of_measurement
|
||||
|
19
homeassistant/components/nexia/services.yaml
Normal file
19
homeassistant/components/nexia/services.yaml
Normal file
@ -0,0 +1,19 @@
|
||||
set_aircleaner_mode:
|
||||
description: "The air cleaner mode."
|
||||
fields:
|
||||
entity_id:
|
||||
description: "This setting will affect all zones connected to the thermostat."
|
||||
example: climate.master_bedroom
|
||||
aircleaner_mode:
|
||||
description: "The air cleaner mode to set. Options include \"auto\", \"quick\", or \"allergy\"."
|
||||
example: allergy
|
||||
|
||||
set_humidify_setpoint:
|
||||
description: "The humidification set point."
|
||||
fields:
|
||||
entity_id:
|
||||
description: "This setting will affect all zones connected to the thermostat."
|
||||
example: climate.master_bedroom
|
||||
humidity:
|
||||
description: "The humidification setpoint as an int, range 35-65."
|
||||
example: 45
|
6
homeassistant/components/nexia/util.py
Normal file
6
homeassistant/components/nexia/util.py
Normal file
@ -0,0 +1,6 @@
|
||||
"""Utils for Nexia / Trane XL Thermostats."""
|
||||
|
||||
|
||||
def percent_conv(val):
|
||||
"""Convert an actual percentage (0.0-1.0) to 0-100 scale."""
|
||||
return round(val * 100.0, 1)
|
35
tests/components/nexia/test_binary_sensor.py
Normal file
35
tests/components/nexia/test_binary_sensor.py
Normal file
@ -0,0 +1,35 @@
|
||||
"""The binary_sensor tests for the nexia platform."""
|
||||
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
|
||||
from .util import async_init_integration
|
||||
|
||||
|
||||
async def test_create_binary_sensors(hass):
|
||||
"""Test creation of binary sensors."""
|
||||
|
||||
await async_init_integration(hass)
|
||||
|
||||
state = hass.states.get("binary_sensor.master_suite_blower_active")
|
||||
assert state.state == STATE_ON
|
||||
expected_attributes = {
|
||||
"attribution": "Data provided by mynexia.com",
|
||||
"friendly_name": "Master Suite Blower Active",
|
||||
}
|
||||
# Only test for a subset of attributes in case
|
||||
# HA changes the implementation and a new one appears
|
||||
assert all(
|
||||
state.attributes[key] == expected_attributes[key] for key in expected_attributes
|
||||
)
|
||||
|
||||
state = hass.states.get("binary_sensor.downstairs_east_wing_blower_active")
|
||||
assert state.state == STATE_OFF
|
||||
expected_attributes = {
|
||||
"attribution": "Data provided by mynexia.com",
|
||||
"friendly_name": "Downstairs East Wing Blower Active",
|
||||
}
|
||||
# Only test for a subset of attributes in case
|
||||
# HA changes the implementation and a new one appears
|
||||
assert all(
|
||||
state.attributes[key] == expected_attributes[key] for key in expected_attributes
|
||||
)
|
@ -43,3 +43,38 @@ async def test_climate_zones(hass):
|
||||
assert all(
|
||||
state.attributes[key] == expected_attributes[key] for key in expected_attributes
|
||||
)
|
||||
|
||||
state = hass.states.get("climate.kitchen")
|
||||
assert state.state == HVAC_MODE_HEAT_COOL
|
||||
|
||||
expected_attributes = {
|
||||
"attribution": "Data provided by mynexia.com",
|
||||
"current_humidity": 36.0,
|
||||
"current_temperature": 25.0,
|
||||
"dehumidify_setpoint": 50.0,
|
||||
"dehumidify_supported": True,
|
||||
"fan_mode": "auto",
|
||||
"fan_modes": ["auto", "on", "circulate"],
|
||||
"friendly_name": "Kitchen",
|
||||
"humidify_supported": False,
|
||||
"humidity": 50.0,
|
||||
"hvac_action": "idle",
|
||||
"hvac_modes": ["off", "auto", "heat_cool", "heat", "cool"],
|
||||
"max_humidity": 65.0,
|
||||
"max_temp": 37.2,
|
||||
"min_humidity": 35.0,
|
||||
"min_temp": 12.8,
|
||||
"preset_mode": "None",
|
||||
"preset_modes": ["None", "Home", "Away", "Sleep"],
|
||||
"supported_features": 31,
|
||||
"target_temp_high": 26.1,
|
||||
"target_temp_low": 17.2,
|
||||
"target_temp_step": 1.0,
|
||||
"temperature": None,
|
||||
"zone_status": "",
|
||||
}
|
||||
# Only test for a subset of attributes in case
|
||||
# HA changes the implementation and a new one appears
|
||||
assert all(
|
||||
state.attributes[key] == expected_attributes[key] for key in expected_attributes
|
||||
)
|
||||
|
@ -1,10 +1,10 @@
|
||||
"""The lock tests for the august platform."""
|
||||
"""The scene tests for the nexia platform."""
|
||||
|
||||
from .util import async_init_integration
|
||||
|
||||
|
||||
async def test_automation_scenees(hass):
|
||||
"""Test creation automation scenees."""
|
||||
async def test_automation_scenes(hass):
|
||||
"""Test creation automation scenes."""
|
||||
|
||||
await async_init_integration(hass)
|
||||
|
||||
|
133
tests/components/nexia/test_sensor.py
Normal file
133
tests/components/nexia/test_sensor.py
Normal file
@ -0,0 +1,133 @@
|
||||
"""The sensor tests for the nexia platform."""
|
||||
|
||||
from .util import async_init_integration
|
||||
|
||||
|
||||
async def test_create_sensors(hass):
|
||||
"""Test creation of sensors."""
|
||||
|
||||
await async_init_integration(hass)
|
||||
|
||||
state = hass.states.get("sensor.nick_office_temperature")
|
||||
assert state.state == "23"
|
||||
|
||||
expected_attributes = {
|
||||
"attribution": "Data provided by mynexia.com",
|
||||
"device_class": "temperature",
|
||||
"friendly_name": "Nick Office Temperature",
|
||||
"unit_of_measurement": "°C",
|
||||
}
|
||||
# Only test for a subset of attributes in case
|
||||
# HA changes the implementation and a new one appears
|
||||
assert all(
|
||||
state.attributes[key] == expected_attributes[key] for key in expected_attributes
|
||||
)
|
||||
|
||||
state = hass.states.get("sensor.nick_office_zone_setpoint_status")
|
||||
assert state.state == "Permanent Hold"
|
||||
expected_attributes = {
|
||||
"attribution": "Data provided by mynexia.com",
|
||||
"friendly_name": "Nick Office Zone Setpoint Status",
|
||||
}
|
||||
# Only test for a subset of attributes in case
|
||||
# HA changes the implementation and a new one appears
|
||||
assert all(
|
||||
state.attributes[key] == expected_attributes[key] for key in expected_attributes
|
||||
)
|
||||
|
||||
state = hass.states.get("sensor.nick_office_zone_status")
|
||||
assert state.state == "Relieving Air"
|
||||
|
||||
expected_attributes = {
|
||||
"attribution": "Data provided by mynexia.com",
|
||||
"friendly_name": "Nick Office Zone Status",
|
||||
}
|
||||
# Only test for a subset of attributes in case
|
||||
# HA changes the implementation and a new one appears
|
||||
assert all(
|
||||
state.attributes[key] == expected_attributes[key] for key in expected_attributes
|
||||
)
|
||||
|
||||
state = hass.states.get("sensor.master_suite_air_cleaner_mode")
|
||||
assert state.state == "auto"
|
||||
|
||||
expected_attributes = {
|
||||
"attribution": "Data provided by mynexia.com",
|
||||
"friendly_name": "Master Suite Air Cleaner Mode",
|
||||
}
|
||||
# Only test for a subset of attributes in case
|
||||
# HA changes the implementation and a new one appears
|
||||
assert all(
|
||||
state.attributes[key] == expected_attributes[key] for key in expected_attributes
|
||||
)
|
||||
|
||||
state = hass.states.get("sensor.master_suite_current_compressor_speed")
|
||||
assert state.state == "69.0"
|
||||
|
||||
expected_attributes = {
|
||||
"attribution": "Data provided by mynexia.com",
|
||||
"friendly_name": "Master Suite Current Compressor Speed",
|
||||
"unit_of_measurement": "%",
|
||||
}
|
||||
# Only test for a subset of attributes in case
|
||||
# HA changes the implementation and a new one appears
|
||||
assert all(
|
||||
state.attributes[key] == expected_attributes[key] for key in expected_attributes
|
||||
)
|
||||
|
||||
state = hass.states.get("sensor.master_suite_outdoor_temperature")
|
||||
assert state.state == "30.6"
|
||||
|
||||
expected_attributes = {
|
||||
"attribution": "Data provided by mynexia.com",
|
||||
"device_class": "temperature",
|
||||
"friendly_name": "Master Suite Outdoor Temperature",
|
||||
"unit_of_measurement": "°C",
|
||||
}
|
||||
# Only test for a subset of attributes in case
|
||||
# HA changes the implementation and a new one appears
|
||||
assert all(
|
||||
state.attributes[key] == expected_attributes[key] for key in expected_attributes
|
||||
)
|
||||
|
||||
state = hass.states.get("sensor.master_suite_relative_humidity")
|
||||
assert state.state == "52.0"
|
||||
|
||||
expected_attributes = {
|
||||
"attribution": "Data provided by mynexia.com",
|
||||
"device_class": "humidity",
|
||||
"friendly_name": "Master Suite Relative Humidity",
|
||||
"unit_of_measurement": "%",
|
||||
}
|
||||
# Only test for a subset of attributes in case
|
||||
# HA changes the implementation and a new one appears
|
||||
assert all(
|
||||
state.attributes[key] == expected_attributes[key] for key in expected_attributes
|
||||
)
|
||||
|
||||
state = hass.states.get("sensor.master_suite_requested_compressor_speed")
|
||||
assert state.state == "69.0"
|
||||
|
||||
expected_attributes = {
|
||||
"attribution": "Data provided by mynexia.com",
|
||||
"friendly_name": "Master Suite Requested Compressor Speed",
|
||||
"unit_of_measurement": "%",
|
||||
}
|
||||
# Only test for a subset of attributes in case
|
||||
# HA changes the implementation and a new one appears
|
||||
assert all(
|
||||
state.attributes[key] == expected_attributes[key] for key in expected_attributes
|
||||
)
|
||||
|
||||
state = hass.states.get("sensor.master_suite_system_status")
|
||||
assert state.state == "Cooling"
|
||||
|
||||
expected_attributes = {
|
||||
"attribution": "Data provided by mynexia.com",
|
||||
"friendly_name": "Master Suite System Status",
|
||||
}
|
||||
# Only test for a subset of attributes in case
|
||||
# HA changes the implementation and a new one appears
|
||||
assert all(
|
||||
state.attributes[key] == expected_attributes[key] for key in expected_attributes
|
||||
)
|
Loading…
x
Reference in New Issue
Block a user