mirror of
https://github.com/home-assistant/core.git
synced 2025-07-08 22:07:10 +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
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
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__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -94,8 +94,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
|||||||
update_interval=timedelta(seconds=DEFAULT_UPDATE_RATE),
|
update_interval=timedelta(seconds=DEFAULT_UPDATE_RATE),
|
||||||
)
|
)
|
||||||
|
|
||||||
hass.data[DOMAIN][entry.entry_id] = {}
|
hass.data[DOMAIN][entry.entry_id] = {
|
||||||
hass.data[DOMAIN][entry.entry_id][DATA_NEXIA] = {
|
|
||||||
NEXIA_DEVICE: nexia_home,
|
NEXIA_DEVICE: nexia_home,
|
||||||
UPDATE_COORDINATOR: coordinator,
|
UPDATE_COORDINATOR: coordinator,
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,15 @@
|
|||||||
"""Support for Nexia / Trane XL Thermostats."""
|
"""Support for Nexia / Trane XL Thermostats."""
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||||
from homeassistant.const import ATTR_ATTRIBUTION
|
|
||||||
|
|
||||||
from .const import (
|
from .const import DOMAIN, NEXIA_DEVICE, UPDATE_COORDINATOR
|
||||||
ATTRIBUTION,
|
from .entity import NexiaThermostatEntity
|
||||||
DATA_NEXIA,
|
|
||||||
DOMAIN,
|
|
||||||
MANUFACTURER,
|
|
||||||
NEXIA_DEVICE,
|
|
||||||
UPDATE_COORDINATOR,
|
|
||||||
)
|
|
||||||
from .entity import NexiaEntity
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up sensors for a Nexia device."""
|
"""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]
|
nexia_home = nexia_data[NEXIA_DEVICE]
|
||||||
coordinator = nexia_data[UPDATE_COORDINATOR]
|
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)
|
async_add_entities(entities, True)
|
||||||
|
|
||||||
|
|
||||||
class NexiaBinarySensor(NexiaEntity, BinarySensorDevice):
|
class NexiaBinarySensor(NexiaThermostatEntity, BinarySensorDevice):
|
||||||
"""Provices Nexia BinarySensor support."""
|
"""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."""
|
"""Initialize the nexia sensor."""
|
||||||
super().__init__(coordinator)
|
super().__init__(
|
||||||
self._coordinator = coordinator
|
coordinator,
|
||||||
self._device = device
|
thermostat,
|
||||||
self._name = f"{self._device.get_name()} {sensor_name}"
|
name=f"{thermostat.get_name()} {sensor_name}",
|
||||||
|
unique_id=f"{thermostat.thermostat_id}_{sensor_call}",
|
||||||
|
)
|
||||||
self._call = sensor_call
|
self._call = sensor_call
|
||||||
self._unique_id = f"{self._device.thermostat_id}_{sensor_call}"
|
|
||||||
self._state = None
|
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
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
"""Return the status of the sensor."""
|
"""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,
|
SYSTEM_STATUS_IDLE,
|
||||||
UNIT_FAHRENHEIT,
|
UNIT_FAHRENHEIT,
|
||||||
)
|
)
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.climate import ClimateDevice
|
from homeassistant.components.climate import ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
|
ATTR_HUMIDITY,
|
||||||
ATTR_MAX_HUMIDITY,
|
ATTR_MAX_HUMIDITY,
|
||||||
ATTR_MIN_HUMIDITY,
|
ATTR_MIN_HUMIDITY,
|
||||||
ATTR_TARGET_TEMP_HIGH,
|
ATTR_TARGET_TEMP_HIGH,
|
||||||
@ -36,26 +38,50 @@ from homeassistant.components.climate.const import (
|
|||||||
SUPPORT_TARGET_TEMPERATURE_RANGE,
|
SUPPORT_TARGET_TEMPERATURE_RANGE,
|
||||||
)
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ATTRIBUTION,
|
ATTR_ENTITY_ID,
|
||||||
ATTR_TEMPERATURE,
|
ATTR_TEMPERATURE,
|
||||||
TEMP_CELSIUS,
|
TEMP_CELSIUS,
|
||||||
TEMP_FAHRENHEIT,
|
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 (
|
from .const import (
|
||||||
|
ATTR_AIRCLEANER_MODE,
|
||||||
ATTR_DEHUMIDIFY_SETPOINT,
|
ATTR_DEHUMIDIFY_SETPOINT,
|
||||||
ATTR_DEHUMIDIFY_SUPPORTED,
|
ATTR_DEHUMIDIFY_SUPPORTED,
|
||||||
ATTR_HUMIDIFY_SETPOINT,
|
ATTR_HUMIDIFY_SETPOINT,
|
||||||
ATTR_HUMIDIFY_SUPPORTED,
|
ATTR_HUMIDIFY_SUPPORTED,
|
||||||
ATTR_ZONE_STATUS,
|
ATTR_ZONE_STATUS,
|
||||||
ATTRIBUTION,
|
|
||||||
DATA_NEXIA,
|
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
MANUFACTURER,
|
|
||||||
NEXIA_DEVICE,
|
NEXIA_DEVICE,
|
||||||
|
SIGNAL_THERMOSTAT_UPDATE,
|
||||||
|
SIGNAL_ZONE_UPDATE,
|
||||||
UPDATE_COORDINATOR,
|
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__)
|
_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):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up climate for a Nexia device."""
|
"""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]
|
nexia_home = nexia_data[NEXIA_DEVICE]
|
||||||
coordinator = nexia_data[UPDATE_COORDINATOR]
|
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 = []
|
entities = []
|
||||||
for thermostat_id in nexia_home.get_thermostat_ids():
|
for thermostat_id in nexia_home.get_thermostat_ids():
|
||||||
thermostat = nexia_home.get_thermostat_by_id(thermostat_id)
|
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)
|
async_add_entities(entities, True)
|
||||||
|
|
||||||
|
|
||||||
class NexiaZone(NexiaEntity, ClimateDevice):
|
class NexiaZone(NexiaThermostatZoneEntity, ClimateDevice):
|
||||||
"""Provides Nexia Climate support."""
|
"""Provides Nexia Climate support."""
|
||||||
|
|
||||||
def __init__(self, coordinator, device):
|
def __init__(self, coordinator, zone):
|
||||||
"""Initialize the thermostat."""
|
"""Initialize the thermostat."""
|
||||||
super().__init__(coordinator)
|
super().__init__(
|
||||||
self.thermostat = device.thermostat
|
coordinator, zone, name=zone.get_name(), unique_id=zone.zone_id
|
||||||
self._device = device
|
)
|
||||||
self._coordinator = coordinator
|
self._undo_humidfy_dispatcher = None
|
||||||
|
self._undo_aircleaner_dispatcher = None
|
||||||
# The has_* calls are stable for the life of the device
|
# The has_* calls are stable for the life of the device
|
||||||
# and do not do I/O
|
# and do not do I/O
|
||||||
self._has_relative_humidity = self.thermostat.has_relative_humidity()
|
self._has_relative_humidity = self._thermostat.has_relative_humidity()
|
||||||
self._has_emergency_heat = self.thermostat.has_emergency_heat()
|
self._has_emergency_heat = self._thermostat.has_emergency_heat()
|
||||||
self._has_humidify_support = self.thermostat.has_humidify_support()
|
self._has_humidify_support = self._thermostat.has_humidify_support()
|
||||||
self._has_dehumidify_support = self.thermostat.has_dehumidify_support()
|
self._has_dehumidify_support = self._thermostat.has_dehumidify_support()
|
||||||
|
|
||||||
@property
|
|
||||||
def unique_id(self):
|
|
||||||
"""Device Uniqueid."""
|
|
||||||
return self._device.zone_id
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
@ -139,27 +172,22 @@ class NexiaZone(NexiaEntity, ClimateDevice):
|
|||||||
@property
|
@property
|
||||||
def is_fan_on(self):
|
def is_fan_on(self):
|
||||||
"""Blower is on."""
|
"""Blower is on."""
|
||||||
return self.thermostat.is_blower_active()
|
return self._thermostat.is_blower_active()
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""Name of the zone."""
|
|
||||||
return self._device.get_name()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def temperature_unit(self):
|
def temperature_unit(self):
|
||||||
"""Return the unit of measurement."""
|
"""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
|
@property
|
||||||
def current_temperature(self):
|
def current_temperature(self):
|
||||||
"""Return the current temperature."""
|
"""Return the current temperature."""
|
||||||
return self._device.get_temperature()
|
return self._zone.get_temperature()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_mode(self):
|
def fan_mode(self):
|
||||||
"""Return the fan setting."""
|
"""Return the fan setting."""
|
||||||
return self.thermostat.get_fan_mode()
|
return self._thermostat.get_fan_mode()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_modes(self):
|
def fan_modes(self):
|
||||||
@ -169,92 +197,92 @@ class NexiaZone(NexiaEntity, ClimateDevice):
|
|||||||
@property
|
@property
|
||||||
def min_temp(self):
|
def min_temp(self):
|
||||||
"""Minimum temp for the current setting."""
|
"""Minimum temp for the current setting."""
|
||||||
return (self._device.thermostat.get_setpoint_limits())[0]
|
return (self._thermostat.get_setpoint_limits())[0]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max_temp(self):
|
def max_temp(self):
|
||||||
"""Maximum temp for the current setting."""
|
"""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):
|
def set_fan_mode(self, fan_mode):
|
||||||
"""Set new target fan mode."""
|
"""Set new target fan mode."""
|
||||||
self.thermostat.set_fan_mode(fan_mode)
|
self._thermostat.set_fan_mode(fan_mode)
|
||||||
self.schedule_update_ha_state()
|
self._signal_thermostat_update()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def preset_mode(self):
|
def preset_mode(self):
|
||||||
"""Preset that is active."""
|
"""Preset that is active."""
|
||||||
return self._device.get_preset()
|
return self._zone.get_preset()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def preset_modes(self):
|
def preset_modes(self):
|
||||||
"""All presets."""
|
"""All presets."""
|
||||||
return self._device.get_presets()
|
return self._zone.get_presets()
|
||||||
|
|
||||||
def set_humidity(self, humidity):
|
def set_humidity(self, humidity):
|
||||||
"""Dehumidify target."""
|
"""Dehumidify target."""
|
||||||
self.thermostat.set_dehumidify_setpoint(humidity / 100.0)
|
self._thermostat.set_dehumidify_setpoint(humidity / 100.0)
|
||||||
self.schedule_update_ha_state()
|
self._signal_thermostat_update()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_humidity(self):
|
def target_humidity(self):
|
||||||
"""Humidity indoors setpoint."""
|
"""Humidity indoors setpoint."""
|
||||||
if self._has_dehumidify_support:
|
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:
|
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
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_humidity(self):
|
def current_humidity(self):
|
||||||
"""Humidity indoors."""
|
"""Humidity indoors."""
|
||||||
if self._has_relative_humidity:
|
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
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature(self):
|
def target_temperature(self):
|
||||||
"""Temperature we try to reach."""
|
"""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:
|
if current_mode == OPERATION_MODE_COOL:
|
||||||
return self._device.get_cooling_setpoint()
|
return self._zone.get_cooling_setpoint()
|
||||||
if current_mode == OPERATION_MODE_HEAT:
|
if current_mode == OPERATION_MODE_HEAT:
|
||||||
return self._device.get_heating_setpoint()
|
return self._zone.get_heating_setpoint()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature_step(self):
|
def target_temperature_step(self):
|
||||||
"""Step size of temperature units."""
|
"""Step size of temperature units."""
|
||||||
if self._device.thermostat.get_unit() == UNIT_FAHRENHEIT:
|
if self._thermostat.get_unit() == UNIT_FAHRENHEIT:
|
||||||
return 1.0
|
return 1.0
|
||||||
return 0.5
|
return 0.5
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature_high(self):
|
def target_temperature_high(self):
|
||||||
"""Highest temperature we are trying to reach."""
|
"""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):
|
if current_mode in (OPERATION_MODE_COOL, OPERATION_MODE_HEAT):
|
||||||
return None
|
return None
|
||||||
return self._device.get_cooling_setpoint()
|
return self._zone.get_cooling_setpoint()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature_low(self):
|
def target_temperature_low(self):
|
||||||
"""Lowest temperature we are trying to reach."""
|
"""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):
|
if current_mode in (OPERATION_MODE_COOL, OPERATION_MODE_HEAT):
|
||||||
return None
|
return None
|
||||||
return self._device.get_heating_setpoint()
|
return self._zone.get_heating_setpoint()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hvac_action(self) -> str:
|
def hvac_action(self) -> str:
|
||||||
"""Operation ie. heat, cool, idle."""
|
"""Operation ie. heat, cool, idle."""
|
||||||
system_status = self.thermostat.get_system_status()
|
system_status = self._thermostat.get_system_status()
|
||||||
zone_called = self._device.is_calling()
|
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
|
return CURRENT_HVAC_OFF
|
||||||
if not zone_called:
|
if not zone_called:
|
||||||
return CURRENT_HVAC_IDLE
|
return CURRENT_HVAC_IDLE
|
||||||
@ -269,8 +297,8 @@ class NexiaZone(NexiaEntity, ClimateDevice):
|
|||||||
@property
|
@property
|
||||||
def hvac_mode(self):
|
def hvac_mode(self):
|
||||||
"""Return current mode, as the user-visible name."""
|
"""Return current mode, as the user-visible name."""
|
||||||
mode = self._device.get_requested_mode()
|
mode = self._zone.get_requested_mode()
|
||||||
hold = self._device.is_in_permanent_hold()
|
hold = self._zone.is_in_permanent_hold()
|
||||||
|
|
||||||
# If the device is in hold mode with
|
# If the device is in hold mode with
|
||||||
# OPERATION_MODE_AUTO
|
# OPERATION_MODE_AUTO
|
||||||
@ -299,10 +327,10 @@ class NexiaZone(NexiaEntity, ClimateDevice):
|
|||||||
new_cool_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH, None)
|
new_cool_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH, None)
|
||||||
set_temp = kwargs.get(ATTR_TEMPERATURE, None)
|
set_temp = kwargs.get(ATTR_TEMPERATURE, None)
|
||||||
|
|
||||||
deadband = self.thermostat.get_deadband()
|
deadband = self._thermostat.get_deadband()
|
||||||
cur_cool_temp = self._device.get_cooling_setpoint()
|
cur_cool_temp = self._zone.get_cooling_setpoint()
|
||||||
cur_heat_temp = self._device.get_heating_setpoint()
|
cur_heat_temp = self._zone.get_heating_setpoint()
|
||||||
(min_temp, max_temp) = self.thermostat.get_setpoint_limits()
|
(min_temp, max_temp) = self._thermostat.get_setpoint_limits()
|
||||||
|
|
||||||
# Check that we're not going to hit any minimum or maximum values
|
# Check that we're not going to hit any minimum or maximum values
|
||||||
if new_heat_temp and new_heat_temp + deadband > max_temp:
|
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:
|
if new_cool_temp - new_heat_temp < deadband:
|
||||||
new_heat_temp = new_cool_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,
|
heat_temperature=new_heat_temp,
|
||||||
cool_temperature=new_cool_temp,
|
cool_temperature=new_cool_temp,
|
||||||
set_temperature=set_temp,
|
set_temperature=set_temp,
|
||||||
)
|
)
|
||||||
self.schedule_update_ha_state()
|
self._signal_zone_update()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_aux_heat(self):
|
def is_aux_heat(self):
|
||||||
"""Emergency heat state."""
|
"""Emergency heat state."""
|
||||||
return self.thermostat.is_emergency_heat_active()
|
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),
|
|
||||||
}
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
"""Return the device specific state attributes."""
|
"""Return the device specific state attributes."""
|
||||||
data = {
|
data = super().device_state_attributes
|
||||||
ATTR_ATTRIBUTION: ATTRIBUTION,
|
|
||||||
ATTR_ZONE_STATUS: self._device.get_status(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if self._has_relative_humidity:
|
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(
|
data.update(
|
||||||
{
|
{
|
||||||
ATTR_HUMIDIFY_SUPPORTED: self._has_humidify_support,
|
ATTR_MIN_HUMIDITY: min_humidity,
|
||||||
|
ATTR_MAX_HUMIDITY: max_humidity,
|
||||||
ATTR_DEHUMIDIFY_SUPPORTED: self._has_dehumidify_support,
|
ATTR_DEHUMIDIFY_SUPPORTED: self._has_dehumidify_support,
|
||||||
ATTR_MIN_HUMIDITY: round(
|
ATTR_HUMIDIFY_SUPPORTED: self._has_humidify_support,
|
||||||
self.thermostat.get_humidity_setpoint_limits()[0] * 100.0, 1,
|
|
||||||
),
|
|
||||||
ATTR_MAX_HUMIDITY: round(
|
|
||||||
self.thermostat.get_humidity_setpoint_limits()[1] * 100.0, 1,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if self._has_dehumidify_support:
|
if self._has_dehumidify_support:
|
||||||
data.update(
|
dehumdify_setpoint = percent_conv(
|
||||||
{
|
self._thermostat.get_dehumidify_setpoint()
|
||||||
ATTR_DEHUMIDIFY_SETPOINT: round(
|
|
||||||
self.thermostat.get_dehumidify_setpoint() * 100.0, 1
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
data[ATTR_DEHUMIDIFY_SETPOINT] = dehumdify_setpoint
|
||||||
|
|
||||||
if self._has_humidify_support:
|
if self._has_humidify_support:
|
||||||
data.update(
|
humdify_setpoint = percent_conv(self._thermostat.get_humidify_setpoint())
|
||||||
{
|
data[ATTR_HUMIDIFY_SETPOINT] = humdify_setpoint
|
||||||
ATTR_HUMIDIFY_SETPOINT: round(
|
|
||||||
self.thermostat.get_humidify_setpoint() * 100.0, 1
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def set_preset_mode(self, preset_mode: str):
|
def set_preset_mode(self, preset_mode: str):
|
||||||
"""Set the preset mode."""
|
"""Set the preset mode."""
|
||||||
self._device.set_preset(preset_mode)
|
self._zone.set_preset(preset_mode)
|
||||||
self.schedule_update_ha_state()
|
self._signal_zone_update()
|
||||||
|
|
||||||
def turn_aux_heat_off(self):
|
def turn_aux_heat_off(self):
|
||||||
"""Turn. Aux Heat off."""
|
"""Turn. Aux Heat off."""
|
||||||
self.thermostat.set_emergency_heat(False)
|
self._thermostat.set_emergency_heat(False)
|
||||||
self.schedule_update_ha_state()
|
self._signal_thermostat_update()
|
||||||
|
|
||||||
def turn_aux_heat_on(self):
|
def turn_aux_heat_on(self):
|
||||||
"""Turn. Aux Heat on."""
|
"""Turn. Aux Heat on."""
|
||||||
self.thermostat.set_emergency_heat(True)
|
self._thermostat.set_emergency_heat(True)
|
||||||
self.schedule_update_ha_state()
|
self._signal_thermostat_update()
|
||||||
|
|
||||||
def turn_off(self):
|
def turn_off(self):
|
||||||
"""Turn. off the zone."""
|
"""Turn. off the zone."""
|
||||||
self.set_hvac_mode(OPERATION_MODE_OFF)
|
self.set_hvac_mode(OPERATION_MODE_OFF)
|
||||||
self.schedule_update_ha_state()
|
self._signal_zone_update()
|
||||||
|
|
||||||
def turn_on(self):
|
def turn_on(self):
|
||||||
"""Turn. on the zone."""
|
"""Turn. on the zone."""
|
||||||
self.set_hvac_mode(OPERATION_MODE_AUTO)
|
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:
|
def set_hvac_mode(self, hvac_mode: str) -> None:
|
||||||
"""Set the system mode (Auto, Heat_Cool, Cool, Heat, etc)."""
|
"""Set the system mode (Auto, Heat_Cool, Cool, Heat, etc)."""
|
||||||
if hvac_mode == HVAC_MODE_AUTO:
|
if hvac_mode == HVAC_MODE_AUTO:
|
||||||
self._device.call_return_to_schedule()
|
self._zone.call_return_to_schedule()
|
||||||
self._device.set_mode(mode=OPERATION_MODE_AUTO)
|
self._zone.set_mode(mode=OPERATION_MODE_AUTO)
|
||||||
else:
|
else:
|
||||||
self._device.call_permanent_hold()
|
self._zone.call_permanent_hold()
|
||||||
self._device.set_mode(mode=HA_TO_NEXIA_HVAC_MODE_MAP[hvac_mode])
|
self._zone.set_mode(mode=HA_TO_NEXIA_HVAC_MODE_MAP[hvac_mode])
|
||||||
|
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
def set_aircleaner_mode(self, aircleaner_mode):
|
def set_aircleaner_mode(self, aircleaner_mode):
|
||||||
"""Set the aircleaner mode."""
|
"""Set the aircleaner mode."""
|
||||||
self.thermostat.set_air_cleaner(aircleaner_mode)
|
self._thermostat.set_air_cleaner(aircleaner_mode)
|
||||||
self.schedule_update_ha_state()
|
self._signal_thermostat_update()
|
||||||
|
|
||||||
def set_humidify_setpoint(self, humidify_setpoint):
|
def set_humidify_setpoint(self, humidity):
|
||||||
"""Set the humidify setpoint."""
|
"""Set the humidify setpoint."""
|
||||||
self.thermostat.set_humidify_setpoint(humidify_setpoint / 100.0)
|
self._thermostat.set_humidify_setpoint(humidity / 100.0)
|
||||||
self.schedule_update_ha_state()
|
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):
|
async def async_update(self):
|
||||||
"""Update the entity.
|
"""Update the entity.
|
||||||
|
@ -7,7 +7,6 @@ ATTRIBUTION = "Data provided by mynexia.com"
|
|||||||
NOTIFICATION_ID = "nexia_notification"
|
NOTIFICATION_ID = "nexia_notification"
|
||||||
NOTIFICATION_TITLE = "Nexia Setup"
|
NOTIFICATION_TITLE = "Nexia Setup"
|
||||||
|
|
||||||
DATA_NEXIA = "nexia"
|
|
||||||
NEXIA_DEVICE = "device"
|
NEXIA_DEVICE = "device"
|
||||||
NEXIA_SCAN_INTERVAL = "scan_interval"
|
NEXIA_SCAN_INTERVAL = "scan_interval"
|
||||||
|
|
||||||
@ -16,6 +15,8 @@ DEFAULT_ENTITY_NAMESPACE = "nexia"
|
|||||||
|
|
||||||
ATTR_DESCRIPTION = "description"
|
ATTR_DESCRIPTION = "description"
|
||||||
|
|
||||||
|
ATTR_AIRCLEANER_MODE = "aircleaner_mode"
|
||||||
|
|
||||||
ATTR_ZONE_STATUS = "zone_status"
|
ATTR_ZONE_STATUS = "zone_status"
|
||||||
ATTR_HUMIDIFY_SUPPORTED = "humidify_supported"
|
ATTR_HUMIDIFY_SUPPORTED = "humidify_supported"
|
||||||
ATTR_DEHUMIDIFY_SUPPORTED = "dehumidify_supported"
|
ATTR_DEHUMIDIFY_SUPPORTED = "dehumidify_supported"
|
||||||
@ -24,5 +25,7 @@ ATTR_DEHUMIDIFY_SETPOINT = "dehumidify_setpoint"
|
|||||||
|
|
||||||
UPDATE_COORDINATOR = "update_coordinator"
|
UPDATE_COORDINATOR = "update_coordinator"
|
||||||
|
|
||||||
|
|
||||||
MANUFACTURER = "Trane"
|
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."""
|
"""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 homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
ATTRIBUTION,
|
||||||
|
DOMAIN,
|
||||||
|
MANUFACTURER,
|
||||||
|
SIGNAL_THERMOSTAT_UPDATE,
|
||||||
|
SIGNAL_ZONE_UPDATE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class NexiaEntity(Entity):
|
class NexiaEntity(Entity):
|
||||||
"""Base class for nexia entities."""
|
"""Base class for nexia entities."""
|
||||||
|
|
||||||
def __init__(self, coordinator):
|
def __init__(self, coordinator, name, unique_id):
|
||||||
"""Initialize the entity."""
|
"""Initialize the entity."""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
self._unique_id = unique_id
|
||||||
|
self._name = name
|
||||||
self._coordinator = coordinator
|
self._coordinator = coordinator
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -16,6 +28,23 @@ class NexiaEntity(Entity):
|
|||||||
"""Return True if entity is available."""
|
"""Return True if entity is available."""
|
||||||
return self._coordinator.last_update_success
|
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
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
"""Return False, updates are controlled via coordinator."""
|
"""Return False, updates are controlled via coordinator."""
|
||||||
@ -28,3 +57,77 @@ class NexiaEntity(Entity):
|
|||||||
async def async_will_remove_from_hass(self):
|
async def async_will_remove_from_hass(self):
|
||||||
"""Undo subscription."""
|
"""Undo subscription."""
|
||||||
self._coordinator.async_remove_listener(self.async_write_ha_state)
|
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."""
|
"""Support for Nexia Automations."""
|
||||||
|
|
||||||
from homeassistant.components.scene import Scene
|
from homeassistant.components.scene import Scene
|
||||||
from homeassistant.const import ATTR_ATTRIBUTION
|
from homeassistant.helpers.event import async_call_later
|
||||||
|
|
||||||
from .const import (
|
from .const import ATTR_DESCRIPTION, DOMAIN, NEXIA_DEVICE, UPDATE_COORDINATOR
|
||||||
ATTR_DESCRIPTION,
|
|
||||||
ATTRIBUTION,
|
|
||||||
DATA_NEXIA,
|
|
||||||
DOMAIN,
|
|
||||||
NEXIA_DEVICE,
|
|
||||||
UPDATE_COORDINATOR,
|
|
||||||
)
|
|
||||||
from .entity import NexiaEntity
|
from .entity import NexiaEntity
|
||||||
|
|
||||||
|
SCENE_ACTIVATION_TIME = 5
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up automations for a Nexia device."""
|
"""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]
|
nexia_home = nexia_data[NEXIA_DEVICE]
|
||||||
coordinator = nexia_data[UPDATE_COORDINATOR]
|
coordinator = nexia_data[UPDATE_COORDINATOR]
|
||||||
entities = []
|
entities = []
|
||||||
@ -36,33 +31,28 @@ class NexiaAutomationScene(NexiaEntity, Scene):
|
|||||||
|
|
||||||
def __init__(self, coordinator, automation):
|
def __init__(self, coordinator, automation):
|
||||||
"""Initialize the automation scene."""
|
"""Initialize the automation scene."""
|
||||||
super().__init__(coordinator)
|
super().__init__(
|
||||||
|
coordinator, name=automation.name, unique_id=automation.automation_id,
|
||||||
|
)
|
||||||
self._automation = automation
|
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
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
"""Return the scene specific state attributes."""
|
"""Return the scene specific state attributes."""
|
||||||
return {
|
data = super().device_state_attributes
|
||||||
ATTR_ATTRIBUTION: ATTRIBUTION,
|
data[ATTR_DESCRIPTION] = self._automation.description
|
||||||
ATTR_DESCRIPTION: self._automation.description,
|
return data
|
||||||
}
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def icon(self):
|
def icon(self):
|
||||||
"""Return the icon of the automation scene."""
|
"""Return the icon of the automation scene."""
|
||||||
return "mdi:script-text-outline"
|
return "mdi:script-text-outline"
|
||||||
|
|
||||||
def activate(self):
|
async def async_activate(self):
|
||||||
"""Activate an automation scene."""
|
"""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 nexia.const import UNIT_CELSIUS
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ATTRIBUTION,
|
|
||||||
DEVICE_CLASS_HUMIDITY,
|
DEVICE_CLASS_HUMIDITY,
|
||||||
DEVICE_CLASS_TEMPERATURE,
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
TEMP_CELSIUS,
|
TEMP_CELSIUS,
|
||||||
@ -11,21 +10,15 @@ from homeassistant.const import (
|
|||||||
UNIT_PERCENTAGE,
|
UNIT_PERCENTAGE,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .const import (
|
from .const import DOMAIN, NEXIA_DEVICE, UPDATE_COORDINATOR
|
||||||
ATTRIBUTION,
|
from .entity import NexiaThermostatEntity, NexiaThermostatZoneEntity
|
||||||
DATA_NEXIA,
|
from .util import percent_conv
|
||||||
DOMAIN,
|
|
||||||
MANUFACTURER,
|
|
||||||
NEXIA_DEVICE,
|
|
||||||
UPDATE_COORDINATOR,
|
|
||||||
)
|
|
||||||
from .entity import NexiaEntity
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up sensors for a Nexia device."""
|
"""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]
|
nexia_home = nexia_data[NEXIA_DEVICE]
|
||||||
coordinator = nexia_data[UPDATE_COORDINATOR]
|
coordinator = nexia_data[UPDATE_COORDINATOR]
|
||||||
entities = []
|
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)
|
thermostat = nexia_home.get_thermostat_by_id(thermostat_id)
|
||||||
|
|
||||||
entities.append(
|
entities.append(
|
||||||
NexiaSensor(
|
NexiaThermostatSensor(
|
||||||
coordinator,
|
coordinator,
|
||||||
thermostat,
|
thermostat,
|
||||||
"get_system_status",
|
"get_system_status",
|
||||||
@ -46,7 +39,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
)
|
)
|
||||||
# Air cleaner
|
# Air cleaner
|
||||||
entities.append(
|
entities.append(
|
||||||
NexiaSensor(
|
NexiaThermostatSensor(
|
||||||
coordinator,
|
coordinator,
|
||||||
thermostat,
|
thermostat,
|
||||||
"get_air_cleaner_mode",
|
"get_air_cleaner_mode",
|
||||||
@ -58,7 +51,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
# Compressor Speed
|
# Compressor Speed
|
||||||
if thermostat.has_variable_speed_compressor():
|
if thermostat.has_variable_speed_compressor():
|
||||||
entities.append(
|
entities.append(
|
||||||
NexiaSensor(
|
NexiaThermostatSensor(
|
||||||
coordinator,
|
coordinator,
|
||||||
thermostat,
|
thermostat,
|
||||||
"get_current_compressor_speed",
|
"get_current_compressor_speed",
|
||||||
@ -69,7 +62,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
entities.append(
|
entities.append(
|
||||||
NexiaSensor(
|
NexiaThermostatSensor(
|
||||||
coordinator,
|
coordinator,
|
||||||
thermostat,
|
thermostat,
|
||||||
"get_requested_compressor_speed",
|
"get_requested_compressor_speed",
|
||||||
@ -87,7 +80,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
else TEMP_FAHRENHEIT
|
else TEMP_FAHRENHEIT
|
||||||
)
|
)
|
||||||
entities.append(
|
entities.append(
|
||||||
NexiaSensor(
|
NexiaThermostatSensor(
|
||||||
coordinator,
|
coordinator,
|
||||||
thermostat,
|
thermostat,
|
||||||
"get_outdoor_temperature",
|
"get_outdoor_temperature",
|
||||||
@ -99,7 +92,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
# Relative Humidity
|
# Relative Humidity
|
||||||
if thermostat.has_relative_humidity():
|
if thermostat.has_relative_humidity():
|
||||||
entities.append(
|
entities.append(
|
||||||
NexiaSensor(
|
NexiaThermostatSensor(
|
||||||
coordinator,
|
coordinator,
|
||||||
thermostat,
|
thermostat,
|
||||||
"get_relative_humidity",
|
"get_relative_humidity",
|
||||||
@ -120,7 +113,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
)
|
)
|
||||||
# Temperature
|
# Temperature
|
||||||
entities.append(
|
entities.append(
|
||||||
NexiaZoneSensor(
|
NexiaThermostatZoneSensor(
|
||||||
coordinator,
|
coordinator,
|
||||||
zone,
|
zone,
|
||||||
"get_temperature",
|
"get_temperature",
|
||||||
@ -132,13 +125,13 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
)
|
)
|
||||||
# Zone Status
|
# Zone Status
|
||||||
entities.append(
|
entities.append(
|
||||||
NexiaZoneSensor(
|
NexiaThermostatZoneSensor(
|
||||||
coordinator, zone, "get_status", "Zone Status", None, None,
|
coordinator, zone, "get_status", "Zone Status", None, None,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
# Setpoint Status
|
# Setpoint Status
|
||||||
entities.append(
|
entities.append(
|
||||||
NexiaZoneSensor(
|
NexiaThermostatZoneSensor(
|
||||||
coordinator,
|
coordinator,
|
||||||
zone,
|
zone,
|
||||||
"get_setpoint_status",
|
"get_setpoint_status",
|
||||||
@ -151,18 +144,13 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
async_add_entities(entities, True)
|
async_add_entities(entities, True)
|
||||||
|
|
||||||
|
|
||||||
def percent_conv(val):
|
class NexiaThermostatSensor(NexiaThermostatEntity):
|
||||||
"""Convert an actual percentage (0.0-1.0) to 0-100 scale."""
|
|
||||||
return val * 100.0
|
|
||||||
|
|
||||||
|
|
||||||
class NexiaSensor(NexiaEntity):
|
|
||||||
"""Provides Nexia thermostat sensor support."""
|
"""Provides Nexia thermostat sensor support."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator,
|
coordinator,
|
||||||
device,
|
thermostat,
|
||||||
sensor_call,
|
sensor_call,
|
||||||
sensor_name,
|
sensor_name,
|
||||||
sensor_class,
|
sensor_class,
|
||||||
@ -170,35 +158,18 @@ class NexiaSensor(NexiaEntity):
|
|||||||
modifier=None,
|
modifier=None,
|
||||||
):
|
):
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
super().__init__(coordinator)
|
super().__init__(
|
||||||
self._coordinator = coordinator
|
coordinator,
|
||||||
self._device = device
|
thermostat,
|
||||||
|
name=f"{thermostat.get_name()} {sensor_name}",
|
||||||
|
unique_id=f"{thermostat.thermostat_id}_{sensor_call}",
|
||||||
|
)
|
||||||
self._call = sensor_call
|
self._call = sensor_call
|
||||||
self._sensor_name = sensor_name
|
|
||||||
self._class = sensor_class
|
self._class = sensor_class
|
||||||
self._state = None
|
self._state = None
|
||||||
self._name = f"{self._device.get_name()} {self._sensor_name}"
|
|
||||||
self._unit_of_measurement = sensor_unit
|
self._unit_of_measurement = sensor_unit
|
||||||
self._modifier = modifier
|
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
|
@property
|
||||||
def device_class(self):
|
def device_class(self):
|
||||||
"""Return the device class of the sensor."""
|
"""Return the device class of the sensor."""
|
||||||
@ -207,7 +178,7 @@ class NexiaSensor(NexiaEntity):
|
|||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
"""Return the state of the sensor."""
|
"""Return the state of the sensor."""
|
||||||
val = getattr(self._device, self._call)()
|
val = getattr(self._thermostat, self._call)()
|
||||||
if self._modifier:
|
if self._modifier:
|
||||||
val = self._modifier(val)
|
val = self._modifier(val)
|
||||||
if isinstance(val, float):
|
if isinstance(val, float):
|
||||||
@ -219,25 +190,14 @@ class NexiaSensor(NexiaEntity):
|
|||||||
"""Return the unit of measurement this sensor expresses itself in."""
|
"""Return the unit of measurement this sensor expresses itself in."""
|
||||||
return self._unit_of_measurement
|
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 NexiaThermostatZoneSensor(NexiaThermostatZoneEntity):
|
||||||
class NexiaZoneSensor(NexiaSensor):
|
|
||||||
"""Nexia Zone Sensor Support."""
|
"""Nexia Zone Sensor Support."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator,
|
coordinator,
|
||||||
device,
|
zone,
|
||||||
sensor_call,
|
sensor_call,
|
||||||
sensor_name,
|
sensor_name,
|
||||||
sensor_class,
|
sensor_class,
|
||||||
@ -248,29 +208,32 @@ class NexiaZoneSensor(NexiaSensor):
|
|||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
coordinator,
|
coordinator,
|
||||||
device,
|
zone,
|
||||||
sensor_call,
|
name=f"{zone.get_name()} {sensor_name}",
|
||||||
sensor_name,
|
unique_id=f"{zone.zone_id}_{sensor_call}",
|
||||||
sensor_class,
|
|
||||||
sensor_unit,
|
|
||||||
modifier,
|
|
||||||
)
|
)
|
||||||
self._device = device
|
self._call = sensor_call
|
||||||
|
self._class = sensor_class
|
||||||
|
self._state = None
|
||||||
|
self._unit_of_measurement = sensor_unit
|
||||||
|
self._modifier = modifier
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def device_class(self):
|
||||||
"""Return the unique id of the sensor."""
|
"""Return the device class of the sensor."""
|
||||||
# This is the zone unique_id
|
return self._class
|
||||||
return f"{self._device.zone_id}_{self._call}"
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_info(self):
|
def state(self):
|
||||||
"""Return the device_info of the device."""
|
"""Return the state of the sensor."""
|
||||||
return {
|
val = getattr(self._zone, self._call)()
|
||||||
"identifiers": {(DOMAIN, self._device.zone_id)},
|
if self._modifier:
|
||||||
"name": self._device.get_name(),
|
val = self._modifier(val)
|
||||||
"model": self._device.thermostat.get_model(),
|
if isinstance(val, float):
|
||||||
"sw_version": self._device.thermostat.get_firmware(),
|
val = round(val, 1)
|
||||||
"manufacturer": MANUFACTURER,
|
return val
|
||||||
"via_device": (DOMAIN, self._device.thermostat.thermostat_id),
|
|
||||||
}
|
@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(
|
assert all(
|
||||||
state.attributes[key] == expected_attributes[key] for key in expected_attributes
|
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
|
from .util import async_init_integration
|
||||||
|
|
||||||
|
|
||||||
async def test_automation_scenees(hass):
|
async def test_automation_scenes(hass):
|
||||||
"""Test creation automation scenees."""
|
"""Test creation automation scenes."""
|
||||||
|
|
||||||
await async_init_integration(hass)
|
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